From 5b4431818b44bda89a9b4ae32fbeb51a65346bb2 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 23 Jun 2023 01:11:37 +0800 Subject: [PATCH 0001/1069] feat: code migration --- {front-end => chat2db-client}/.gitignore | 0 {front-end => chat2db-client}/.npmrc | 0 {front-end => chat2db-client}/.prettierignore | 0 {front-end => chat2db-client}/.prettierrc | 0 {front-end => chat2db-client}/.umirc.prod.ts | 0 {front-end => chat2db-client}/.umirc.ts | 0 .../.vscode/settings.json | 0 chat2db-client/mock/sqlResult.json | 169 +++++ {front-end => chat2db-client}/package.json | 0 {front-end => chat2db-client}/readme.md | 0 .../src/blocks/Setting/index.less | 0 .../src/blocks/Setting/index.tsx | 0 .../src/components/BrandLogo/index.less | 0 .../src/components/BrandLogo/index.tsx | 0 .../components/Console/ChatInput/index.less | 0 .../components/Console/ChatInput/index.tsx | 0 .../Console/MonacoEditor/index.less | 0 .../components/Console/MonacoEditor/index.tsx | 0 .../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 .../src/components/Console/index.less | 0 .../src/components/Console/index.tsx | 33 +- .../CreateConnection/config/dataSource.ts | 0 .../CreateConnection/config/enum.ts | 0 .../CreateConnection/config/types.ts | 0 .../components/CreateConnection/index.less | 0 .../src/components/CreateConnection/index.tsx | 577 ++++++++++++++++++ .../components/DraggableContainer/index.less | 0 .../components/DraggableContainer/index.tsx | 0 .../src/components/Iconfont/desktop.less | 0 .../src/components/Iconfont/index.tsx | 0 .../src/components/Iconfont/prod.less | 0 .../src/components/LayoutBasic/index.less | 0 .../src/components/LayoutBasic/index.tsx | 0 .../components/Loading/LazyLoading/index.less | 0 .../components/Loading/LazyLoading/index.tsx | 0 .../src/components/Loading/Loading/index.less | 0 .../src/components/Loading/Loading/index.tsx | 0 .../Loading/LoadingContent/index.less | 0 .../Loading/LoadingContent/index.tsx | 0 .../Loading/LoadingLiquid/index.less | 0 .../Loading/LoadingLiquid/index.tsx | 0 .../src/components/SearchResult/index.less | 120 ++++ .../src/components/SearchResult/index.tsx | 199 ++++++ .../src/components/StateIndicator/index.less | 0 .../src/components/StateIndicator/index.tsx | 0 chat2db-client/src/components/Tabs/index.less | 50 ++ chat2db-client/src/components/Tabs/index.tsx | 37 ++ .../src/components/XXXX_FN/index.less | 0 .../src/components/XXXX_FN/index.tsx | 0 .../src/constants/appConfig.ts | 0 .../src/constants/common.ts | 0 .../src/constants/database.ts | 0 .../src/constants/environment.ts | 0 .../src/constants/index.ts | 0 .../src/constants/monacoEditor.ts | 0 chat2db-client/src/constants/table.ts | 22 + .../src/constants/theme.ts | 0 .../src/constants/tree.ts | 0 .../src/hooks/index.ts | 0 .../src/hooks/useEventSource.ts | 0 .../src/hooks/useTheme.ts | 0 .../src/hooks/useUpdateEffect.ts | 0 .../src/i18n/en-us/common.ts | 11 +- .../src/i18n/en-us/connection.ts | 2 +- .../src/i18n/en-us/dashboard.ts | 0 .../src/i18n/en-us/index.ts | 0 .../src/i18n/en-us/menu.ts | 0 .../src/i18n/en-us/setting.ts | 0 .../src/i18n/en-us/workspace.ts | 0 .../src/i18n/index.tsx | 0 .../src/i18n/zh-cn/common.ts | 1 + .../src/i18n/zh-cn/connection.ts | 0 .../src/i18n/zh-cn/dashboard.ts | 0 .../src/i18n/zh-cn/index.ts | 0 .../src/i18n/zh-cn/menu.ts | 0 .../src/i18n/zh-cn/setting.ts | 0 .../src/i18n/zh-cn/workspace.ts | 0 .../src/layouts/index.less | 0 .../src/layouts/index.tsx | 0 .../src/main/constants.js | 0 .../src/main/i18n/en/index.js | 0 .../src/main/i18n/index.js | 0 .../src/main/i18n/zh-cn/index.js | 0 .../src/main/index.js | 0 .../src/main/menu.js | 0 .../src/main/preload.js | 0 .../src/main/utils.js | 0 .../src/models/global.ts | 0 .../src/pages/demo/index.less | 0 .../src/pages/demo/index.tsx | 0 .../src/pages/document.ejs | 0 .../src/pages/main/chat/index.less | 0 .../src/pages/main/chat/index.tsx | 0 .../src/pages/main/connections/index.less | 28 +- .../src/pages/main/connections/index.tsx | 173 ++++++ .../main/dashboard/chart-item/index.less | 3 +- .../pages/main/dashboard/chart-item/index.tsx | 10 +- .../pages/main/dashboard/chart/bar/index.less | 0 .../pages/main/dashboard/chart/bar/index.tsx | 2 +- .../main/dashboard/chart/line/index.less | 0 .../pages/main/dashboard/chart/line/index.tsx | 0 .../pages/main/dashboard/chart/pie/index.less | 0 .../pages/main/dashboard/chart/pie/index.tsx | 6 +- .../main/dashboard/echart-test/index.less | 0 .../main/dashboard/echart-test/index.tsx | 0 .../src/pages/main/dashboard/index.less | 0 .../src/pages/main/dashboard/index.tsx | 18 +- .../src/pages/main/index.less | 0 .../src/pages/main/index.tsx | 0 .../Tree/TreeNodeRightClick/index.less | 0 .../Tree/TreeNodeRightClick/index.tsx | 0 .../main/workspace/components/Tree/index.less | 0 .../main/workspace/components/Tree/index.tsx | 0 .../workspace/components/Tree/treeConfig.tsx | 0 .../components/WorkspaceLeft/index.less | 0 .../components/WorkspaceLeft/index.tsx | 0 .../components/WorkspaceRight/index.less | 12 +- .../components/WorkspaceRight/index.tsx | 105 ++-- .../src/pages/main/workspace/context.ts | 0 .../src/pages/main/workspace/index.less | 0 .../src/pages/main/workspace/index.tsx | 25 +- .../src/pages/test/index.less | 0 .../src/pages/test/index.tsx | 0 .../src/service/base.ts | 2 + .../src/service/config.ts | 0 .../src/service/connection.ts | 0 .../src/service/dashboard.ts | 0 .../src/service/history.ts | 0 .../src/service/misc.tsx | 0 .../src/service/sql.ts | 0 .../src/service/user.ts | 0 .../src/styles/global.less | 0 .../src/styles/var.less | 0 .../src/theme/abandon/dark.less | 0 .../src/theme/abandon/demo/dark.less | 0 .../src/theme/abandon/demo/light.less | 0 .../src/theme/abandon/light.less | 0 .../src/theme/abandon/primaryColor.less | 0 .../src/theme/common.ts | 4 +- .../src/theme/dark.ts | 1 + .../src/theme/index.ts | 0 .../src/theme/light.ts | 1 + .../src/typings/common.ts | 0 .../src/typings/connection.ts | 2 +- .../src/typings/dashboard.ts | 0 chat2db-client/src/typings/database.ts | 23 + .../src/typings/index.ts | 0 .../src/typings/main.ts | 0 .../src/typings/theme.ts | 0 .../src/typings/tree.ts | 0 .../src/utils/check.ts | 0 .../src/utils/common.ts | 0 .../src/utils/date.ts | 0 .../src/utils/eventSource.ts | 0 .../src/utils/getTree.ts | 0 .../src/utils/index.ts | 0 .../src/utils/localStorage.ts | 0 {front-end => chat2db-client}/tsconfig.json | 0 {front-end => chat2db-client}/typings.d.ts | 0 front-end/mock/sqlResult.json | 64 -- front-end/src/assets/font/iconfont.css | 383 ------------ front-end/src/assets/font/iconfont.ttf | Bin 31224 -> 0 bytes front-end/src/assets/font/iconfont.woff | Bin 19684 -> 0 bytes front-end/src/assets/font/iconfont.woff2 | Bin 17076 -> 0 bytes front-end/src/assets/img/add.svg | 15 - front-end/src/assets/img/ai.svg | 30 - front-end/src/assets/img/connection.svg | 17 - front-end/src/assets/img/databaseImg/h2.png | Bin 12968 -> 0 bytes .../src/assets/img/databaseImg/mysql.png | Bin 2802 -> 0 bytes .../src/assets/img/databaseImg/other.png | Bin 844 -> 0 bytes .../src/assets/img/databaseImg/redis.png | Bin 3552 -> 0 bytes front-end/src/assets/img/theme-auto.png | Bin 3484 -> 0 bytes front-end/src/assets/img/theme-dark.png | Bin 1994 -> 0 bytes front-end/src/assets/img/theme-light.png | Bin 3408 -> 0 bytes front-end/src/assets/logo/logo.icns | Bin 1714502 -> 0 bytes front-end/src/assets/logo/logo.png | Bin 918232 -> 0 bytes front-end/src/assets/logo/logo.webp | Bin 68658 -> 0 bytes .../src/components/CreateConnection/index.tsx | 540 ---------------- .../src/pages/main/connections/index.tsx | 132 ---- front-end/src/typings/database.ts | 8 - 201 files changed, 1530 insertions(+), 1295 deletions(-) rename {front-end => chat2db-client}/.gitignore (100%) rename {front-end => chat2db-client}/.npmrc (100%) rename {front-end => chat2db-client}/.prettierignore (100%) rename {front-end => chat2db-client}/.prettierrc (100%) rename {front-end => chat2db-client}/.umirc.prod.ts (100%) rename {front-end => chat2db-client}/.umirc.ts (100%) rename {front-end => chat2db-client}/.vscode/settings.json (100%) create mode 100644 chat2db-client/mock/sqlResult.json rename {front-end => chat2db-client}/package.json (100%) rename {front-end => chat2db-client}/readme.md (100%) rename {front-end => chat2db-client}/src/blocks/Setting/index.less (100%) rename {front-end => chat2db-client}/src/blocks/Setting/index.tsx (100%) rename {front-end => chat2db-client}/src/components/BrandLogo/index.less (100%) rename {front-end => chat2db-client}/src/components/BrandLogo/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Console/ChatInput/index.less (100%) rename {front-end => chat2db-client}/src/components/Console/ChatInput/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/index.less (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/index.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts (100%) rename {front-end => chat2db-client}/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts (100%) rename {front-end => chat2db-client}/src/components/Console/index.less (100%) rename {front-end => chat2db-client}/src/components/Console/index.tsx (92%) rename {front-end => chat2db-client}/src/components/CreateConnection/config/dataSource.ts (100%) rename {front-end => chat2db-client}/src/components/CreateConnection/config/enum.ts (100%) rename {front-end => chat2db-client}/src/components/CreateConnection/config/types.ts (100%) rename {front-end => chat2db-client}/src/components/CreateConnection/index.less (100%) create mode 100644 chat2db-client/src/components/CreateConnection/index.tsx rename {front-end => chat2db-client}/src/components/DraggableContainer/index.less (100%) rename {front-end => chat2db-client}/src/components/DraggableContainer/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Iconfont/desktop.less (100%) rename {front-end => chat2db-client}/src/components/Iconfont/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Iconfont/prod.less (100%) rename {front-end => chat2db-client}/src/components/LayoutBasic/index.less (100%) rename {front-end => chat2db-client}/src/components/LayoutBasic/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Loading/LazyLoading/index.less (100%) rename {front-end => chat2db-client}/src/components/Loading/LazyLoading/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Loading/Loading/index.less (100%) rename {front-end => chat2db-client}/src/components/Loading/Loading/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Loading/LoadingContent/index.less (100%) rename {front-end => chat2db-client}/src/components/Loading/LoadingContent/index.tsx (100%) rename {front-end => chat2db-client}/src/components/Loading/LoadingLiquid/index.less (100%) rename {front-end => chat2db-client}/src/components/Loading/LoadingLiquid/index.tsx (100%) create mode 100644 chat2db-client/src/components/SearchResult/index.less create mode 100644 chat2db-client/src/components/SearchResult/index.tsx rename {front-end => chat2db-client}/src/components/StateIndicator/index.less (100%) rename {front-end => chat2db-client}/src/components/StateIndicator/index.tsx (100%) create mode 100644 chat2db-client/src/components/Tabs/index.less create mode 100644 chat2db-client/src/components/Tabs/index.tsx rename {front-end => chat2db-client}/src/components/XXXX_FN/index.less (100%) rename {front-end => chat2db-client}/src/components/XXXX_FN/index.tsx (100%) rename {front-end => chat2db-client}/src/constants/appConfig.ts (100%) rename {front-end => chat2db-client}/src/constants/common.ts (100%) rename {front-end => chat2db-client}/src/constants/database.ts (100%) rename {front-end => chat2db-client}/src/constants/environment.ts (100%) rename {front-end => chat2db-client}/src/constants/index.ts (100%) rename {front-end => chat2db-client}/src/constants/monacoEditor.ts (100%) create mode 100644 chat2db-client/src/constants/table.ts rename {front-end => chat2db-client}/src/constants/theme.ts (100%) rename {front-end => chat2db-client}/src/constants/tree.ts (100%) rename {front-end => chat2db-client}/src/hooks/index.ts (100%) rename {front-end => chat2db-client}/src/hooks/useEventSource.ts (100%) rename {front-end => chat2db-client}/src/hooks/useTheme.ts (100%) rename {front-end => chat2db-client}/src/hooks/useUpdateEffect.ts (100%) rename {front-end => chat2db-client}/src/i18n/en-us/common.ts (79%) rename {front-end => chat2db-client}/src/i18n/en-us/connection.ts (94%) rename {front-end => chat2db-client}/src/i18n/en-us/dashboard.ts (100%) rename {front-end => chat2db-client}/src/i18n/en-us/index.ts (100%) rename {front-end => chat2db-client}/src/i18n/en-us/menu.ts (100%) rename {front-end => chat2db-client}/src/i18n/en-us/setting.ts (100%) rename {front-end => chat2db-client}/src/i18n/en-us/workspace.ts (100%) rename {front-end => chat2db-client}/src/i18n/index.tsx (100%) rename {front-end => chat2db-client}/src/i18n/zh-cn/common.ts (96%) rename {front-end => chat2db-client}/src/i18n/zh-cn/connection.ts (100%) rename {front-end => chat2db-client}/src/i18n/zh-cn/dashboard.ts (100%) rename {front-end => chat2db-client}/src/i18n/zh-cn/index.ts (100%) rename {front-end => chat2db-client}/src/i18n/zh-cn/menu.ts (100%) rename {front-end => chat2db-client}/src/i18n/zh-cn/setting.ts (100%) rename {front-end => chat2db-client}/src/i18n/zh-cn/workspace.ts (100%) rename {front-end => chat2db-client}/src/layouts/index.less (100%) rename {front-end => chat2db-client}/src/layouts/index.tsx (100%) rename {front-end => chat2db-client}/src/main/constants.js (100%) rename {front-end => chat2db-client}/src/main/i18n/en/index.js (100%) rename {front-end => chat2db-client}/src/main/i18n/index.js (100%) rename {front-end => chat2db-client}/src/main/i18n/zh-cn/index.js (100%) rename {front-end => chat2db-client}/src/main/index.js (100%) rename {front-end => chat2db-client}/src/main/menu.js (100%) rename {front-end => chat2db-client}/src/main/preload.js (100%) rename {front-end => chat2db-client}/src/main/utils.js (100%) rename {front-end => chat2db-client}/src/models/global.ts (100%) rename {front-end => chat2db-client}/src/pages/demo/index.less (100%) rename {front-end => chat2db-client}/src/pages/demo/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/document.ejs (100%) rename {front-end => chat2db-client}/src/pages/main/chat/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/chat/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/connections/index.less (88%) create mode 100644 chat2db-client/src/pages/main/connections/index.tsx rename {front-end => chat2db-client}/src/pages/main/dashboard/chart-item/index.less (97%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart-item/index.tsx (97%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart/bar/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart/bar/index.tsx (91%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart/line/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart/line/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart/pie/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/chart/pie/index.tsx (95%) rename {front-end => chat2db-client}/src/pages/main/dashboard/echart-test/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/echart-test/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/dashboard/index.tsx (95%) rename {front-end => chat2db-client}/src/pages/main/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/Tree/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/Tree/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/Tree/treeConfig.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/WorkspaceLeft/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/WorkspaceLeft/index.tsx (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/WorkspaceRight/index.less (79%) rename {front-end => chat2db-client}/src/pages/main/workspace/components/WorkspaceRight/index.tsx (74%) rename {front-end => chat2db-client}/src/pages/main/workspace/context.ts (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/index.less (100%) rename {front-end => chat2db-client}/src/pages/main/workspace/index.tsx (62%) rename {front-end => chat2db-client}/src/pages/test/index.less (100%) rename {front-end => chat2db-client}/src/pages/test/index.tsx (100%) rename {front-end => chat2db-client}/src/service/base.ts (98%) rename {front-end => chat2db-client}/src/service/config.ts (100%) rename {front-end => chat2db-client}/src/service/connection.ts (100%) rename {front-end => chat2db-client}/src/service/dashboard.ts (100%) rename {front-end => chat2db-client}/src/service/history.ts (100%) rename {front-end => chat2db-client}/src/service/misc.tsx (100%) rename {front-end => chat2db-client}/src/service/sql.ts (100%) rename {front-end => chat2db-client}/src/service/user.ts (100%) rename {front-end => chat2db-client}/src/styles/global.less (100%) rename {front-end => chat2db-client}/src/styles/var.less (100%) rename {front-end => chat2db-client}/src/theme/abandon/dark.less (100%) rename {front-end => chat2db-client}/src/theme/abandon/demo/dark.less (100%) rename {front-end => chat2db-client}/src/theme/abandon/demo/light.less (100%) rename {front-end => chat2db-client}/src/theme/abandon/light.less (100%) rename {front-end => chat2db-client}/src/theme/abandon/primaryColor.less (100%) rename {front-end => chat2db-client}/src/theme/common.ts (58%) rename {front-end => chat2db-client}/src/theme/dark.ts (96%) rename {front-end => chat2db-client}/src/theme/index.ts (100%) rename {front-end => chat2db-client}/src/theme/light.ts (96%) rename {front-end => chat2db-client}/src/typings/common.ts (100%) rename {front-end => chat2db-client}/src/typings/connection.ts (96%) rename {front-end => chat2db-client}/src/typings/dashboard.ts (100%) create mode 100644 chat2db-client/src/typings/database.ts rename {front-end => chat2db-client}/src/typings/index.ts (100%) rename {front-end => chat2db-client}/src/typings/main.ts (100%) rename {front-end => chat2db-client}/src/typings/theme.ts (100%) rename {front-end => chat2db-client}/src/typings/tree.ts (100%) rename {front-end => chat2db-client}/src/utils/check.ts (100%) rename {front-end => chat2db-client}/src/utils/common.ts (100%) rename {front-end => chat2db-client}/src/utils/date.ts (100%) rename {front-end => chat2db-client}/src/utils/eventSource.ts (100%) rename {front-end => chat2db-client}/src/utils/getTree.ts (100%) rename {front-end => chat2db-client}/src/utils/index.ts (100%) rename {front-end => chat2db-client}/src/utils/localStorage.ts (100%) rename {front-end => chat2db-client}/tsconfig.json (100%) rename {front-end => chat2db-client}/typings.d.ts (100%) delete mode 100644 front-end/mock/sqlResult.json delete mode 100644 front-end/src/assets/font/iconfont.css delete mode 100644 front-end/src/assets/font/iconfont.ttf delete mode 100644 front-end/src/assets/font/iconfont.woff delete mode 100644 front-end/src/assets/font/iconfont.woff2 delete mode 100644 front-end/src/assets/img/add.svg delete mode 100644 front-end/src/assets/img/ai.svg delete mode 100644 front-end/src/assets/img/connection.svg delete mode 100644 front-end/src/assets/img/databaseImg/h2.png delete mode 100644 front-end/src/assets/img/databaseImg/mysql.png delete mode 100644 front-end/src/assets/img/databaseImg/other.png delete mode 100644 front-end/src/assets/img/databaseImg/redis.png delete mode 100644 front-end/src/assets/img/theme-auto.png delete mode 100644 front-end/src/assets/img/theme-dark.png delete mode 100644 front-end/src/assets/img/theme-light.png delete mode 100644 front-end/src/assets/logo/logo.icns delete mode 100644 front-end/src/assets/logo/logo.png delete mode 100644 front-end/src/assets/logo/logo.webp delete mode 100644 front-end/src/components/CreateConnection/index.tsx delete mode 100644 front-end/src/pages/main/connections/index.tsx delete mode 100644 front-end/src/typings/database.ts diff --git a/front-end/.gitignore b/chat2db-client/.gitignore similarity index 100% rename from front-end/.gitignore rename to chat2db-client/.gitignore diff --git a/front-end/.npmrc b/chat2db-client/.npmrc similarity index 100% rename from front-end/.npmrc rename to chat2db-client/.npmrc diff --git a/front-end/.prettierignore b/chat2db-client/.prettierignore similarity index 100% rename from front-end/.prettierignore rename to chat2db-client/.prettierignore diff --git a/front-end/.prettierrc b/chat2db-client/.prettierrc similarity index 100% rename from front-end/.prettierrc rename to chat2db-client/.prettierrc diff --git a/front-end/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts similarity index 100% rename from front-end/.umirc.prod.ts rename to chat2db-client/.umirc.prod.ts diff --git a/front-end/.umirc.ts b/chat2db-client/.umirc.ts similarity index 100% rename from front-end/.umirc.ts rename to chat2db-client/.umirc.ts diff --git a/front-end/.vscode/settings.json b/chat2db-client/.vscode/settings.json similarity index 100% rename from front-end/.vscode/settings.json rename to chat2db-client/.vscode/settings.json diff --git a/chat2db-client/mock/sqlResult.json b/chat2db-client/mock/sqlResult.json new file mode 100644 index 000000000..9bd8bb91b --- /dev/null +++ b/chat2db-client/mock/sqlResult.json @@ -0,0 +1,169 @@ +{ + "success": true, + "errorCode": null, + "errorMessage": null, + "data": [ + { + "sql": "SELECT *\nFROM students\nLIMIT 500", + "description": "执行成功", + "message": null, + "success": true, + "headerList": [ + { + "dataType": "NUMERIC", + "name": "id" + }, + { + "dataType": "STRING", + "name": "name" + }, + { + "dataType": "STRING", + "name": "gender" + }, + { + "dataType": "DATETIME", + "name": "birthday" + }, + { + "dataType": "STRING", + "name": "address" + }, + { + "dataType": "STRING", + "name": "phone" + }, + { + "dataType": "STRING", + "name": "email" + }, + { + "dataType": "DATETIME", + "name": "create_time" + }, + { + "dataType": "DATETIME", + "name": "update_time" + } + ], + "dataList": [ + [ + "1", + "张三", + "男", + null, + "北京市海淀区", + "12345678901", + "zhangsan@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "2", + "李四", + "男", + null, + "上海市浦东新区", + "12345678902", + "lisi@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "3", + "王五", + "女", + null, + "广州市天河区", + "12345678903", + "wangwu@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "4", + "赵六", + "男", + null, + "深圳市南山区", + "12345678904", + "zhaoliu@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "5", + "陈七", + "女", + null, + "武汉市江汉区", + "12345678905", + "chenqi@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "6", + "刘八", + "男", + null, + "成都市高新区", + "12345678906", + "liuba@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "7", + "魏九", + "女", + null, + "重庆市渝北区", + "12345678907", + "weijiu@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "8", + "孙十", + "男", + null, + "南京市鼓楼区", + "12345678908", + "sunshi@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "9", + "郑十一", + "男", + null, + "西安市雁塔区", + "12345678909", + "zhengshiyi@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ], + [ + "10", + "许十二", + "女", + null, + "苏州市姑苏区", + "12345678910", + "xushier@example.com", + "2023-05-31 10:41:56.000", + "2023-05-31 10:41:56.000" + ] + ], + "sqlType": "SELECT", + "hasNextPage": false, + "pageNo": 1, + "pageSize": 500, + "duration": 6 + } + ], + "traceId": null +} diff --git a/front-end/package.json b/chat2db-client/package.json similarity index 100% rename from front-end/package.json rename to chat2db-client/package.json diff --git a/front-end/readme.md b/chat2db-client/readme.md similarity index 100% rename from front-end/readme.md rename to chat2db-client/readme.md diff --git a/front-end/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less similarity index 100% rename from front-end/src/blocks/Setting/index.less rename to chat2db-client/src/blocks/Setting/index.less diff --git a/front-end/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx similarity index 100% rename from front-end/src/blocks/Setting/index.tsx rename to chat2db-client/src/blocks/Setting/index.tsx diff --git a/front-end/src/components/BrandLogo/index.less b/chat2db-client/src/components/BrandLogo/index.less similarity index 100% rename from front-end/src/components/BrandLogo/index.less rename to chat2db-client/src/components/BrandLogo/index.less diff --git a/front-end/src/components/BrandLogo/index.tsx b/chat2db-client/src/components/BrandLogo/index.tsx similarity index 100% rename from front-end/src/components/BrandLogo/index.tsx rename to chat2db-client/src/components/BrandLogo/index.tsx diff --git a/front-end/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less similarity index 100% rename from front-end/src/components/Console/ChatInput/index.less rename to chat2db-client/src/components/Console/ChatInput/index.less diff --git a/front-end/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx similarity index 100% rename from front-end/src/components/Console/ChatInput/index.tsx rename to chat2db-client/src/components/Console/ChatInput/index.tsx diff --git a/front-end/src/components/Console/MonacoEditor/index.less b/chat2db-client/src/components/Console/MonacoEditor/index.less similarity index 100% rename from front-end/src/components/Console/MonacoEditor/index.less rename to chat2db-client/src/components/Console/MonacoEditor/index.less diff --git a/front-end/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx similarity index 100% rename from front-end/src/components/Console/MonacoEditor/index.tsx rename to chat2db-client/src/components/Console/MonacoEditor/index.tsx diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/index.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/index.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/index.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/index.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/index.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/lexer/token.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/chain.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/define.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/index.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/match.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/scanner.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/parser/utils.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/default-opts.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/index.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/monaco-plugin/parser.worker.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/define.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/four-operations.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/parser.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reader.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/reserve-keys.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/base/utils.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/index.tsx diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/index.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/lexer.ts diff --git a/front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts b/chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts similarity index 100% rename from front-end/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts rename to chat2db-client/src/components/Console/MonacoEditor/syntax-parser/plugin/sql-parser/mysql/parser.ts diff --git a/front-end/src/components/Console/index.less b/chat2db-client/src/components/Console/index.less similarity index 100% rename from front-end/src/components/Console/index.less rename to chat2db-client/src/components/Console/index.less diff --git a/front-end/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx similarity index 92% rename from front-end/src/components/Console/index.tsx rename to chat2db-client/src/components/Console/index.tsx index f160b72ee..6d9297c49 100644 --- a/front-end/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -8,7 +8,7 @@ import { format } from 'sql-formatter'; import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import MonacoEditor from 'react-monaco-editor'; -import { useReducerContext } from '@/pages/main/workspace/index' +import { useReducerContext } from '@/pages/main/workspace/index'; import styles from './index.less'; import Loading from '../Loading/Loading'; @@ -43,7 +43,8 @@ interface IProps { consoleId: number; schemaName?: string; consoleName: string; - } + }; + onExecuteSQL: (value: any) => void; } function Console(props: IProps) { @@ -58,7 +59,7 @@ function Console(props: IProps) { useEffect(() => { setContext(value); - }, [value]) + }, [value]); const onPressChatInput = (value: string) => { const params = formatParams({ @@ -108,17 +109,21 @@ function Console(props: IProps) { sql: sqlContent, ...executeParams, }; - sqlServer.executeSql(p).then((res) => { - console.log(res) - let p = { - ...executeParams, - ddl: sqlContent - }; - historyServer.createHistory(p); - // setManageResultDataList(res); - }).catch((error) => { - // setManageResultDataList([]); - }); + sqlServer + .executeSql(p) + .then((res) => { + props.onExecuteSQL && props.onExecuteSQL(res); + // console.log(res) + let p = { + ...executeParams, + ddl: sqlContent, + }; + historyServer.createHistory(p); + // setManageResultDataList(res); + }) + .catch((error) => { + // setManageResultDataList([]); + }); }; const saveWindowTab = () => { diff --git a/front-end/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts similarity index 100% rename from front-end/src/components/CreateConnection/config/dataSource.ts rename to chat2db-client/src/components/CreateConnection/config/dataSource.ts diff --git a/front-end/src/components/CreateConnection/config/enum.ts b/chat2db-client/src/components/CreateConnection/config/enum.ts similarity index 100% rename from front-end/src/components/CreateConnection/config/enum.ts rename to chat2db-client/src/components/CreateConnection/config/enum.ts diff --git a/front-end/src/components/CreateConnection/config/types.ts b/chat2db-client/src/components/CreateConnection/config/types.ts similarity index 100% rename from front-end/src/components/CreateConnection/config/types.ts rename to chat2db-client/src/components/CreateConnection/config/types.ts diff --git a/front-end/src/components/CreateConnection/index.less b/chat2db-client/src/components/CreateConnection/index.less similarity index 100% rename from front-end/src/components/CreateConnection/index.less rename to chat2db-client/src/components/CreateConnection/index.less diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx new file mode 100644 index 000000000..2c55daea2 --- /dev/null +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -0,0 +1,577 @@ +import React, { memo, useEffect, useMemo, useState, Fragment, useContext, useCallback, useLayoutEffect } from 'react'; +import { i18n, isEn } from '@/i18n'; +import styles from './index.less'; +import classnames from 'classnames'; + +import connectionService from '@/service/connection'; + +import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants/database'; +import { dataSourceFormConfigs } from './config/dataSource'; +import { IConnectionConfig, IFormItem, ISelect } from './config/types'; +import { IConnectionDetails } from '@/typings/connection'; +import { InputType } from './config/enum'; +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 { useTheme } from '@/hooks/useTheme'; + +const { Option } = Select; + +type ITabsType = 'ssh' | 'baseInfo'; + +export enum submitType { + UPDATE = 'update', + SAVE = 'save', + TEST = 'test', +} + +interface IProps { + className?: string; + closeCreateConnection: () => void; + connectionData: IConnectionDetails; + submitCallback?: Function; +} + +export default function CreateConnection(props: IProps) { + const { className, closeCreateConnection, submitCallback } = props; + const [baseInfoForm] = Form.useForm(); + const [sshForm] = Form.useForm(); + const [backfillData, setBackfillData] = useState(props.connectionData); + const [loadings, setLoading] = useState({ + confirmButton: false, + testButton: false, + }); + // const [connectionData, setConnectionData] = useState(props.connectionData); + // const [currentType, setCurrentType] = useState(createType || DatabaseTypeCode.MYSQL); + + useEffect(() => { + setBackfillData(props.connectionData); + }, [props.connectionData]); + + useEffect(() => { + if (backfillData.id) { + getConnectionDetails(backfillData.id); + } + }, [backfillData.id]); + + function getConnectionDetails(id: number) { + connectionService.getDetails({ id }).then((res) => { + if (!res) { + return; + } + if (res.user) { + res.authentication = 1; + } else { + res.authentication = 2; + } + setTimeout(() => { + setBackfillData(res); + }, 300); + }); + } + + const getItems = () => [ + { + key: 'ssh', + label: 'SSH Configuration', + children: ( +
+ +
+
+ {i18n('connection.message.testSshConnection')} +
+
+
+ ), + }, + { + key: 'extendInfo', + label: 'Advanced Configuration', + children: ( +
+ +
+ ), + }, + ]; + + // 测试、保存、修改连接 + function saveConnection(type: submitType) { + const ssh = sshForm.getFieldsValue(); + const baseInfo = baseInfoForm.getFieldsValue(); + const extendInfo: any = []; + const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton'; + extendTableData.map((t: any) => { + if (t.label || t.value) { + extendInfo.push({ + key: t.label, + value: t.value, + }); + } + }); + + let p: any = { + ssh, + ...baseInfo, + extendInfo, + // ...values, + ConnectionEnvType: ConnectionEnvType.DAILY, + type: backfillData.type, + }; + + if (type !== submitType.SAVE) { + p.id = backfillData.id; + } + + const api: any = connectionService[type](p); + + setLoading({ + ...loadings, + [loadingsButton]: true, + }); + + api + .then((res: any) => { + if (type === submitType.TEST) { + message.success( + res === false + ? i18n('connection.message.testConnectResult', i18n('common.text.failure')) + : i18n('connection.message.testConnectResult', i18n('common.text.successful')), + ); + } else { + submitCallback?.(); + message.success( + type === submitType.UPDATE + ? i18n('common.message.modifySuccessfully') + : i18n('common.message.addedSuccessfully'), + ); + } + }) + .finally(() => { + setLoading({ + ...loadings, + [loadingsButton]: false, + }); + }); + } + + function onCancel() { + closeCreateConnection(); + // setEditDataSourceData(false) + } + + function testSSH() { + let p = sshForm.getFieldsValue(); + connectionService.testSSH(p).then((res) => { + message.success(i18n('connection.message.testConnectResult', i18n('common.text.successful'))); + }); + } + + return ( +
+ +
+
+ +
{databaseMap[backfillData.type]?.name}
+
+
+ +
+ +
+
+ { + + } +
+
+ + +
+
+
+
+
+ ); +} + +interface IRenderFormProps { + tab: ITabsType; + form: any; + backfillData: IConnectionDetails; +} + +function RenderForm(props: IRenderFormProps) { + const { tab, form, backfillData } = props; + const editId = backfillData.id; + const databaseType = backfillData.type; + + let aliasChanged = false; + + const dataSourceFormConfigMemo = useMemo(() => { + return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { + return t.type === databaseType; + }); + }, [databaseType]); + + const [dataSourceFormConfig, setDataSourceFormConfig] = useState(dataSourceFormConfigMemo); + + useEffect(() => { + setDataSourceFormConfig(dataSourceFormConfigMemo); + }, [databaseType]); + + const initialValuesMemo = useMemo(() => { + return initialFormData(dataSourceFormConfigMemo[tab].items); + }, []); + + const [initialValues] = useState(initialValuesMemo); + + useEffect(() => { + if (!backfillData) { + return; + } + if (tab === 'baseInfo') { + // TODO: + // selectChange({ name: 'authentication', value: backfillData.user ? 1 : 2 }); + regEXFormatting({ url: backfillData.url }, backfillData); + } + + if (tab === 'ssh') { + regEXFormatting({}, backfillData.ssh || {}); + } + }, [backfillData]); + + function initialFormData(dataSourceFormConfig: IFormItem[] | undefined) { + let initValue: any = {}; + dataSourceFormConfig?.map((t) => { + initValue[t.name] = t.defaultValue; + if (t.selects?.length) { + t.selects?.map((item) => { + if (item.value === t.defaultValue) { + initValue = { + ...initValue, + ...initialFormData(item.items), + }; + } + }); + } + }); + return initValue; + } + + function selectChange(t: { name: string; value: any }) { + dataSourceFormConfig[tab].items.map((j, i) => { + if (j.name === t.name) { + j.defaultValue = t.value; + } + }); + setDataSourceFormConfig({ ...dataSourceFormConfig }); + } + + function onFieldsChange(data: any, datas: any) { + // 将antd的格式转换为正常的对象格式 + if (!data.length) { + return; + } + const keyName = data[0].name[0]; + const keyValue = data[0].value; + const variableData = { + [keyName]: keyValue, + }; + const dataObj: any = {}; + datas.map((t: any) => { + dataObj[t.name[0]] = t.value; + }); + // 正则拆分url/组建url + if (tab === 'baseInfo') { + regEXFormatting(variableData, dataObj); + } + } + + function extractObj(url: any) { + const { template, pattern } = dataSourceFormConfig.baseInfo; + // 提取关键词对应的内容 value + const matches = url.match(pattern)!; + // 提取花括号内的关键词 key + const reg = /{(.*?)}/g; + let match; + const arr = []; + while ((match = reg.exec(template)) !== null) { + arr.push(match[1]); + } + // key与value一一对应 + const newExtract: any = {}; + arr.map((t, i) => { + newExtract[t] = t === 'database' ? matches[i + 2] || '' : matches[i + 1]; + }); + return newExtract; + } + + function regEXFormatting(variableData: { [key: string]: any }, dataObj: { [key: string]: any }) { + const { template, pattern } = dataSourceFormConfig.baseInfo; + const keyName = Object.keys(variableData)[0]; + const keyValue = variableData[Object.keys(variableData)[0]]; + let newData: any = {}; + if (keyName === 'url') { + //先判断url是否符合规定的正则 + if (pattern.test(keyValue)) { + newData = extractObj(keyValue); + } + } else if (keyName === 'alias') { + aliasChanged = true; + } else { + // 改变上边url动 + let url = template; + Object.keys(dataObj).map((t) => { + url = url.replace(`{${t}}`, dataObj[t]); + }); + newData = { + url, + }; + } + if (keyName === 'host' && !aliasChanged) { + newData.alias = '@' + keyValue; + } + console.log({ + ...dataObj, + ...newData, + }); + form.setFieldsValue({ + ...dataObj, + ...newData, + }); + } + + function renderFormItem(t: IFormItem): React.ReactNode { + const label = isEn ? t.labelNameEN : t.labelNameCN; + const name = t.name; + const width = t?.styles?.width || '100%'; + const labelWidth = isEn ? t?.styles?.labelWidthEN || '100px' : t?.styles?.labelWidthCN || '70px'; + const labelAlign = t?.styles?.labelAlign || 'left'; + + const FormItemTypes: { [key in InputType]: () => React.ReactNode } = { + [InputType.INPUT]: () => ( + + + + ), + + [InputType.SELECT]: () => ( + + + + ), + + [InputType.PASSWORD]: () => ( + + + + ), + }; + + return ( + +
+ {FormItemTypes[t.inputType]()} +
+ {t.selects?.map((item) => { + if (t.defaultValue === item.value) { + return item.items?.map((t) => renderFormItem(t)); + } + })} +
+ ); + } + + return ( +
+ {dataSourceFormConfig[tab]!.items.map((t) => renderFormItem(t))} +
+ ); +} +interface IRenderExtendTableProps { + backfillData: IConnectionDetails; +} + +let extendTableData: any = []; + +function RenderExtendTable(props: IRenderExtendTableProps) { + const { backfillData } = props; + const databaseType = backfillData.type; + const dataSourceFormConfigMemo = useMemo(() => { + return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { + return t.type === databaseType; + }); + }, [backfillData.type]); + + const extendInfo = + dataSourceFormConfigMemo.extendInfo?.map((t, i) => { + return { + key: i, + label: t.key, + value: t.value, + }; + }) || []; + + const [data, setData] = useState([...extendInfo, { key: extendInfo.length, label: '', value: '' }]); + + useEffect(() => { + const backfillDataExtendInfo = + backfillData?.extendInfo.map((t, i) => { + return { + key: i, + label: t.key, + value: t.value, + }; + }) || []; + setData([...backfillDataExtendInfo, { key: extendInfo.length, label: '', value: '' }]); + }, [backfillData]); + + useEffect(() => { + extendTableData = data; + }, [data]); + + const columns: any = [ + { + title: i18n('connection.tableHeader.name'), + dataIndex: 'label', + width: '60%', + render: (value: any, row: any, index: number) => { + let isCustomLabel = true; + + dataSourceFormConfigMemo.extendInfo?.map((item) => { + if (item.key === row.label) { + isCustomLabel = false; + } + }); + + function change(e: any) { + const newData = [...data]; + newData[index] = { + key: index, + label: e.target.value, + value: '', + }; + setData(newData); + } + + function blur() { + const newData = []; + data.map((t) => { + if (t.label) { + newData.push(t); + } + }); + if (index === data.length - 1 && row.label) { + newData[index] = { + key: index, + label: row.label, + value: '', + }; + } + setData([...newData, { key: newData.length, label: '', value: '' }]); + } + + if (index === data.length - 1 || isCustomLabel) { + return ( + + ); + } else { + return {value}; + } + }, + }, + { + title: i18n('connection.tableHeader.value'), + dataIndex: 'value', + width: '40%', + render: (value: any, row: any, index: number) => { + function change(e: any) { + const newData = [...data]; + newData[index] = { + key: index, + label: row.label, + value: e.target.value, + }; + setData(newData); + } + + function blur() {} + + if (index === data.length - 1) { + return ; + } else { + return ; + } + }, + }, + ]; + + return ( +
+ + + ); +} diff --git a/front-end/src/components/DraggableContainer/index.less b/chat2db-client/src/components/DraggableContainer/index.less similarity index 100% rename from front-end/src/components/DraggableContainer/index.less rename to chat2db-client/src/components/DraggableContainer/index.less diff --git a/front-end/src/components/DraggableContainer/index.tsx b/chat2db-client/src/components/DraggableContainer/index.tsx similarity index 100% rename from front-end/src/components/DraggableContainer/index.tsx rename to chat2db-client/src/components/DraggableContainer/index.tsx diff --git a/front-end/src/components/Iconfont/desktop.less b/chat2db-client/src/components/Iconfont/desktop.less similarity index 100% rename from front-end/src/components/Iconfont/desktop.less rename to chat2db-client/src/components/Iconfont/desktop.less diff --git a/front-end/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx similarity index 100% rename from front-end/src/components/Iconfont/index.tsx rename to chat2db-client/src/components/Iconfont/index.tsx diff --git a/front-end/src/components/Iconfont/prod.less b/chat2db-client/src/components/Iconfont/prod.less similarity index 100% rename from front-end/src/components/Iconfont/prod.less rename to chat2db-client/src/components/Iconfont/prod.less diff --git a/front-end/src/components/LayoutBasic/index.less b/chat2db-client/src/components/LayoutBasic/index.less similarity index 100% rename from front-end/src/components/LayoutBasic/index.less rename to chat2db-client/src/components/LayoutBasic/index.less diff --git a/front-end/src/components/LayoutBasic/index.tsx b/chat2db-client/src/components/LayoutBasic/index.tsx similarity index 100% rename from front-end/src/components/LayoutBasic/index.tsx rename to chat2db-client/src/components/LayoutBasic/index.tsx diff --git a/front-end/src/components/Loading/LazyLoading/index.less b/chat2db-client/src/components/Loading/LazyLoading/index.less similarity index 100% rename from front-end/src/components/Loading/LazyLoading/index.less rename to chat2db-client/src/components/Loading/LazyLoading/index.less diff --git a/front-end/src/components/Loading/LazyLoading/index.tsx b/chat2db-client/src/components/Loading/LazyLoading/index.tsx similarity index 100% rename from front-end/src/components/Loading/LazyLoading/index.tsx rename to chat2db-client/src/components/Loading/LazyLoading/index.tsx diff --git a/front-end/src/components/Loading/Loading/index.less b/chat2db-client/src/components/Loading/Loading/index.less similarity index 100% rename from front-end/src/components/Loading/Loading/index.less rename to chat2db-client/src/components/Loading/Loading/index.less diff --git a/front-end/src/components/Loading/Loading/index.tsx b/chat2db-client/src/components/Loading/Loading/index.tsx similarity index 100% rename from front-end/src/components/Loading/Loading/index.tsx rename to chat2db-client/src/components/Loading/Loading/index.tsx diff --git a/front-end/src/components/Loading/LoadingContent/index.less b/chat2db-client/src/components/Loading/LoadingContent/index.less similarity index 100% rename from front-end/src/components/Loading/LoadingContent/index.less rename to chat2db-client/src/components/Loading/LoadingContent/index.less diff --git a/front-end/src/components/Loading/LoadingContent/index.tsx b/chat2db-client/src/components/Loading/LoadingContent/index.tsx similarity index 100% rename from front-end/src/components/Loading/LoadingContent/index.tsx rename to chat2db-client/src/components/Loading/LoadingContent/index.tsx diff --git a/front-end/src/components/Loading/LoadingLiquid/index.less b/chat2db-client/src/components/Loading/LoadingLiquid/index.less similarity index 100% rename from front-end/src/components/Loading/LoadingLiquid/index.less rename to chat2db-client/src/components/Loading/LoadingLiquid/index.less diff --git a/front-end/src/components/Loading/LoadingLiquid/index.tsx b/chat2db-client/src/components/Loading/LoadingLiquid/index.tsx similarity index 100% rename from front-end/src/components/Loading/LoadingLiquid/index.tsx rename to chat2db-client/src/components/Loading/LoadingLiquid/index.tsx diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less new file mode 100644 index 000000000..5641c7a2f --- /dev/null +++ b/chat2db-client/src/components/SearchResult/index.less @@ -0,0 +1,120 @@ +@import '../../styles/var.less'; + +.box { + height: 100%; + display: flex; + flex-direction: column; +} + +.recordIcon { + font-size: 16px; + margin-right: 4px; +} + +.resultHeader { + flex-shrink: 0; + overflow-x: scroll; + padding: 0px 10px; + background-color: var(--color-bg-300); + border-bottom: 1px solid var(--color-border); + + &::-webkit-scrollbar { + display: none; + } + + & ::after { + height: 0px !important; + } + + .statusIcon { + margin-right: 4px; + font-size: 12px; + } + + .successIcon { + color: rgb(71, 157, 255); + } + + .failIcon { + color: red; + } +} + +.resultContent { + flex: 1; + width: 100%; + position: relative; + overflow: auto; +} + +.tableStatus { + display: flex; + align-items: center; + + .dot { + display: inline-block; + margin-right: 5px; + width: 10px; + height: 10px; + background-color: #ff4d4f; + border-radius: 50%; + } + + .successDot { + background-color: #52c41a; + } +} + +.tableBox { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 0; + opacity: 0; + overflow: auto; + background-color: var(--color-bg-100); +} + +.cursorTableBox { + z-index: 1; + opacity: 1; +} + +.tableIndex { + width: 50px; +} + +.tableHoverBox { + display: none; + align-items: center; + position: absolute; + background-color: var(--color-bg-200); + top: 0; + right: 0; + bottom: 0; + + i { + font-size: 15px; + margin: 0px 2px; + } + + i:hover { + color: var(--custom-primary-color); + } +} + +.monacoEditor { + height: 300px; +} + +.tableItem { + width: 100%; + .f-lines(1); + cursor: pointer; + + &:hover .tableHoverBox { + display: flex; + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx new file mode 100644 index 000000000..49affc9fa --- /dev/null +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -0,0 +1,199 @@ +import React, { memo, useEffect, useState, useRef } from 'react'; +import classnames from 'classnames'; +import Tabs from '@/components/Tabs'; +import Iconfont from '@/components/Iconfont'; +import StateIndicator from '@/components/StateIndicator'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import MonacoEditor from '@/components/Console/MonacoEditor'; +import { Button, DatePicker, Input, Table, Modal, message } from 'antd'; +import { StatusType, TableDataType } from '@/constants/table'; +import { formatDate } from '@/utils/date'; +import { IManageResultData, ITableHeaderItem } from '@/typings/database'; +import styles from './index.less'; + +interface IProps { + className?: string; + manageResultDataList: IManageResultData[]; +} + +interface DataType { + [key: string]: any; +} + +export default memo(function SearchResult({ className, manageResultDataList = [] }) { + const [isUnfold, setIsUnfold] = useState(true); + const [currentTab, setCurrentTab] = useState('0'); + + useEffect(() => { + setCurrentTab('0'); + }, [manageResultDataList]); + + const renderStatus = (text: string) => { + return ( +
+ + {text == StatusType.SUCCESS ? '成功' : '失败'} +
+ ); + }; + + function onChange(index: string) { + setCurrentTab(index); + } + + const makerResultHeaderList = () => { + const list: any = []; + manageResultDataList?.map((item, index) => { + list.push({ + label: ( +
+ + 执行结果{index + 1} +
+ ), + key: index, + }); + }); + return list; + }; + + return ( +
+
+ +
+
+ + {manageResultDataList.map((item, index) => { + if (item.success) { + return ( + + ); + } else { + return ; + } + })} + +
+
+ ); +}); + +interface ITableProps { + headerList: ITableHeaderItem[]; + dataList: string[][]; + className?: string; + data: IManageResultData; +} + +interface IViewTableCellData { + name: string; + value: any; +} + +export function TableBox(props: ITableProps) { + const { headerList, dataList, className, data, ...rest } = props; + const [columns, setColumns] = useState(); + const [tableData, setTableData] = useState(); + const [viewTableCellData, setViewTableCellData] = useState(null); + + function viewTableCell(data: IViewTableCellData) { + setViewTableCellData(data); + } + + function copyTableCell(data: IViewTableCellData) { + navigator.clipboard.writeText(data?.value || viewTableCellData?.value); + message.success('复制成功'); + } + + function handleCancel() { + setViewTableCellData(null); + } + + useEffect(() => { + if (!headerList?.length) { + return; + } + const columns: any = headerList?.map((item: any, index) => { + const data = { + title: item.name, + dataIndex: item.name, + key: item.name, + type: item.dataType, + sorter: (a: any, b: any) => a[item.name] - b[item.name], + render: (value: any) => ( +
+
+ + +
+ {value} +
+ ), + }; + return data; + }); + setColumns(columns); + }, [headerList]); + + useEffect(() => { + if (!columns?.length) return; + const tableData = dataList?.map((item, rowIndex) => { + const rowData: any = {}; + item.map((i: string | null, index: number) => { + const { dataType: type } = headerList[index] || {}; + // console.log('headerList[rowIndex]', headerList[rowIndex]); + if (type === TableDataType.DATETIME && i) { + rowData[columns[index].title] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); + } else if (i === null) { + rowData[columns[index].title] = '[null]'; + } else { + rowData[columns[index].title] = i; + } + }); + rowData.key = rowIndex; + return rowData; + }); + + setTableData(tableData); + }, [columns]); + + return ( +
+ {dataList !== null ? ( +
+ ) : ( + + )} + + { + + } + + } + > +
+ +
+
+ + ); +} diff --git a/front-end/src/components/StateIndicator/index.less b/chat2db-client/src/components/StateIndicator/index.less similarity index 100% rename from front-end/src/components/StateIndicator/index.less rename to chat2db-client/src/components/StateIndicator/index.less diff --git a/front-end/src/components/StateIndicator/index.tsx b/chat2db-client/src/components/StateIndicator/index.tsx similarity index 100% rename from front-end/src/components/StateIndicator/index.tsx rename to chat2db-client/src/components/StateIndicator/index.tsx diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less new file mode 100644 index 000000000..0f94b45dd --- /dev/null +++ b/chat2db-client/src/components/Tabs/index.less @@ -0,0 +1,50 @@ +// @import '../../var.less'; + +.box{ + display: flex; + position: relative; + .extra{ + flex: 1; + } + &::after{ + position: absolute; + content: ''; + bottom: 1px; + left: 0; + width: 100%; + height: 1px; + background-color: var(--color-border); + } + :global { + .custom-tabs{ + width: 100%; + flex-shrink: 0; + } + .custom-tabs-tab { + margin: 0px 10px; + font-size: 12px; + } + .custom-tabs-nav{ + margin: 0 !important; + } + .custom-tabs-nav::before{ + border: 0; + border-bottom: 0 !important; + } + .custom-tabs-tab{ + user-select: none; + padding: 5px 10px; + margin: 0px 0px 5px 0px; + border-radius: 5px; + &:hover{ + color: var(--color-text-85); + background-color: var(--color-bg-hover); + } + } + .custom-tabs-tab-active{ + &:hover{ + background-color: transparent; + } + } + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx new file mode 100644 index 000000000..9f6cbd180 --- /dev/null +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -0,0 +1,37 @@ +import React, { memo, ReactNode, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { Tabs as AntdTabs } from 'antd'; + +export interface ITab { + label: ReactNode; + key: string; +} + +interface IProps { + className?: string; + tabs: ITab[]; + currentTab?: string; + onChange: (key: string, index: number) => void; + extra?: React.ReactNode +} + +export default memo(function Tabs({ className, tabs, currentTab, onChange, extra }: IProps) { + function myChange(key: string) { + const index = tabs.findIndex(t => { + return t.key === key + }) + onChange(key, index) + } + + return
+ +
+ {extra} +
+
+}) diff --git a/front-end/src/components/XXXX_FN/index.less b/chat2db-client/src/components/XXXX_FN/index.less similarity index 100% rename from front-end/src/components/XXXX_FN/index.less rename to chat2db-client/src/components/XXXX_FN/index.less diff --git a/front-end/src/components/XXXX_FN/index.tsx b/chat2db-client/src/components/XXXX_FN/index.tsx similarity index 100% rename from front-end/src/components/XXXX_FN/index.tsx rename to chat2db-client/src/components/XXXX_FN/index.tsx diff --git a/front-end/src/constants/appConfig.ts b/chat2db-client/src/constants/appConfig.ts similarity index 100% rename from front-end/src/constants/appConfig.ts rename to chat2db-client/src/constants/appConfig.ts diff --git a/front-end/src/constants/common.ts b/chat2db-client/src/constants/common.ts similarity index 100% rename from front-end/src/constants/common.ts rename to chat2db-client/src/constants/common.ts diff --git a/front-end/src/constants/database.ts b/chat2db-client/src/constants/database.ts similarity index 100% rename from front-end/src/constants/database.ts rename to chat2db-client/src/constants/database.ts diff --git a/front-end/src/constants/environment.ts b/chat2db-client/src/constants/environment.ts similarity index 100% rename from front-end/src/constants/environment.ts rename to chat2db-client/src/constants/environment.ts diff --git a/front-end/src/constants/index.ts b/chat2db-client/src/constants/index.ts similarity index 100% rename from front-end/src/constants/index.ts rename to chat2db-client/src/constants/index.ts diff --git a/front-end/src/constants/monacoEditor.ts b/chat2db-client/src/constants/monacoEditor.ts similarity index 100% rename from front-end/src/constants/monacoEditor.ts rename to chat2db-client/src/constants/monacoEditor.ts diff --git a/chat2db-client/src/constants/table.ts b/chat2db-client/src/constants/table.ts new file mode 100644 index 000000000..ab8c4f5a0 --- /dev/null +++ b/chat2db-client/src/constants/table.ts @@ -0,0 +1,22 @@ +export enum TableDataType { + BOOLEAN = 'BOOLEAN', + NUMERIC = 'NUMERIC', + STRING = 'STRING', + DATETIME = 'DATETIME', + // 暂时不适配 + BINARY = 'BINARY', + CONTENT = 'CONTENT', + STRUCT = 'STRUCT', + DOCUMENT = 'DOCUMENT', + ARRAY = 'ARRAY', + OBJECT = 'OBJECT', + REFERENCE = 'REFERENCE', + ROWID = 'ROWID', + ANY = 'ANY', + UNKNOWN = 'UNKNOWN', +} + +export enum StatusType { + SUCCESS = 'success', + FAIL = 'fail', +} \ No newline at end of file diff --git a/front-end/src/constants/theme.ts b/chat2db-client/src/constants/theme.ts similarity index 100% rename from front-end/src/constants/theme.ts rename to chat2db-client/src/constants/theme.ts diff --git a/front-end/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts similarity index 100% rename from front-end/src/constants/tree.ts rename to chat2db-client/src/constants/tree.ts diff --git a/front-end/src/hooks/index.ts b/chat2db-client/src/hooks/index.ts similarity index 100% rename from front-end/src/hooks/index.ts rename to chat2db-client/src/hooks/index.ts diff --git a/front-end/src/hooks/useEventSource.ts b/chat2db-client/src/hooks/useEventSource.ts similarity index 100% rename from front-end/src/hooks/useEventSource.ts rename to chat2db-client/src/hooks/useEventSource.ts diff --git a/front-end/src/hooks/useTheme.ts b/chat2db-client/src/hooks/useTheme.ts similarity index 100% rename from front-end/src/hooks/useTheme.ts rename to chat2db-client/src/hooks/useTheme.ts diff --git a/front-end/src/hooks/useUpdateEffect.ts b/chat2db-client/src/hooks/useUpdateEffect.ts similarity index 100% rename from front-end/src/hooks/useUpdateEffect.ts rename to chat2db-client/src/hooks/useUpdateEffect.ts diff --git a/front-end/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts similarity index 79% rename from front-end/src/i18n/en-us/common.ts rename to chat2db-client/src/i18n/en-us/common.ts index 3bf84c9c4..f76f9891d 100644 --- a/front-end/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -21,9 +21,10 @@ export default { 'common.button.save': 'Save', 'common.button.execute': 'Run', 'common.message.successfulConfig': 'Successful configuration', - 'common.text.successful':'successful', + 'common.text.successful': 'successful', 'common.text.failure': 'failure', - 'common.message.modifySuccessfully':'modify successfully', - 'common.message.addedSuccessfully':'successfully added', - 'common.text.custom':'custom', -} \ No newline at end of file + 'common.message.modifySuccessfully': 'modify successfully', + 'common.message.addedSuccessfully': 'successfully added', + 'common.text.custom': 'custom', + 'common.button.delete': 'Delete', +}; diff --git a/front-end/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts similarity index 94% rename from front-end/src/i18n/en-us/connection.ts rename to chat2db-client/src/i18n/en-us/connection.ts index c495d35d9..3027aa320 100644 --- a/front-end/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -11,7 +11,7 @@ export default { 'connection.button.testConnection': 'Test', 'connection.label.advancedConfiguration': 'Advanced Configuration', 'connection.label.sshConfiguration': 'SSH Configuration', - 'connection.button.addConnection': 'ADD Connection', + 'connection.button.addConnection': 'Add Connection', 'connection.button.connect': 'Connect', 'connection.message.testConnectResult': 'Test connection is {1}', 'connection.message.testSshConnection': 'Test the ssh connection', diff --git a/front-end/src/i18n/en-us/dashboard.ts b/chat2db-client/src/i18n/en-us/dashboard.ts similarity index 100% rename from front-end/src/i18n/en-us/dashboard.ts rename to chat2db-client/src/i18n/en-us/dashboard.ts diff --git a/front-end/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts similarity index 100% rename from front-end/src/i18n/en-us/index.ts rename to chat2db-client/src/i18n/en-us/index.ts diff --git a/front-end/src/i18n/en-us/menu.ts b/chat2db-client/src/i18n/en-us/menu.ts similarity index 100% rename from front-end/src/i18n/en-us/menu.ts rename to chat2db-client/src/i18n/en-us/menu.ts diff --git a/front-end/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts similarity index 100% rename from front-end/src/i18n/en-us/setting.ts rename to chat2db-client/src/i18n/en-us/setting.ts diff --git a/front-end/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts similarity index 100% rename from front-end/src/i18n/en-us/workspace.ts rename to chat2db-client/src/i18n/en-us/workspace.ts diff --git a/front-end/src/i18n/index.tsx b/chat2db-client/src/i18n/index.tsx similarity index 100% rename from front-end/src/i18n/index.tsx rename to chat2db-client/src/i18n/index.tsx diff --git a/front-end/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts similarity index 96% rename from front-end/src/i18n/zh-cn/common.ts rename to chat2db-client/src/i18n/zh-cn/common.ts index bf34ce7d6..de2d86aa5 100644 --- a/front-end/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -26,4 +26,5 @@ export default { 'common.message.modifySuccessfully':'修改成功', 'common.message.addedSuccessfully': '添加成功', 'common.text.custom':'自定义', + 'common.button.delete':'删除' } \ No newline at end of file diff --git a/front-end/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts similarity index 100% rename from front-end/src/i18n/zh-cn/connection.ts rename to chat2db-client/src/i18n/zh-cn/connection.ts diff --git a/front-end/src/i18n/zh-cn/dashboard.ts b/chat2db-client/src/i18n/zh-cn/dashboard.ts similarity index 100% rename from front-end/src/i18n/zh-cn/dashboard.ts rename to chat2db-client/src/i18n/zh-cn/dashboard.ts diff --git a/front-end/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts similarity index 100% rename from front-end/src/i18n/zh-cn/index.ts rename to chat2db-client/src/i18n/zh-cn/index.ts diff --git a/front-end/src/i18n/zh-cn/menu.ts b/chat2db-client/src/i18n/zh-cn/menu.ts similarity index 100% rename from front-end/src/i18n/zh-cn/menu.ts rename to chat2db-client/src/i18n/zh-cn/menu.ts diff --git a/front-end/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts similarity index 100% rename from front-end/src/i18n/zh-cn/setting.ts rename to chat2db-client/src/i18n/zh-cn/setting.ts diff --git a/front-end/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts similarity index 100% rename from front-end/src/i18n/zh-cn/workspace.ts rename to chat2db-client/src/i18n/zh-cn/workspace.ts diff --git a/front-end/src/layouts/index.less b/chat2db-client/src/layouts/index.less similarity index 100% rename from front-end/src/layouts/index.less rename to chat2db-client/src/layouts/index.less diff --git a/front-end/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx similarity index 100% rename from front-end/src/layouts/index.tsx rename to chat2db-client/src/layouts/index.tsx diff --git a/front-end/src/main/constants.js b/chat2db-client/src/main/constants.js similarity index 100% rename from front-end/src/main/constants.js rename to chat2db-client/src/main/constants.js diff --git a/front-end/src/main/i18n/en/index.js b/chat2db-client/src/main/i18n/en/index.js similarity index 100% rename from front-end/src/main/i18n/en/index.js rename to chat2db-client/src/main/i18n/en/index.js diff --git a/front-end/src/main/i18n/index.js b/chat2db-client/src/main/i18n/index.js similarity index 100% rename from front-end/src/main/i18n/index.js rename to chat2db-client/src/main/i18n/index.js diff --git a/front-end/src/main/i18n/zh-cn/index.js b/chat2db-client/src/main/i18n/zh-cn/index.js similarity index 100% rename from front-end/src/main/i18n/zh-cn/index.js rename to chat2db-client/src/main/i18n/zh-cn/index.js diff --git a/front-end/src/main/index.js b/chat2db-client/src/main/index.js similarity index 100% rename from front-end/src/main/index.js rename to chat2db-client/src/main/index.js diff --git a/front-end/src/main/menu.js b/chat2db-client/src/main/menu.js similarity index 100% rename from front-end/src/main/menu.js rename to chat2db-client/src/main/menu.js diff --git a/front-end/src/main/preload.js b/chat2db-client/src/main/preload.js similarity index 100% rename from front-end/src/main/preload.js rename to chat2db-client/src/main/preload.js diff --git a/front-end/src/main/utils.js b/chat2db-client/src/main/utils.js similarity index 100% rename from front-end/src/main/utils.js rename to chat2db-client/src/main/utils.js diff --git a/front-end/src/models/global.ts b/chat2db-client/src/models/global.ts similarity index 100% rename from front-end/src/models/global.ts rename to chat2db-client/src/models/global.ts diff --git a/front-end/src/pages/demo/index.less b/chat2db-client/src/pages/demo/index.less similarity index 100% rename from front-end/src/pages/demo/index.less rename to chat2db-client/src/pages/demo/index.less diff --git a/front-end/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx similarity index 100% rename from front-end/src/pages/demo/index.tsx rename to chat2db-client/src/pages/demo/index.tsx diff --git a/front-end/src/pages/document.ejs b/chat2db-client/src/pages/document.ejs similarity index 100% rename from front-end/src/pages/document.ejs rename to chat2db-client/src/pages/document.ejs diff --git a/front-end/src/pages/main/chat/index.less b/chat2db-client/src/pages/main/chat/index.less similarity index 100% rename from front-end/src/pages/main/chat/index.less rename to chat2db-client/src/pages/main/chat/index.less diff --git a/front-end/src/pages/main/chat/index.tsx b/chat2db-client/src/pages/main/chat/index.tsx similarity index 100% rename from front-end/src/pages/main/chat/index.tsx rename to chat2db-client/src/pages/main/chat/index.tsx diff --git a/front-end/src/pages/main/connections/index.less b/chat2db-client/src/pages/main/connections/index.less similarity index 88% rename from front-end/src/pages/main/connections/index.less rename to chat2db-client/src/pages/main/connections/index.less index 76ca9e622..c52d556c7 100644 --- a/front-end/src/pages/main/connections/index.less +++ b/chat2db-client/src/pages/main/connections/index.less @@ -39,10 +39,31 @@ color: var(--color-primary) !important; } + .menuItems { + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + padding: 12px; + height: 20px; + + &:hover { + background-color: var(--color-hover-bg); + border: var(--border-radius); + } + } + + .menuItemsTitle {} + + .menuItemActive { + color: var(--color-primary); + } + :global { .ant-menu-inline { border-inline-end: none !important; } + .ant-menu-item { padding-left: 14px !important; } @@ -94,6 +115,7 @@ .databaseItemRight { display: none; + i { font-size: 16px; } @@ -103,12 +125,15 @@ background-color: var(--color-bg-medium); cursor: pointer; color: var(--color-primary); + .databaseItemMain { transform: scale(1.02); transition: transform 0.5s; } + .databaseItemRight { display: block; + i { color: var(--color-primary); } @@ -124,6 +149,7 @@ background-image: linear-gradient(138deg, #858dff 0%, #597ef7 100%); border-radius: 8px; margin-right: 16px; + i { font-size: 16px; color: #fff; @@ -152,4 +178,4 @@ z-index: 1; transform: scale(1); transition: transform 0.3s ease-in-out; -} +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/connections/index.tsx b/chat2db-client/src/pages/main/connections/index.tsx new file mode 100644 index 000000000..6a00550b1 --- /dev/null +++ b/chat2db-client/src/pages/main/connections/index.tsx @@ -0,0 +1,173 @@ +import React, { Fragment, memo, useEffect, useMemo, useRef, useState } from 'react'; +import cs from 'classnames'; +import i18n from '@/i18n'; +import CreateConnection from '@/components/CreateConnection'; +import Iconfont from '@/components/Iconfont'; +import connectionService from '@/service/connection'; + +import { DatabaseTypeCode, databaseMap, databaseTypeList } from '@/constants/database'; + +import { IDatabase } from '@/typings/database'; +import { IConnectionDetails } from '@/typings/connection'; +import { Button, Dropdown, Modal } from 'antd'; +import { MoreOutlined } from '@ant-design/icons'; +import styles from './index.less'; + +interface IMenu { + key: number; + label: string; + icon: React.ReactNode; + meta: IConnectionDetails; +} +interface IProps {} + +export default memo(function Connections(props) { + const volatileRef = useRef(); + const [connectionList, setConnectionList] = useState(); + const [curConnection, setCurConnection] = useState>({}); + + useEffect(() => { + getConnectionList(); + }, []); + + function getConnectionList() { + let p = { + pageNo: 1, + pageSize: 999, + }; + connectionService.getList(p).then((res) => { + setConnectionList(res.data); + }); + } + + function handleCreateConnections(database: IDatabase) { + setCurConnection({ + type: database.code, + }); + } + + const menuItems: IMenu[] = useMemo( + () => + (connectionList || []).map((t) => ({ + key: t.id, + icon: , + label: t.alias, + meta: t, + })), + [connectionList], + ); + + const renderMenu = () => { + return ( +
+ {(menuItems || []).map((menu) => { + const { key, label, icon } = menu; + return ( +
{ + setCurConnection(menu.meta); + }} + > +
+ {icon} + {label} +
+ { + domEvent.preventDefault(); + await connectionService.remove({ id: key }); + // if (key === curConnection.id) { + // } + setCurConnection({}); + getConnectionList(); + }, + }, + ], + }} + > + + +
+ ); + })} +
+ ); + }; + + return ( +
+
+
{i18n('connection.title.connections')}
+ {renderMenu()} + {/*
+ +
*/} + +
+
+ {curConnection && Object.keys(curConnection).length ? ( +
+ { + setCurConnection({}); + }} + submitCallback={getConnectionList} + /> +
+ ) : ( +
+ {databaseTypeList.map((t) => { + return ( +
+
+
+
+ +
+ {t.name} +
+
+ +
+
+
+ ); + })} +
+
+
+ )} +
+
+ ); +}); diff --git a/front-end/src/pages/main/dashboard/chart-item/index.less b/chat2db-client/src/pages/main/dashboard/chart-item/index.less similarity index 97% rename from front-end/src/pages/main/dashboard/chart-item/index.less rename to chat2db-client/src/pages/main/dashboard/chart-item/index.less index c4129a643..d1c294115 100644 --- a/front-end/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -114,10 +114,11 @@ .editorBlock { display: flex; border: 1px solid #eee; + flex-wrap: wrap ; } .editor { flex: 1; border-right: 1px solid #eee; - + min-width: 320px } \ No newline at end of file diff --git a/front-end/src/pages/main/dashboard/chart-item/index.tsx b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx similarity index 97% rename from front-end/src/pages/main/dashboard/chart-item/index.tsx rename to chat2db-client/src/pages/main/dashboard/chart-item/index.tsx index 1ee6ccb9a..cc03f138a 100644 --- a/front-end/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -49,7 +49,7 @@ interface IChartItemProps { id: number; canAddRowItem: boolean; - onDelete?: () => void; + onDelete?: (id: number) => void; addChartTop?: () => void; addChartBottom?: () => void; addChartLeft?: () => void; @@ -153,7 +153,7 @@ function ChartItem(props: IChartItemProps) { const onDeleteChart = () => { const { id } = props; deleteChart({ id }); - props.onDelete && props.onDelete(); + props.onDelete && props.onDelete(id); }; const renderEmptyBlock = () => { @@ -181,10 +181,10 @@ function ChartItem(props: IChartItemProps) {
编辑区域
diff --git a/front-end/src/pages/main/dashboard/chart/bar/index.less b/chat2db-client/src/pages/main/dashboard/chart/bar/index.less similarity index 100% rename from front-end/src/pages/main/dashboard/chart/bar/index.less rename to chat2db-client/src/pages/main/dashboard/chart/bar/index.less diff --git a/front-end/src/pages/main/dashboard/chart/bar/index.tsx b/chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx similarity index 91% rename from front-end/src/pages/main/dashboard/chart/bar/index.tsx rename to chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx index 49b0c6a8d..b56d7598c 100644 --- a/front-end/src/pages/main/dashboard/chart/bar/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart/bar/index.tsx @@ -36,7 +36,7 @@ const BarChart = (props: IProps, ref) => { useImperativeHandle(ref, () => ({ getEchartsInstance: () => barRef.current.getEchartsInstance(), })); - return ; + return ; }; export default forwardRef(BarChart); diff --git a/front-end/src/pages/main/dashboard/chart/line/index.less b/chat2db-client/src/pages/main/dashboard/chart/line/index.less similarity index 100% rename from front-end/src/pages/main/dashboard/chart/line/index.less rename to chat2db-client/src/pages/main/dashboard/chart/line/index.less diff --git a/front-end/src/pages/main/dashboard/chart/line/index.tsx b/chat2db-client/src/pages/main/dashboard/chart/line/index.tsx similarity index 100% rename from front-end/src/pages/main/dashboard/chart/line/index.tsx rename to chat2db-client/src/pages/main/dashboard/chart/line/index.tsx diff --git a/front-end/src/pages/main/dashboard/chart/pie/index.less b/chat2db-client/src/pages/main/dashboard/chart/pie/index.less similarity index 100% rename from front-end/src/pages/main/dashboard/chart/pie/index.less rename to chat2db-client/src/pages/main/dashboard/chart/pie/index.less diff --git a/front-end/src/pages/main/dashboard/chart/pie/index.tsx b/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx similarity index 95% rename from front-end/src/pages/main/dashboard/chart/pie/index.tsx rename to chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx index 320999757..2e10c43c8 100644 --- a/front-end/src/pages/main/dashboard/chart/pie/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx @@ -19,9 +19,9 @@ const PieChart = (props: IProps, ref: ForwardedRef<{ getEchartsInstance: Functio trigger: 'item', }, legend: { - orient: 'vertical', - right: 'right', - top: 'center', + orient: 'horizontal', + // top: 'center', + align: 'auto' }, // color:['#45C2E0', '#C1EBDD', '#FFC851','#5A5476','#1869A0','#FF9393'], series: [ diff --git a/front-end/src/pages/main/dashboard/echart-test/index.less b/chat2db-client/src/pages/main/dashboard/echart-test/index.less similarity index 100% rename from front-end/src/pages/main/dashboard/echart-test/index.less rename to chat2db-client/src/pages/main/dashboard/echart-test/index.less diff --git a/front-end/src/pages/main/dashboard/echart-test/index.tsx b/chat2db-client/src/pages/main/dashboard/echart-test/index.tsx similarity index 100% rename from front-end/src/pages/main/dashboard/echart-test/index.tsx rename to chat2db-client/src/pages/main/dashboard/echart-test/index.tsx diff --git a/front-end/src/pages/main/dashboard/index.less b/chat2db-client/src/pages/main/dashboard/index.less similarity index 100% rename from front-end/src/pages/main/dashboard/index.less rename to chat2db-client/src/pages/main/dashboard/index.less diff --git a/front-end/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx similarity index 95% rename from front-end/src/pages/main/dashboard/index.tsx rename to chat2db-client/src/pages/main/dashboard/index.tsx index 20c970287..f4061479c 100644 --- a/front-end/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -163,18 +163,24 @@ function Chart(props: IProps) { setCurDashboard(newDashboard); }; - const onDelete = async (rowIndex: number, colIndex: number) => { - const { schema } = curDashboard || {}; + const onDelete = async (chartId: number, rowIndex: number, colIndex: number) => { + const { id, schema, chartIds } = curDashboard || {}; + const chartList: number[][] = JSON.parse(schema || '') || [[]]; - if (chartList.length === 1) { + if (chartList[rowIndex].length === 1) { chartList.splice(rowIndex, 1); } else { chartList[rowIndex].splice(colIndex, 1); } - await updateDashboard({ + + const newDashboard = { + id: id!, ...curDashboard, schema: JSON.stringify(chartList), - }); + chartIds: chartIds?.filter((id) => id !== chartId), + }; + await updateDashboard(newDashboard); + setCurDashboard(newDashboard); }; const renderContent = () => { @@ -210,7 +216,7 @@ function Chart(props: IProps) { addChartBottom={() => onAddChart('bottom', rowIndex, colIndex)} addChartLeft={() => onAddChart('left', rowIndex, colIndex)} addChartRight={() => onAddChart('right', rowIndex, colIndex)} - onDelete={() => onDelete(rowIndex, colIndex)} + onDelete={(id: number) => onDelete(id, rowIndex, colIndex)} /> ))} diff --git a/front-end/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less similarity index 100% rename from front-end/src/pages/main/index.less rename to chat2db-client/src/pages/main/index.less diff --git a/front-end/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx similarity index 100% rename from front-end/src/pages/main/index.tsx rename to chat2db-client/src/pages/main/index.tsx diff --git a/front-end/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less similarity index 100% rename from front-end/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less rename to chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less diff --git a/front-end/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx similarity index 100% rename from front-end/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx rename to chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx diff --git a/front-end/src/pages/main/workspace/components/Tree/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/index.less similarity index 100% rename from front-end/src/pages/main/workspace/components/Tree/index.less rename to chat2db-client/src/pages/main/workspace/components/Tree/index.less diff --git a/front-end/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx similarity index 100% rename from front-end/src/pages/main/workspace/components/Tree/index.tsx rename to chat2db-client/src/pages/main/workspace/components/Tree/index.tsx diff --git a/front-end/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx similarity index 100% rename from front-end/src/pages/main/workspace/components/Tree/treeConfig.tsx rename to chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx diff --git a/front-end/src/pages/main/workspace/components/WorkspaceLeft/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less similarity index 100% rename from front-end/src/pages/main/workspace/components/WorkspaceLeft/index.less rename to chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less diff --git a/front-end/src/pages/main/workspace/components/WorkspaceLeft/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx similarity index 100% rename from front-end/src/pages/main/workspace/components/WorkspaceLeft/index.tsx rename to chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx diff --git a/front-end/src/pages/main/workspace/components/WorkspaceRight/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less similarity index 79% rename from front-end/src/pages/main/workspace/components/WorkspaceRight/index.less rename to chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less index 1fd63ab3b..f14f20776 100644 --- a/front-end/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -8,7 +8,7 @@ bottom: 0; } -.console_box{ +.consoleBox{ position: absolute; top: 40px; left: 0; @@ -17,24 +17,24 @@ display: none; } -.tab_box{ +.tabBox{ padding: 10px 10px 0px; } -.active_console_box{ +.activeConsoleBox{ display: block; } -.box_right_center { +.boxRightCenter { height: 100%; } -.box_right_console { +.boxRightConsole { height: 50vh; overflow: hidden; } -.box_right_result { +.boxRightResult { border-top: 1px solid var(--color-border); padding: 10px; height: 0px; diff --git a/front-end/src/pages/main/workspace/components/WorkspaceRight/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx similarity index 74% rename from front-end/src/pages/main/workspace/components/WorkspaceRight/index.tsx rename to chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx index 0426c4750..ab8cba1ec 100644 --- a/front-end/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -10,6 +10,9 @@ import historyService from '@/service/history'; import { Button, Tabs } from 'antd'; import { useReducerContext } from '@/pages/main/workspace'; import { workspaceActionType } from '@/pages/main/workspace/context'; +import SearchResult from '@/components/SearchResult'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import { IManageResultData } from '@/typings/database'; interface IProps { className?: string; @@ -23,14 +26,15 @@ export default memo(function WorkspaceRight(props) { const { state, dispatch } = useReducerContext(); const { dblclickTreeNodeData, currentWorkspaceData } = state; const [consoleValue, setConsoleValue] = useState(); + const [resultData, setResultData] = useState([]); useEffect(() => { getConsoleList(); - }, []) + }, []); useEffect(() => { if (dblclickTreeNodeData) { - const { extraParams } = dblclickTreeNodeData + const { extraParams } = dblclickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; let flag = false; @@ -43,7 +47,7 @@ export default memo(function WorkspaceRight(props) { }); if (!flag) { - const name = [databaseName, schemaName, 'console'].filter(t => t).join('-'); + const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); let p = { name: name, type: databaseType, @@ -69,12 +73,11 @@ export default memo(function WorkspaceRight(props) { }; setActiveConsoleId(newConsole.id); setConsoleList([...(consoleList || []), newConsole]); - console.log([...(consoleList || []), newConsole]) + console.log([...(consoleList || []), newConsole]); }); } - } - }, [dblclickTreeNodeData]) + }, [dblclickTreeNodeData]); function getConsoleList() { let p = { @@ -138,7 +141,7 @@ export default memo(function WorkspaceRight(props) { } function onChange(key: string) { - setActiveConsoleId(+key) + setActiveConsoleId(+key); } const onEdit = (targetKey: any, action: 'add' | 'remove') => { @@ -172,9 +175,9 @@ export default memo(function WorkspaceRight(props) { tabOpened: 'n', }; - const window = consoleList?.find(t => t.id === +targetKey); + const window = consoleList?.find((t) => t.id === +targetKey); if (!window?.status) { - return + return; } if (window!.status === 'DRAFT') { historyService.deleteWindowTab({ id: window!.id }); @@ -183,48 +186,54 @@ export default memo(function WorkspaceRight(props) { } }; - return
-
- { - return { - label: t.name, - key: t.id + '', - }; - })} - /> -
- { - consoleList?.map((t, index) => { - return
- -
- +
+ { + return { + label: t.name, + key: t.id + '', + }; + })} + /> +
+ {consoleList?.map((t, index) => { + return ( +
+ +
+ -
-
-

{t.databaseName}

-
-
-
- }) - } -
-}) + }} + hasAiChat={true} + hasAi2Lang={true} + value={consoleValue} + onExecuteSQL={(result) => { + console.log('onExecuteSQL', result); + setResultData(result); + }} + /> +
+
+ + + +
+ +
+ ); + })} + + ); +}); diff --git a/front-end/src/pages/main/workspace/context.ts b/chat2db-client/src/pages/main/workspace/context.ts similarity index 100% rename from front-end/src/pages/main/workspace/context.ts rename to chat2db-client/src/pages/main/workspace/context.ts diff --git a/front-end/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less similarity index 100% rename from front-end/src/pages/main/workspace/index.less rename to chat2db-client/src/pages/main/workspace/index.less diff --git a/front-end/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx similarity index 62% rename from front-end/src/pages/main/workspace/index.tsx rename to chat2db-client/src/pages/main/workspace/index.tsx index 24932d48b..e9ba65a0e 100644 --- a/front-end/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -3,7 +3,7 @@ import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; -import { reducer, initState, workspaceActionType, IState }from './context'; +import { reducer, initState, IState } from './context'; interface IProps { className?: string; @@ -22,19 +22,20 @@ export const useReducerContext = () => { return useContext(WorkspaceContext); }; - export default memo(function workspace(props) { const draggableRef = useRef(); const [state, dispatch] = useReducer(reducer, initState); - return - -
- -
-
- -
-
-
+ return ( + + +
+ +
+
+ +
+
+
+ ); }); diff --git a/front-end/src/pages/test/index.less b/chat2db-client/src/pages/test/index.less similarity index 100% rename from front-end/src/pages/test/index.less rename to chat2db-client/src/pages/test/index.less diff --git a/front-end/src/pages/test/index.tsx b/chat2db-client/src/pages/test/index.tsx similarity index 100% rename from front-end/src/pages/test/index.tsx rename to chat2db-client/src/pages/test/index.tsx diff --git a/front-end/src/service/base.ts b/chat2db-client/src/service/base.ts similarity index 98% rename from front-end/src/service/base.ts rename to chat2db-client/src/service/base.ts index d2b46d863..14f384ccb 100644 --- a/front-end/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,5 +1,6 @@ import { extend, ResponseError } from 'umi-request'; import { message } from 'antd'; +import { getLang } from '@/utils/localStorage'; export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; export interface IOptions { @@ -80,6 +81,7 @@ request.interceptors.request.use((url, options) => { if (localStorage.getItem('DBHUB')) { myOptions.headers.DBHUB = localStorage.getItem('DBHUB'); } + myOptions.headers.lang = getLang() || 'en-us'; return { options: myOptions, }; diff --git a/front-end/src/service/config.ts b/chat2db-client/src/service/config.ts similarity index 100% rename from front-end/src/service/config.ts rename to chat2db-client/src/service/config.ts diff --git a/front-end/src/service/connection.ts b/chat2db-client/src/service/connection.ts similarity index 100% rename from front-end/src/service/connection.ts rename to chat2db-client/src/service/connection.ts diff --git a/front-end/src/service/dashboard.ts b/chat2db-client/src/service/dashboard.ts similarity index 100% rename from front-end/src/service/dashboard.ts rename to chat2db-client/src/service/dashboard.ts diff --git a/front-end/src/service/history.ts b/chat2db-client/src/service/history.ts similarity index 100% rename from front-end/src/service/history.ts rename to chat2db-client/src/service/history.ts diff --git a/front-end/src/service/misc.tsx b/chat2db-client/src/service/misc.tsx similarity index 100% rename from front-end/src/service/misc.tsx rename to chat2db-client/src/service/misc.tsx diff --git a/front-end/src/service/sql.ts b/chat2db-client/src/service/sql.ts similarity index 100% rename from front-end/src/service/sql.ts rename to chat2db-client/src/service/sql.ts diff --git a/front-end/src/service/user.ts b/chat2db-client/src/service/user.ts similarity index 100% rename from front-end/src/service/user.ts rename to chat2db-client/src/service/user.ts diff --git a/front-end/src/styles/global.less b/chat2db-client/src/styles/global.less similarity index 100% rename from front-end/src/styles/global.less rename to chat2db-client/src/styles/global.less diff --git a/front-end/src/styles/var.less b/chat2db-client/src/styles/var.less similarity index 100% rename from front-end/src/styles/var.less rename to chat2db-client/src/styles/var.less diff --git a/front-end/src/theme/abandon/dark.less b/chat2db-client/src/theme/abandon/dark.less similarity index 100% rename from front-end/src/theme/abandon/dark.less rename to chat2db-client/src/theme/abandon/dark.less diff --git a/front-end/src/theme/abandon/demo/dark.less b/chat2db-client/src/theme/abandon/demo/dark.less similarity index 100% rename from front-end/src/theme/abandon/demo/dark.less rename to chat2db-client/src/theme/abandon/demo/dark.less diff --git a/front-end/src/theme/abandon/demo/light.less b/chat2db-client/src/theme/abandon/demo/light.less similarity index 100% rename from front-end/src/theme/abandon/demo/light.less rename to chat2db-client/src/theme/abandon/demo/light.less diff --git a/front-end/src/theme/abandon/light.less b/chat2db-client/src/theme/abandon/light.less similarity index 100% rename from front-end/src/theme/abandon/light.less rename to chat2db-client/src/theme/abandon/light.less diff --git a/front-end/src/theme/abandon/primaryColor.less b/chat2db-client/src/theme/abandon/primaryColor.less similarity index 100% rename from front-end/src/theme/abandon/primaryColor.less rename to chat2db-client/src/theme/abandon/primaryColor.less diff --git a/front-end/src/theme/common.ts b/chat2db-client/src/theme/common.ts similarity index 58% rename from front-end/src/theme/common.ts rename to chat2db-client/src/theme/common.ts index eb242eff5..7706296f6 100644 --- a/front-end/src/theme/common.ts +++ b/chat2db-client/src/theme/common.ts @@ -1,6 +1,6 @@ export const commonToken = { "fontSize1": '14px', "wireframe": true, - "borderRadius": 4, - "borderRadiusLG": '12', + "borderRadius": '4px', + "borderRadiusLG": '12px', } \ No newline at end of file diff --git a/front-end/src/theme/dark.ts b/chat2db-client/src/theme/dark.ts similarity index 96% rename from front-end/src/theme/dark.ts rename to chat2db-client/src/theme/dark.ts index bd060502b..043fc6cab 100644 --- a/front-end/src/theme/dark.ts +++ b/chat2db-client/src/theme/dark.ts @@ -26,6 +26,7 @@ const antDarkTheme = { token: { ...commonToken, colorBgBase: '#0a0b0c', + colorHoverBg: '#eee', colorBgContainer: '#0a0b0c', colorBgElevated: '#131418', colorBorder: '#36373a', diff --git a/front-end/src/theme/index.ts b/chat2db-client/src/theme/index.ts similarity index 100% rename from front-end/src/theme/index.ts rename to chat2db-client/src/theme/index.ts diff --git a/front-end/src/theme/light.ts b/chat2db-client/src/theme/light.ts similarity index 96% rename from front-end/src/theme/light.ts rename to chat2db-client/src/theme/light.ts index 764a6c371..dbbb1e485 100644 --- a/front-end/src/theme/light.ts +++ b/chat2db-client/src/theme/light.ts @@ -26,6 +26,7 @@ const antdLightTheme = { token: { ...commonToken, colorBgBase: '#fff', + colorHoverBg: '#eee', colorBgContainer: '#fff', colorBgElevated: '#F8F9FA', colorBorder: '#d3d3d4', diff --git a/front-end/src/typings/common.ts b/chat2db-client/src/typings/common.ts similarity index 100% rename from front-end/src/typings/common.ts rename to chat2db-client/src/typings/common.ts diff --git a/front-end/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts similarity index 96% rename from front-end/src/typings/connection.ts rename to chat2db-client/src/typings/connection.ts index 9d3b448f9..afee15eb9 100644 --- a/front-end/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -7,7 +7,7 @@ export interface IConnectionExtendInfoItem { } export interface IConnectionDetails { - id?: number; + id: number; alias: string; url: string; user: string; diff --git a/front-end/src/typings/dashboard.ts b/chat2db-client/src/typings/dashboard.ts similarity index 100% rename from front-end/src/typings/dashboard.ts rename to chat2db-client/src/typings/dashboard.ts diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts new file mode 100644 index 000000000..326bf2820 --- /dev/null +++ b/chat2db-client/src/typings/database.ts @@ -0,0 +1,23 @@ +import { DatabaseTypeCode } from '@/constants/database'; +import { TableDataType } from '@/constants/table'; + +export interface IDatabase { + name: string; + code: DatabaseTypeCode; + img: string; + icon: string; +} + +export interface ITableHeaderItem { + dataType: TableDataType; + stringValue: string; +} + +export interface IManageResultData { + dataList: string[][]; + headerList: ITableHeaderItem[]; + description: string; + message: string; + sql: string; + success: boolean; +} diff --git a/front-end/src/typings/index.ts b/chat2db-client/src/typings/index.ts similarity index 100% rename from front-end/src/typings/index.ts rename to chat2db-client/src/typings/index.ts diff --git a/front-end/src/typings/main.ts b/chat2db-client/src/typings/main.ts similarity index 100% rename from front-end/src/typings/main.ts rename to chat2db-client/src/typings/main.ts diff --git a/front-end/src/typings/theme.ts b/chat2db-client/src/typings/theme.ts similarity index 100% rename from front-end/src/typings/theme.ts rename to chat2db-client/src/typings/theme.ts diff --git a/front-end/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts similarity index 100% rename from front-end/src/typings/tree.ts rename to chat2db-client/src/typings/tree.ts diff --git a/front-end/src/utils/check.ts b/chat2db-client/src/utils/check.ts similarity index 100% rename from front-end/src/utils/check.ts rename to chat2db-client/src/utils/check.ts diff --git a/front-end/src/utils/common.ts b/chat2db-client/src/utils/common.ts similarity index 100% rename from front-end/src/utils/common.ts rename to chat2db-client/src/utils/common.ts diff --git a/front-end/src/utils/date.ts b/chat2db-client/src/utils/date.ts similarity index 100% rename from front-end/src/utils/date.ts rename to chat2db-client/src/utils/date.ts diff --git a/front-end/src/utils/eventSource.ts b/chat2db-client/src/utils/eventSource.ts similarity index 100% rename from front-end/src/utils/eventSource.ts rename to chat2db-client/src/utils/eventSource.ts diff --git a/front-end/src/utils/getTree.ts b/chat2db-client/src/utils/getTree.ts similarity index 100% rename from front-end/src/utils/getTree.ts rename to chat2db-client/src/utils/getTree.ts diff --git a/front-end/src/utils/index.ts b/chat2db-client/src/utils/index.ts similarity index 100% rename from front-end/src/utils/index.ts rename to chat2db-client/src/utils/index.ts diff --git a/front-end/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts similarity index 100% rename from front-end/src/utils/localStorage.ts rename to chat2db-client/src/utils/localStorage.ts diff --git a/front-end/tsconfig.json b/chat2db-client/tsconfig.json similarity index 100% rename from front-end/tsconfig.json rename to chat2db-client/tsconfig.json diff --git a/front-end/typings.d.ts b/chat2db-client/typings.d.ts similarity index 100% rename from front-end/typings.d.ts rename to chat2db-client/typings.d.ts diff --git a/front-end/mock/sqlResult.json b/front-end/mock/sqlResult.json deleted file mode 100644 index 43dde3f87..000000000 --- a/front-end/mock/sqlResult.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "success": true, - "errorCode": null, - "errorMessage": null, - "data": [ - { - "sql": "SELECT *\nFROM users\nLIMIT 500", - "description": "执行成功", - "message": null, - "success": true, - "headerList": [ - { - "dataType": "NUMERIC", - "name": "id" - }, - { - "dataType": "STRING", - "name": "username" - }, - { - "dataType": "STRING", - "name": "email" - }, - { - "dataType": "STRING", - "name": "gender" - }, - { - "dataType": "STRING", - "name": "address" - } - ], - "dataList": [ - [ - "1", - "Alice", - "alice@example.com", - "F", - "123 Main St." - ], - [ - "2", - "Bob", - "bob@example.com", - "M", - "456 High St." - ], - [ - "3", - "Charlie", - "charlie@example.com", - "M", - "789 Elm St." - ] - ], - "sqlType": "SELECT", - "hasNextPage": false, - "pageNo": 1, - "pageSize": 500, - "duration": 203 - } - ], - "traceId": null -} \ No newline at end of file diff --git a/front-end/src/assets/font/iconfont.css b/front-end/src/assets/font/iconfont.css deleted file mode 100644 index a3d9c9bcc..000000000 --- a/front-end/src/assets/font/iconfont.css +++ /dev/null @@ -1,383 +0,0 @@ -@font-face { - font-family: "iconfont"; /* Project id 3633546 */ - src: url('iconfont.woff2?t=1685165802134') format('woff2'), - url('iconfont.woff?t=1685165802134') format('woff'), - url('iconfont.ttf?t=1685165802134') format('truetype'); -} - -.iconfont { - font-family: "iconfont" !important; - font-size: 16px; - font-style: normal; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.icon-dameng1:before { - content: "\e655"; -} - -.icon-dameng:before { - content: "\ea30"; -} - -.icon-proxy:before { - content: "\e63f"; -} - -.icon-openai:before { - content: "\e646"; -} - -.icon-guanyu:before { - content: "\e60c"; -} - -.icon-yifu:before { - content: "\e666"; -} - -.icon-shujuku4:before { - content: "\e609"; -} - -.icon-shujuyuanpeizhi:before { - content: "\e649"; -} - -.icon-jurassic_server:before { - content: "\e6a6"; -} - -.icon-shujuku2:before { - content: "\e607"; -} - -.icon-shujuku3:before { - content: "\e625"; -} - -.icon-shujukushuju:before { - content: "\e63c"; -} - -.icon-shujuku1:before { - content: "\e636"; -} - -.icon-peizhishujuyuan:before { - content: "\e62f"; -} - -.icon-SQLlishichaxun:before { - content: "\e80a"; -} - -.icon-zhongmingming:before { - content: "\e623"; -} - -.icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun:before { - content: "\e8ff"; -} - -.icon-clickhouse-yunshujukuClickHouse:before { - content: "\e8f4"; -} - -.icon-rds_mariadb:before { - content: "\e6f5"; -} - -.icon-jianshaojianqujianhao:before { - content: "\e62a"; -} - -.icon-sqlserver:before { - content: "\e664"; -} - -.icon-sqlite:before { - content: "\e65a"; -} - -.icon-queshengye_zanwushuju:before { - content: "\e760"; -} - -.icon-weiwancheng:before { - content: "\e755"; -} - -.icon-wancheng-:before { - content: "\e62e"; -} - -.icon-chenggong1:before { - content: "\e620"; -} - -.icon-jiqiren:before { - content: "\e70e"; -} - -.icon-huanyihuan:before { - content: "\e635"; -} - -.icon-icon_infomation:before { - content: "\e65b"; -} - -.icon-key1:before { - content: "\e775"; -} - -.icon-mysql:before { - content: "\ec6d"; -} - -.icon-oracle:before { - content: "\ec48"; -} - -.icon-postgresql:before { - content: "\ec5d"; -} - -.icon-h2:before { - content: "\e61c"; -} - -.icon-cc-schema:before { - content: "\e696"; -} - -.icon-xinjianbiaoge:before { - content: "\e6b6"; -} - -.icon-export:before { - content: "\e613"; -} - -.icon-jiaoseguanli:before { - content: "\e66d"; -} - -.icon-console:before { - content: "\e619"; -} - -.icon-24gf-folderMinus:before { - content: "\eac5"; -} - -.icon-chakan:before { - content: "\e606"; -} - -.icon-fuzhi_o:before { - content: "\eb4e"; -} - -.icon-zhihang:before { - content: "\e626"; -} - -.icon-m-geshihuawenzi:before { - content: "\e7f8"; -} - -.icon-github-fill:before { - content: "\e885"; -} - -.icon-baocun2:before { - content: "\e645"; -} - -.icon-jiantou_xiangzuoliangci_o:before { - content: "\eb93"; -} - -.icon-xinjianchuangkou:before { - content: "\e603"; -} - -.icon-loading2:before { - content: "\e6cd"; -} - -.icon-lianjiekelong:before { - content: "\e6ca"; -} - -.icon-SQLshengjiwenjian:before { - content: "\e63b"; -} - -.icon-sql:before { - content: "\e610"; -} - -.icon-lianjieliu:before { - content: "\ec57"; -} - -.icon-tiaozhuan:before { - content: "\e685"; -} - -.icon-key:before { - content: "\e648"; -} - -.icon-bofangjilu:before { - content: "\e8ad"; -} - -.icon-chenggong:before { - content: "\e605"; -} - -.icon-shibai:before { - content: "\e87c"; -} - -.icon-shouhuishangxia:before { - content: "\e790"; -} - -.icon-zhankaishangxia:before { - content: "\e7b1"; -} - -.icon-shujuku:before { - content: "\e62c"; -} - -.icon-baocun:before { - content: "\e936"; -} - -.icon-chaxun:before { - content: "\ec4c"; -} - -.icon-duigou11:before { - content: "\e61f"; -} - -.icon-check1:before { - content: "\e617"; -} - -.icon-gailan:before { - content: "\e632"; -} - -.icon-huaban2:before { - content: "\e63d"; -} - -.icon-bianji:before { - content: "\e602"; -} - -.icon-shuaxin1:before { - content: "\ec08"; -} - -.icon-caidan:before { - content: "\e611"; -} - -.icon-biaoge:before { - content: "\e63e"; -} - -.icon-zhankai:before { - content: "\e65f"; -} - -.icon-shouqi:before { - content: "\e61e"; -} - -.icon-zhuti_o:before { - content: "\eb6f"; -} - -.icon-duankailianjie:before { - content: "\e65e"; -} - -.icon-xiugai:before { - content: "\e60f"; -} - -.icon-delete:before { - content: "\e604"; -} - -.icon-gengduo1:before { - content: "\e601"; -} - -.icon-jianshao:before { - content: "\e644"; -} - -.icon-jia:before { - content: "\e61b"; -} - -.icon-hao:before { - content: "\e631"; -} - -.icon-right:before { - content: "\e608"; -} - -.icon-search1:before { - content: "\e600"; -} - -.icon-download1:before { - content: "\e66c"; -} - -.icon-xiangyoujiantou:before { - content: "\e79c"; -} - -.icon-shanchuxianxing:before { - content: "\e6a7"; -} - -.icon-cross-copy:before { - content: "\ec8e"; -} - -.icon-shuaxin:before { - content: "\e62d"; -} - -.icon-tixing:before { - content: "\e913"; -} - -.icon-shezhixitongshezhigongnengshezhishuxing:before { - content: "\e795"; -} - -.icon-zhihangsqljiaoben:before { - content: "\e759"; -} - -.icon-xunishujukuguanli:before { - content: "\e61a"; -} - diff --git a/front-end/src/assets/font/iconfont.ttf b/front-end/src/assets/font/iconfont.ttf deleted file mode 100644 index 6bfcd6658e8054e631ff2da4529ee37bd26aa535..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31224 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 diff --git a/front-end/src/assets/font/iconfont.woff b/front-end/src/assets/font/iconfont.woff deleted file mode 100644 index fb9ba006d2a5155ae076968cc9252b3ab7e28f9a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19684 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 diff --git a/front-end/src/assets/font/iconfont.woff2 b/front-end/src/assets/font/iconfont.woff2 deleted file mode 100644 index 5b72f984f1739ed110b7f2766ffbb37a56154f3f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17076 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 diff --git a/front-end/src/assets/img/add.svg b/front-end/src/assets/img/add.svg deleted file mode 100644 index edc2a864d..000000000 --- a/front-end/src/assets/img/add.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - 编组 - - - - - - - - - - - - \ No newline at end of file diff --git a/front-end/src/assets/img/ai.svg b/front-end/src/assets/img/ai.svg deleted file mode 100644 index c8b0593d3..000000000 --- a/front-end/src/assets/img/ai.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - 编组备份 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/front-end/src/assets/img/connection.svg b/front-end/src/assets/img/connection.svg deleted file mode 100644 index 83892ea97..000000000 --- a/front-end/src/assets/img/connection.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - 编组 4 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/front-end/src/assets/img/databaseImg/h2.png b/front-end/src/assets/img/databaseImg/h2.png deleted file mode 100644 index 764bfdceb40ef2de60cef8e8dd2b0903b5bca796..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12968 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 Ef>WL}Q2+n{ diff --git a/front-end/src/assets/img/databaseImg/other.png b/front-end/src/assets/img/databaseImg/other.png deleted file mode 100644 index 8ffddd4648a8bb2246726a5baf60e0a1090456b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 844 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 a)c*rcJo8_(`HGtW0000tIn*bw4I~WsiK;q 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

WldNR)DgL5mJoplYN#O>wDr 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�RaE>&Abjz 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�zhJ?`n#YjaU1}Nh+uuO)Y;%Dr7Z|dEt-Z1nSDq| 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±);$u^HjkB9xOrpXrK_sowxb~K8t*lEX!6Zpm4<9t&5Va zsr9e~#~eG2Ol=PGHsskXF<-B+{awXsLv-)KKOJ(D&?D%E7##r_7NlL#TJw@1k{cpX z1j$R@5*9wor2sioXWyc-m;dZPS1nR~?tCgZ5!SUJDp-GVE3&2%nneoHzWlACf7^Na zNME74F9E8yK+(n?Mp3Sa#2AFe#><#?j>gKzZ;OS`AB};J9F6LSzrgxLG=Nw@Ur?M9 zoxZa0avji|(Y<#v?)`8rUigikSUa;TiTMgF{)R{}kv6p#o-pt0h(^!=j7}7MS8N=H2P!S~0>cf60xAPg}hzUkt**n4F@-9&pPK^L2wD%=Cl64BP*jaSYx?%Sa~ z3);`$%r#*&3G=!wukC3QG;e=#&HdH#^L18&wgS^~S9VQY$L9|UrYpX;WBY+TFKCszCNd)O67crB_VRtH~3&Z#}j~n&+H%nGfI&gpd?$Zo~bU zvy&UYu>dk(9;H0}@m*@s_thQ_=KukUyc>{{N$7$Ii}M5k8|+RLD_HM!5U;o)&6mh- zdq{2pc_=3A!G8S00bG4>NXt5$jf=P|UFz?MefW7V<5!>qQ=9?BjeEP7;~jW16VZKY z1m&Ktv3TOI{>8ZC*)y^K$WyU7)|DcFQqLehf!HlrKZt!oAt8-G57Q6)Zp;;Af}f&chs0s^S{`<8DnPRgV1$IAyzv1UM57Ni)1m zo;Z@nOft6M+})N0fO~S0f=u5vypYXdsDN-NN)$k#bqyerU@bu#$Q@1mbJ@>VI&_w6{o z7qNOZAA{fgnYKVM#j_>0%W9{wAB^jJe{w$BNJ~=1JS6jJhSQ4hVm$8ly`b_J) zHi1%*IB1g)Qvy|F@Ay_B3M>Ve)eMlR^)lBP#=HV2rO`VW=Z6Mks;3lK%^~7}vw;^r zk1jqv2I6L1Iw9ILgKZ)*QYUl}bX}oa*07!1#7%FmJK}7xYi+>3g6qy%2u$HA_z(;1 zxUuXQs82J;-1qCIsf#*wGyga*gSXbKf~c(4*^85<-k4bFi(&ErRnk*(E+h}y;wyX4 zCqzx;XK&*jf;S${ZD`7cw7KI+KNT0s^&g{#u7NrP zb=(h5U>bbt_=_m^aWwUzhvRF1?nh&O-;H$UN{pU88TVg0jo*v;JcOvm+L61V$3LYZ zZiv&{Y354Ce~i#30R9+RY)fh@T@S$IR-3nd%v)pp=NyCt*F_L%ONSToo}x(wB4_CN zCI~Agmq4xv+lhqsyS%bel2GQs{BoDhWG{=Xc_%_vp-4twIz%y%ADC;}^e1N^+57-( z(tDDc3NiVfyfr6k0GM)(tGEPZ;OSs$Ba1Q{u+mk8FMO?=Ie{{@idq^58BMY1cyO2V z;ywNG%YW;!_@V#vb8*+xS5k{tMVz6z^*DGAa~_C-m-dXtvD(g<*|{e^{e8*CpsO<${ zVE?R=9xiByIgkE-_`grbcYkU+_Tywm+jRFpE<1s`AP~o*{PT1J0iH+&7sT8zgJ92> z&d15&jW{u}9+yXeP@FHYbC-x19w!b~#_s|E4tK-_U@#IX5<9w;_)FdY%Vu`^nI5-A-mE&kT5XVul zJ<|HinuRT|1;;A+Oe;3yxeaj&_9a@oGnN9ilCKf5D zmpkGG#C#iL?$39}&e9I_X&?-+80k^O$Z`;wk3t-bP_jgT$8f2aQ4mzFh+_F*Jzc#4mIt6Cb(M7Cd z7Ry=guHZBV6p9$S7aVZs$@O^qZ@d^gPU7*41-%IZ7v<``ZU|fATPfpvo;(>pH#Qm< zJNn}Ip?l)-AN%3>=->H;Si6L6@d*blUN?b(epp3S12c~u#An(fnW=>wm@d<#8BY-Q@OR<+- z^vLsY8eX1_0py%LDEyVn)CcD{d)NuO?<}Jdow8sctt3_MvcO6bRhuBLRes=ddzo=- zt!ADD1opA#H1s0ptbj~C^ri{mZ^&9ki@S<*79~TOLp1T_doi!Np)3B)yZYk?KHn7|cycBt&f+a_6ry5i z37`HD6u5zHLNsihBuTw&O|psf+s}kgFOKRk@Dl{rAm~}DpnPoLiH{O!6HknjxGny@{)gi84?P;syzP!y z;;^J?>Uyc40y%-M6T+Yi1i$x{OL6n@`RILlDh6gj{OG0VMN{!m6@sHiIW^Yf8fI_n zEsl=8)v8vGXE&T5>#w|7k8{q24T!8$gK)qGMs@Uu4u^Cv^@anX_&|BOnJ{IeI=2Dm zfETj}09Mb$sqV{hgt6V|$IIX7P)zI{i5uVsp>`WPvBnu+#B}r=uAeW=#~fyYBXba= zYaF}GHxJC|Lq|l-`ChW@W4tgvr7(ZiTmhHTbO`~R6XiORV<2!S`T#xW&d1wdeJSp{$nGDP*){U$hSOH1@AJ?jYSY&AcUD-pf>`15 zM+$U)rY{nilQ7h`f7Fn^B}gc2E#&v)=KA1WJIBobkDBpy8vpts09Y`rZicA^&>dwK zZ`$ty*elq-dcQq1M6jx}w^9Vt`L6?oLkR#+=osd}mo@B^S70r>;TvNo z^dmp@g1N-Xm|nb`{V?9iy9@7JRb=9Koxq;>)feL8=sh^eAB?X&^3J&L&mD_hzx-)V z3=RG=~?Yz7gFppHPP(GZLwn~fJzE z@4WeB{MhG?$6ZHGMek|m89UHC3GyQQGs>_|Rx)QLx2ZHG@P=z7aC7R5pi#?C%Z*f6 z@|*9pcgLwuqhlsYBUFrY78>KrICFy7gS*q&m_dfj{KAw?M?aflJ2)IFr&S#jmlQ!l zS3*PNE`su!dK4`JSg}?vC#x!#!pTkYTr>1bvq!GMbX0d^dRaXfpX#|WK6U@2@wq$S z9g~`*imfdR1hbQ4oyWdB9eZEI)$*B1&P!lt4R@#8_00IKqTpAdA8VL~`mA;5l2xU8 zRk$fx#astPbXFPMXIq6!(u6BkhuFrWFUSxg zXt*=FZi2K%w9tWvzb?c`wK;ay5F^XzcD%fdMar#ji~jfA8B-%1!wq7Wneg}6$$y2R zFQYu$N%wcm)?)x-Vn1AqzG;YP2%Moya1F5gzv1+9+;eg@4xL1u6Gu(i&x!o$OZz-K zh)f}iD;vYH_T)%(A7Xt#D7o;;G-&)`JWtH-iLL<%hqH_^@t{dr338O^DB9{8QAC*@ zPhs+Czi}R&f>$29KaPLoFURWcgZTPCA8!NsKXmSR>|K~aT;jUo)KWQY5SBW=%3SG7 zuQ@-t&nd~%pKY*C2CgmQxc#Q}78q;&TOrq1`{6&YtDx-q2<`C{KD9?XcOyKzgJ$vT z0<(^Z3$vp5pJbttO+;u<1mbFTF@XR8KmbWZK~$=_ZxDdx&d}$~kHy2T$YoSVR?J$+ ze4Cg7r3+S(y$Sh~4n}0f=3?O;d09ledMUU>UM@i}e;V_vRO7X7t@>msW|rAyIuv@7 z7+B`SxAd_NoajZtX%$arSr@jpn+QZ3usEfMHenTa^Wm|>7vt2#-ncX&E4>!K^N|n7 z_Z>bPy|284XV_7I1iM?>GS`bt7}j+MVu&8vq&&TW7Vx2=9kFqIB_05Iyf8T%-}QVo zKK5_15Ws;9PKDEmi~~?Xh_csVnFEnNzjHo*=O^~Z?|#QboZ5{^lMYsdV|If5qBj%o zQJ}k#HlqU>XX#Vm3}Z&+_4;=ScE|Gtyo%7)*=akLj%^#!+S^Uo<2jPregoCgg)MLNn_vdr zAbJsyb7O@zEfS|&7B!Wx)89jgecr;Z?~bR?sD5E89{=jec=m%g$Hu*Tpv-8X0&vED z3HQGL>O;50=Wp$b|M_>i;|G5Ga8xH2qdbJ`UK9@V$XVLc;8ec#YCl9BZ5u=elz^Ou zFt?pAC8&`?xsLj3p^}{6j%P21lL+?RNLCJ>n9FGy(k-K@P{ zey-6bYa5GI$cXB*$G$3E)YTNe|0n7 z@tMhZ=O-@4N1i~5HsOi3A^o&bl|@8j5EaxWYIx|xq=zGbkKxUkaTm^@5YPj6<>Zl$ zIQ}$>w#(=M)XpaXG}kwb6oT63WLm3yAa&k5xCSSbaw)om#`|&t^;6^By$++X> z3-Q3YW3ht+sk(p#T@dBfbUz5O!FWMaXOJv)wIvqdVApCgK>1Si%hD^z4(MCRTpYsgFMzIfC6OZmBR;97ue%e%hA z>vhAMvc*UC|Gg(`rRoEq!JH?uBHge!A#_lZbAky{>%>kR#F2jQnbf%}5ZMqXIRf?w zGg36-H+Mc>+rL!&BYu7}AIov#v6HY&znhniMbdXT@8Gp<^{Mf%2GU7cLsg4>)G^52 zKh?=yOe&q{j%e4+-+5}#2f*{vHtc@<}&T}2CzJlW3TyJlD`SywU z#Sa~dWjqFYl<$WQe#o1>{Hsj!Op)MfVCo#W4CdF++rWQ!Je_`?>HFC zt9zn!Zg)I-_V&1S4#3WGGF|${=T4n|CH^lSSc!lBPj8BU`F2jp6S~>2YHAt0__JHU z3)B)nYiQ2XXEa($a+!S+FVtUZ!P*TE$mO{Jw;Y7?(7$G z%L7Fcm0~JooLlMg7*@Y~)D9yn(MuMKIw!c`^R>r=N-+ zee#(Y-2+e`JWCzeqJb%zoHq}&rf3K>iIPOXyc3aR^{fnWn`nS~Rl|IPG8PdKPa&0_ zLn;k-X>%6g2&W5{>FiQJLK$2_(*oj4=+IZN{Y!EI*b$0!OA-Nc5+Ep2K~=6mwx0ay zxh!9Py7fV1vxGdkwvHAFVjDE=vOiYXq;{|M$L}|8j=%EoL-FAXw zG9Gx^@l4+PB;mzx42~;IwD@#7gj!n=diU6oHZ{-qF>$Cg2qG zUT(yDK06aX`cKctJDy&Kz?Np*K(|2M0BeeYB3=h;?`57m_7C}r&IWU-%y|~a4)2K1 zf02_5nR^wSd6n@jSjJuP>gX8$H5e<#^9p0t&-|O;GaN5<4aXP$#$StL4?o77T#7qh zejy$``C=TxT1bLn0E?JDI3Jx@*{C<;F25>Ag?3!?v--Yx!(hGP$jLerI16_P60ffx z=+x{izY{-yT(VHcjc=wcBevPpOqgfg8@JQsHy?%LWR+vjcD&d64QN8G01H|&Xon5hhH#V$x@^`;FdgeQiAJp>WJf=?}f(k83{;TMSCi46E-k>@jng!^7!=0qW%HX)EEeJwzg znVYZ`Qo&H5!avJNrN5l85+F|g;ztxRF3NJPWZtbjB_LA2fx75&Hx&-i?p1IrF(J#d z!eZ@Um$L&!saJ=Zaqa*nMzAE$qGYujcIxP}FGr7>^dOG4=3v}(esA1Ry&bmXaP(ZM z!9k#PSIFm5Bkq3jKz#7*&9P^71fa-4IY2M>%Fy2s0m#bH1b><{>VN8A+#Mf(6s0)p z&H~Mo>}*c9Z`;)w$h%ZDb4R4xVC3uZ{Q2nl%@^a*Bg^qta9SrS{p$c&cl`aj-xWsz z)Xgr}JV`1NbPSaJDbiQDQedN~M30946L`qB2Xu4dA9ZF2+sU;Xq@p!LH;~+2=OE@| z1BeIuU-RoR4P(&-MDg0mHN^mAtp$Z4aWCbq;uHqvOzLKMp1?S4>)TJS#Kg1b<1B3c zm0Q@sv6}>z^Fvc%U@*SA3l=VN# zw7|LH%5UL7#EW-Ec=n#}qC+q<^z=bp1T!9cAAdF8_Zvs!CJs^Z1WLs%U1C($k*Cz< zxN@&F!?hq6Ms|HJ*VY8`u>}z7s@9oIj!Q-`Qw`WFt*RLVd^T&krr8A+(BR>>zjMj?c zO;P67r`NeD8sa=K|4f?&T^qFu(Za^-{Lg>krJud_%bWAbDR+H@6xcc&T9bsq%bm4g z0(NYf?-BwH*s~iPxmtsI7cr@|-vDvssBaCp6UpFo=f~%i^_vy(s?M?_8Aiw;u0(`b5ldRPD>R-V`g?;{Vz^9*Y_#e)r-k zxDOfO#N23n%fN?Y69qqCvvfWbT8i_ zpqHqiUV#0CQC81-@sxEfxCxX&vqbcAoV3P}oyq=)oqj=ZctFq!zrc>3f*Jo}w*Mfr~{vfQ0b95ani#81AbJEm^zjGy=n#}L;z&mE2Y z(!vCV4Y5Oq{jG*QM?&KfhKf;Hv7VJdIV)CoVuLsyn0_F~9j6_m)QZZoe=K&6y-BR; z=}(<<5sF+K)igmn5E@!&h}1~Wx%H-tndc*JVYDGmN;Td!9CTla0T zfP|t)4>*3(Pa0F`CfZQU1=b-My3Tdl)eYgZ!5TMP&&}AxYM{mmjypJ(d4sbvHaTmf zpCkS1muBPKc@CfY__HWDuEr}r2r-7f4TC}-@bl`A_T>;b3@#{(2DmJbSK=4{r#VQl&l%n zIBj<<>_46DB%ZY1tzdQe^d}zd{~SNd^MT-VG3NHoA2+ztw>9jKo85on>8~3Cz+uaw zW9+>8*B!cxRwQ0bPY0Ie%xv926IXG7)NGs0pF2cWbfYDUcT&0AF7hizG8OvBH(B|u zw8^3exiA%rNS4wfE_{&|0nAOyl^~!%*EUG1(8{%XBK@4uWRc&D&wKM}iw%g9K~6#2 z{4hlZyv?-$F;iCw3KN|s{_yo*uI)PO7bPi~m=U*5uf-32;cWbCH29C+!oedP_xo$# z{s=Pv_4x3=dMUnj{hsK?@kjxMx&a{lppYFgj8c*=QE(eXE9H0K_y)d#vvb#=mx-?e8$^rH<1LbMO zBMiz1o`J~O_*y*vy|+YTjNLMIf(1jIj`&9tw?yf&?~WgT?n>-!{0_Ttfc?hpjI&td zVo~XwC_HY^&+Po*zen@L?r6Mx1GK@7eBd3tW@a&08d{lVXTA^0WG^GLm$vBAMsIw& z)A5L7`v3%;?trN{<4$jZv~Q0%O8pwgBlb{_B-Utz<(qe_1M8jhv~-v?H5PrGy4Xaq z@YA*N_#Z#=L-F!~hv7x=xQlyYL6Ks!dN+eD>7hc zqQs({feo;nhxA}{lFplh*4@)q2cL6EU}^{|`ar znVvkix5Emw^YxlAJZtsl|I;43H+@U~O5gkbQl*Iqz`^ogi%Kmf2}qxT6OrZs%5LXb zgSm_gQce~;PwFG(3~R~x+xaE55Z;-Bw5dg$7`>Prg}jJq!%XEfJA1edu+EB!%VH;8 zmf;qqork^v2s#5QECq_ZH78Ugn`ybv_XeCB;1B3&a3Arp|1V0*|x;RSU}Ip8r4ANlDo z#siOEjtOMW0%A_kW#`M$tJHNI^Q(c2IL?99s!w?PK;o$_%NH1DH*giHcO%*v0s-$t zU4JLocJxXm200s`cNsSS3hZSF0ue$LjP33+i3ZXtzjtS!B5kT?zE)L+ijml4=6!M; zyyv^YosoG607W((CLXcGr`Sl3SfJTuAtmDU^eFXCoUfA|>df?L1Qe4{A zM}{_(L@Gi6OKJQ z&JC2+WXpF>g1qPu#Y_eouX}3eh`#Svur5$NuL^;AY-4}?FW>tE@wMAP{_SlwZS0`q z?*IJZxb3MU9EsZxp~N9Mlso|990Xai8W=;Y0bsGq4H%&xN9e~P^gf0m9LDg1*^drO zAC8xLA?A7z8TBxxs4S$ZS_cH81Oob<`IA#1>tB|?a|=sD2%+NILEiYx%3lv2@p`%M z=lqHx@*XAMe&&fxcTFzS+oYlgCY$Zm8BuazxYvE`2J?sM#aSGfTr%dCTVwL{;%H}$ z%|-(vd&jv&^jcSA^&lL!ZYx9|I*+(5CQ<7-7a1Sye!oL(%};d1A=>%SXY_ptM~sPB zX`*Z?L5S$B9*0?hpJN?<36gG#F+E6@yZJ^%;#@S}`lZVn6eUs@IW9s1L%z>%#XXhc#y>@9!>0Cg zaiuR!m_PpH^^MZ_u^o)lg5M@{!Sl1<+id$H=G8QXOJCkPUcSEisfc>bU-Hid+=^%R zualrH0{FQvy!f-%B!81WIPI>BkSFB{h(y4NWVbb|HTqI+&YL>B$ttYrf_2Xe#@==2 z$1;jr=8@;An-55=K*W4!5vKf|$m$WOE0CsznSW7UPG!qr@p$rFMA+7`jG`_%+L$|!<;>< zQ2e26f;ni;XVA}s2tARyh=F_iw5n4W;iiFiyviW=y1`D+GX3sPK<{@sM~fe0Xd?OaEe1-R#CbfLBz6(#{Q$vFX7;6CXT-yWUl!aV^zY`Oy2{a=Mb5f z`fiAaFT!I%Pov&*3Hx+e@Kz3_yh;28oE9D7{_K9nM1W7D;}kz@g7!FG-WqFT(Y%b( zW?kftj|XDHZ09GDU_IFir~6qhnNGs)6tP4ut@38~fkz+;YOi8{N*a{R2pAf8f<^P{r;NxTOWLDy4Kfr7VYad#M^pt-Q5W3uaCfv-E|`JmgU|aOBZJX zYhj@iMj+yq^(5j{vpJFSw#kr(2Z>?K#hrAf;SmIi0677NI4tv(sNlOSU?Svy?^Ie& z*Sc*YfXJywkae`4L~^B557rxzVLN-Jr?KocgBf_`0(5E zA;sWAAcGLe4pf>fqgG&$mCkS0xB`)Ib`KlTeWa(X1YB0ocv(qenFVZJbtJ5O>DQi% z-M@7-_Nm~e(ik1#$%JOmjqP^@{{S8A1eNicXDaoXC+$?^ZM(`KewFYB-N(Ayrp;=6 zSE#_&@;QM9)odU`T0?i<3bGsv6#$6h)Yw_aL| z11t0KWMv=*2M)y@IAmya!deFkJLh5*B5fArdlC-}C%%O-fOusm$Uc;h)BJAacPogj zpRU(MlN6b}o77g|-eA2%A|iQvMT{dtG3Eq+_wwpAYveR#!U>pz(3paV=tjdIzA~F_ z-Xlt*?JFFT)N{E%uCTrStvm0<-u+;F^izl9iH{zLi<})Xx?GOCCSff2k z0y+TQXzDXZJvRg23cYMPYKTwtQhDE#7vc-#weiFELU-k)DVCh$>bxnM%MF$GUeiu) z7GN(*~-+?H^n>On<2lx$yi0{x6#}eUp#=rpznDf!(Na592@>1 z+Wz|&mSRi~noI?CV_asP5_hDv|LF;em+xSoIVN61fknDYSr|BwFj)3opSM2zmwEQT}sg-~7hI=Oxi#qE-ed_FBML_BZd_!=qWrA&A zay2*^`Q~>EVRlM)WYo;YLRtXKIGIoJJ>Mt5q<-77P2$Av5uXF3${;;=sKpZG9;Luk zOp+_&Ld}{Pp`8nbSb(d*)F>Q_Lx+hQ7D8$zdSQ8WrCpz6yQ40Nr7b&oHe14ixLUiJP0d zBoc~xB;KXn?JC(e;g5eD|F(VwZ;H@raypc2Cu)D=>MN&NI~2~E+E3SB9UB9xBN1II}(sV%#4iXO?lSQ;Bc=-REL<*GP=%ylHwpW-rd68`_ha z==mv8piMKt?_WSMWx95HB`=Sn}a~LH^ zn0_cAM(o4;;~?UJX^!=sf^{*2wZ}5+UM0d3gnJ_iTq3V<+%@CbOCR@lbjI``=64VS z>%f$8h=7Nlx*Sh;ybz0j@ooqzN`bgc`pW`s@-@wFO(xfHPOSDv$0%#a)!@DF+V9&L zK^T{YpfgI1V9t2H32$fCV+R+=7M%0WygbfRGw3w zo;xT8R~NkcE-w`J3M60b(gBEk*Iz&q2Ox0mdeeUZizAUP3u9{?x=5TW{EVMs2ssF@ z8h#nydhodQvz~r8T`P#FTS@Yp#}OAiW$16~FY>e?fKicW0lu;xl&h1LZsBa;#%~@fL`hlY%OsU{^6l0rYLZ30`{cre}k{Gl0New;4(l6 zbp6tEanJ9aiXE7h)v+(eO#$p+6yxTxImUfj?-lLl3!Qb3ECm*^ z)BTS4vF1}K*HQ2?;)1!JSU(P;U)xVJaq^S=1P}4qNAKv8$$^#fQo?RS*W&_yj34Kq zq5-@*cFcChAgn5R0*ZC!K% z#JzZ|tRai9Li|bUsafJolqkt@;_vpb9Z_$8@4fN=DB-p7zaek4YmHy#%%6VhFLQka z1-@OlYd+u40bMvuT9Y z=HiB>DzQn!mPVCvQ9c8S;1Rk&Yp4uB!;?z^5E_?os{ zHv~YjibBS$ied6!)K=aZ3lry;zN*M34*AihA)UzuXP(K2&D`Fas^v* zW$XpQXs6zOwPLRq*tw2(#U!lJqH@GBs#P%v(Ae75{_XSe$fr(6XHQqmBWN818SYZS zi=%UUaFJ_*H3$)VJqa{K7D)u~-u~UeE^-W0I_q9VH$ZQEs;}$dF}WIavEyU8?V@?Q zCL}Rs<=U{a{gqcD^fvL=er?nQ3cN_BjDKDyd;>8+Is@j~)XmI&(&UadLCL5qp+&<8 z?qB=aMK*81T#FOx9)J~Fo1BgrT;%RvUX3%oYcV-E9zV1GP<+RZBlHz6XcyVl&tM#h zs07Uk@;re=7>|y~fjiOO#|oX2tbn_DiGX!tl;WRrX2HnDNW6FIMqKOeKzAH20E2@s zNn$R>$v6fPAn!n{4>}fZ!9d)AP+<<`v12i&j)59=%ZLJ+dr?m(e?0hU6C%m+RZKKr z-G4_6L!30~98N_iPoeDSDUTk@TLR$^(;qwWl{<)1VIM?LReu~1wHu6kYU7iAHRb6P z|2m`iTBL2Pb|~81etP|@bE_R*8~+>fwhI}zJmjmDNM%BGF#Dl{IyG6uA=MzKXD*<_ zvkc;2z!YH~nVb3pORUv3=I|oVWlIGK`d`>WoOPj zrX!2B(<*QK#a+2N6U?aBM6yk-3AdHN30J&pptE$bzb{^xfJ-w0#|B-Fo%3_Jt)9Wz z(iONt&JosJ!Xwi0zY`**{pbsqRRQFDGVgCb7~d3IO3K_POnz;ZUpLEQO|}>IYfCmC zVMD^_gpaLz^YYp7nsT!2R-H1&R;I;`d6{QBd6CKW_xHLX0EEd2V_gT_6qCUbWYWo& zH@s#N_;CZJoDLL`YU$C71ppgt6JX?QSJT|-;ABzRFS!FP*pbsDKN^9oMdpd9xfdC? z%FH?y)yVn@Nb)2>;ujvViuCOT)y`7lz&tIRqI2$Bv~)*iM4nkDE7X7A&@`eMasdNu zgn9OfJ7|%A8N^eQGXk-+-f||RJiQAIc1krRKd*mSXvae~Io6G~^S1a|*VePRvqe|K=u&DXh~AoouE z1nfl3U?etA9>cSsTKwH{47>L)-M1&Y`*+6bEV>8`;uW0zte5-X?qEcU9DRkIaE=q- z^inBjr%8X<$;xRxHak1VS8ov7Yvm0-|3`dHNz}utwBd`H>*djR`VVn8-#1H-K-3J; zT`QR1uHf5af%!g((}@}8@+yShBG+CB+{Wx&jO-kbMLhr41^@$ha@=rY2 zTHs_Z5s+(u>$YHh8qAeqQfAFsU%h|hump$i_z2=A#eJM&84JrXimB%KBBz3Sh*k@} zxlI5v6{5FRmQL~fwVwarlHRxuf7)M!cSX$|L+}6an%lpv5%-2*y6ZPwKLh{+C!TX{ zf@aICFkiO*7{eq9$tcGnBm&CRwF)7i*_^vHx9BwpA5G}K`OxmfEG0IzZKhsZ z6pNqzW#hS`6jj=>lilq?46%(Go8(|0cDuHHh5pl>vHx`aDpSHbccVew-_euy;WgO( z=AB2OicL`{EJ@7Rg#b)Ziz;ts@9 z>!+gv=i;8VJxN9!!MHVoh-naf_XZXS+%M1LlK>6>ZcOY3 zL5AH}K{%i5sW}O8N?&xK5UJ-%PiZV>Ls5gPJ?nF8Tx{H*Xed^Or8ytkrE1lnxG}v; zvg=P1UWgYhZ6pK`j>Jwj0i!qtx&)(S5;4~*#DIDltBs|YM2}*yr!NNZYtfG%`zvtX z-ge|leER5A)Zap%JGULaYa5-kuZ#2RM78m${)5D<_ZE zHWwL(B}6fJ*o3e~iovF*5D$9PuLP>OS`C-AA;C}HihW~ax!j0v@0fFdRYQGAzh-U($M{1X{V5TyAYigYPL5P>D;u!V_5FGEji9g?D0n--s5!Th11|bXrEPJpumkn8g&Fb;Snr7+j z2fUdY_ElKRC0MIP>6TLzE#UHAqCok-3=X>U8-sOhA!$D*!m*EhO<%YBGOOQuD2B9% zQ}lrMreL0kCGx(=tKCI<=+$&khkcpeE^-%X9jO@pa&Sh2q8O*cO>i>Y1(YH-YB=Pf z;2IVZ3o1#-j_)0a`RP6JLhpNGPuKs?-kZhRnq&uBvBxtHcRF{@n{!rXR%X`hESIZo zqjq5%Fm4`bpaFqejk;x!EcF9C^V%;+fIvJT1dR|a3>eWi-R^47F55NRmCo_z&6{@~ z&pq>=H{ZA7kA3zzdDB$|>2~Fn}DwSdRevZ;D$ z_2o++BWSaK7L(g7yXFujR`y-*1&9^y;Ov+IKvA-3Ba%a=3Lc%#^d=wvvCE+=B>UTsv>L3!DBvIRq9y zp<$JO42+24I}h>>W+L1ZGi^5+z7IO_WmZ#;IrmIUsjf%H&^;dwJ18>fm;kF9!{3`LUk2oX?( zbM#l{w_Gf!$!JZ2fwLr(_4mAikRe}Qqd+(_34r1UJ29Pp0MlG+;EjHc*hA&-P_YVc zvI-#*%ZXarBnsvdF8-hcE_UQ2g97kvgr61@=WR}rTkH@(tk&I=QexOl;E9PuCtn9G z70iVImELqXI$5kSU;Q#i{I+Q$SCWsQ`g2@my3H+VyJS0rA+)HQBODfU)WR`6GiG!m zU$shI+n~^977Y9Y%4jeAqz|!mtO71Bw zPa$3z);bSMRO11AY$+m`I2c-hXdi@=92Ix{aul<(JOPt9Ls-`bcnhCei`m;dY38hZ z{>8l(^Ykj$U3m}HMxwdAu#*1%bDvKCUX2mt|FfActuCcoC#a{DchXO7tgy2m0hm^H z5II8)tz-Ki((CCmYLPdZTj~1QCR+`?(8lQ&N7@3((Xf0E=McC!Z~kAty_){*jpcOX z#;x=(^)8T#Ck3`p{-00ZSh|=lf91J!4`bHF=sx61V8iI2 z&)P5$2KaSzhlz9Mo+*KxN>oD&01z0*c7}6?cE@dkXn$m|p2L6t;x+c;vn`LQl9Rn9 z4kBU!pAHbBEeIlgDiZLBTJCzbXS)#6@lS?#C zZpG|xSRZI!gwmDDG|KE$=U9}y#pJrZVap1oEa9wOs*B-UURxwzg})qcR>NZZ@|!L%KP(@7m^sE(K6 z8bcfvNyN=KuOBsCjPQ)kV+Z$enn))`bC6vL77N5f8FBOK-FN4dPbg%+&i3!i}{?T3dl$diAzi>PC2oaq7i*VGLhcjpK=N#Y~ty zPO%V^d0PY9)-shS9x#|5K4Gl-x(N};8JNHuKgEF(rizmz5I6jZM*(6!oPaV98Dj4N zCmx<;+|9Slc|bZt+nx4FFop-Cqv<>Sm(u2EH`0Iql|M^wzx;Pn`^Wxv+I{`C^gsW- zo9W-$pdV1#uol?*4zu}O1I_Zi@4m7bmHIma{Bf!p{jmxE?xOg|5myJXaAlkz`S?u zZBDskJ7W{(ze}I6JUgT*dNp90Ef>roNc7NZ**D`bCXPpT2SPXyE~~f^V2JOUQ0pc7 zlzu$6?a<)FM(54<+?}ps6*+SUe;J%F` z9O(4YAKgvQusHAaCE8!OOrRu!HH?CRiB>S?0O2#`HvMfqfD0?6sZ{HQ85~G-f{T8Z z*{0Zb3XL+m{wMGtGTcwSr1Vj~qgXK*GF9M<@_dvz{8d^%Xbn6v0)VT^d%Ikf2}7*8 z{~qE2T1WrTUX0{H!l987x*$(UeZ;(TNPUsQV%=G3{rD_nC}HDfhQvwqnyn-?LV*zk1 z>F~iP@lBh*ER|yy6yR|Ipt}ZK1K_;h4DX<<3I;|JxB~|F54ar&zz3JNIKM3@rM@L% zerv$AQ{TRq&T*}8oss9(32r0g_}gu?0H>Mze(EMu5G=ra0CTu~h6PhFi-4D?F-78= zJOoit^qV|73AgR4e=$`tmYC6KvF|#JudpKQVC~R$*`2gW-0JkF&#L^mT`uaDC{ii> z+(A)?naL0g8txH=FC_!wMjB>f0FCnx4g>Y$)0U0%#Ert^vBWzxtmEwdT}Jwkt%O2G zyzA2T1Jf0&RN=y06DPU7+xl8cuW}I3D}SBQ`&xSJ=@-)R<*#xQla)^Tq}5F~((kvg zr~l(W_-wlVcYh*{6)bEcjk5y+V{qS60w3$A z5i0RuOw?}(byQE(CHd>axy1C-#xpqrE1X{P{`d`mTcEFp`8aYKLL{PVC}~cQ^6V%$NhX4} zhdQ2bdh?iYaXMfK1$Jo|2aC{TI_?&zyAY4K5|mK$^EUY$68Eap#2e&v^x}O01R*XX zb?gBnBSH~M!eNh7Ga%6@YJ3@Xj3|WBAS4h3h3J{r>#7HNFzi$?h+>&uF+}h)lQ0Pr zj}#eKg%Ib(V=ExcMOS?q| z%UUqu`Wsi%#nW80a;%nq?>U04chYCyW5fuj-J{IR;6&ZZlY5K|GyKHIl*Lb>eyhr+ z0H=EhMTAB~i#Ue2PW&^CEsm{~SckE$G}Zx?>{$re@Yau)+bJc~3Q8)gb3ycm`K#qY zoogKip~tsMF%weaA0`(1``Cs)q|6^MQP7@S0c}^@_gE;_;uO3#ZP634sFNY{$AQ{= zaL)yqKk#rXV`_02&?&iipR0+R=|BFjuBRWr#?`)Xc10U7D!tvhoxbxQy_By0(od)H zJPpk;y|IA8a@syV-a?3RI!w@FD=bWX{Os}cwP*Iyf5P#ROXDsRHf-Cwem%YYU;ppv zTW2q%8-J4(>%bNaMGnH{2g{ssKhNz)F2-C&y9AokHb4Z(LoVMMa;KtIu@Q7G=d5aOUc)+S<@Lz84O4)C8&hz|DcUYhHFK zrB~w&9 z>GinI+kOAtSOl!9+qQD*G$5`JHAA0Mi8{;H5ByH!`?URVK>cFCblBqu`X+AFf&H$E zz_)ndDTBrQdHI(xz9-=#jqLNmGLn%nM}0)B9PknPePjfHZO)8up=JxFK)cB(Lse@V z9%wKS2z<$B%kTTx$RdE60LimP=F;aV+CHOEnY3s1$4J1$4PrjNA~W(X@pP1G(6K$5 zx53-PhI%Z3k|o87g5>3y1+D||TT|#&ydf(7X(F67NeyZHmh*SuJNmzf~LY1>G>BeLEauM%lMg;UHI2T7(?|<9rQ`a#d~UuSq1_3 zKmeGB;m=T^3UbX;2nwi@dmHJeUfWIo;ji4})VY(y*d^_JD9<~mIqjeAlfV3*eS-vc z*ik?-jOK0PJv2b7=j*JjG@UsZgMx(f^&1>+vjv0x#jm-b&D#Vy+a5t}><9;W zd&!Qg&g1-kd`dgozR~U#?wGvBdQ}_LMFnp~+h+rpqdsL|S+Jst32UWCv`9CP+B2xp zPxUSdR{6X1Jv`J)x$}M2x%w!v}b*AMp$$H1quUHfK^dSWXsz{3&n~b z1OVV;o^%=J-(m%{C!$)OsGq6=eC?+{N$g*^CDSm=2Q<-G89?jRsoFTu4Pi)%XuDJu z@-H0Dk8_5Pi~xWhExNyZYcpMj$U;D3RGsmL{+XG4^b?7wBYp9UM+8z}hy`^7u+!@2 zK{-Z`ZF#C=LAVh^vS8Z83AH?d4I62R4cJ4VAf~JCV`Z#D03)kN4ME)Lg{(QKHef~k z3Q+oTAh?taV;MpU^`n#_!pnC2BMg6b4B_^PGU1^%Vs2qB&Nr~!v52J#FT+z&T!#qN zha3nC=ao5TE-pbaWvOr>EWRC@NaQkXz8!bpT;L!eG}7T!yel}2QoDRVUAV!8I2<;9 znyr6N?J-NuF|cd*fkCDYCiW4q94R@#X#tBw$t`_qbQA*I<>9UkM@(@Rx%EPp_Wgj= z>)*l!h+#xg7k)d%VSq4vhut(Br3!3i_zoeE1OboHx2aRJGVOS1Kk|0ln)MxRK4~Lb z6NEE{KScn`DdRwSM!`93GlB@T)Ezuf*brw`#HijLu#Jw(?qXEl<=Zv;adoT$1V<<5 zN#`fQ#dbRP?&b9N|8O;~&UI#-Qw0dT3A_KZboBP?>F@kEN7Bjuv#{_P(ov{D$jVR> zT4>wktD0C5jPhgicE2>blU8B=$Ar<&MW|uzB5>bG&;IEkJ$~U*TAKfpbZPB)di@Ly zkAOMD%KmKxj!o)qj?`TM*n?RupjoRkz0;#Fu;GM7g3j@)JfKc^yW_%2vE8i}p&B9} z#A5gATYudH$7v^_3Qb$K@TIXmNmHgqnjvuEYP`7m*&wDPN7oU%NTOAzrXu#NRs zC^fjozNyjOewj4@;DhcuVj^tV@#kHR9?xgh%d*68`x7B~xed0vh~RXaH6Sd07%BiO zG*m$%QGPa7TWOO8eSOZtX!4h9!i>yO{I|afpLm_&w3pnQr~SCdqRxPZ+DRumoWO_> zVBa-lfXX}Yn)mlJ*2}|8siW*C-Ks)i2`2rJNYRf7H>tCN1R4B@AzTq6`b~zjK%1FA z{*l)~2qxK?fB7(6q%(0TJ_p|=Tm@T{`3H%2FqI$auSZ4zupr{gh>jy$bLD@X`C9k% z!@~oyy#cToX=$t(!wGs4+q4D-gkUuxR4$4M#Pml-BjfcC;v(9FV4=-_JAM%+stiPY ziH58FN1X09v&hM9^v^CMIT?jl_hvko=+fa~1Vs}dQSph)@bZm2Od)$^7T|Gb!qU)(^qb8#^mi}@$DL2#Rxv(ez06+jqL_t(- zF?y5TSVA~$ML;ssC!_e5z*xikzKmVdjni}K_n6n8m_mn)hJEgh2e74nI(L1Xp4ehP z`L2h^p%r1zdyKLzv&6`9mZ4qXEp{SQrw~7M)h^ z8i|Ha{P<}Sp}pC6u56s1ZvitPfI@zep7}w#P0xzQ1U`_^nD22-=9|qlGZB}{$&=1F zh!>F_?+`dS{K!j;GC8JhyIt<7XKUC73+X5{hyKAenOwI5TuN>m*UzQJ#%%fmHFbC*|X^;4YRr|6mg%sv}!S(u?D(Ygu%PAlyq z7`TR@!-hb&@j0fi*RIpQVD_C8Y4`p6=}dn;{mh$xlHNG~Y8s>dU%A=mf*iJ_B2aG8 zDDML*2M%*`Be=_fPl;(A&w7yIyW-qydBs8dgR&7`F~cDqE2Pjxtfug@wpCI4G+CXO zS7_T$a!LGEP>fbVs0$`f{9+W~iix(VJ||i;JfnY=K;B0g0%={bAVAX2O^0h`Sg@&<$~y8>=I5eRd|DyG=ck#(jk1C|t?E%Kgd$+8Gxi zM*jpKsHqHz9ZmtjM-u??wr}^+)fFzCs^8<>jlHz7yvl-Ggz-Mt5_3pXD+-WPF&l*f zrIsw9L}CYj@DAata;>V8uiR&?>u8tgn3--xcQIa+V?R0L=B|Wi5U@yw_!&=fNJCi> zyiL(8kl}M5v6&AT}81`T_|? zFlV>r;hEiG-W8LGv@3W0!k;+Xs`h}zyo4-%u#3yTN0$atMPA9)S3 z7qbvm2FiglX~7SOe4Rs;VC$b!RlwUN zN&b>&!O?)pQRb`-us}YhokK3t{!2j7B`;>}alcRfdu9W3{|JkocsX(DuOJgx&a_t58Z3?OqDSJA^Cam`HIq1MHEL&iZAia0~Zv+5ahSC5eZA z<2hjpM9nZ!%eWVslC=%e9`fZ;01VtPJl}!$SlVm}oNsfn5`9!bCId^LLZJg^xGanA zd6ee&t_R2RBWl`uM2z4eUQ8${45&}CJ;gijiDb9I+2KNZ)7v=FEL`W}rf08t@Dfuy z;IOdxn3T94u<`jLX=uM_LMrdWvB#XZ$UAPGo($;(j?7jQJV>Qg2aj5||=A4mWQjRB*z5CVjiC(2D3 z0|LHPSE>n-(5vDPvaiq}jiBQXAUqprJX{P=hd|8r9r-H&NH{bGR4CBh#bpweS<+*q z2ZN9qv8*rs>1U3lO^!3iIS)bvz@T7Qi|FUrUp~{AOLvz%-HyXi(C>DsZ}(AKr!oXh zhC35hX`Zg3Ip^)S{^2O)wSDtTS4>VL+ao1h{L7$BhseG;Wc(I~@}Bb2ng>bLr@&$i zrZM$6CK?x+STQ!UZJ~dhuU_F@xR69-L;XZ*q$!hAeeEGKK3SXr*=SjzT8DPsfVYg9AA#+&@ z%o!TnU2(;g2@T?}68a|gH;Hzi=N$IAw7_D;)i+P2-3yn~D_7o1U%PagQ}9@`fVN@) zUUk5`n=~8;6{il{&Q8OhR)ERTrhP>%irY^WM5)1OTkFqJXSBPc`27w=2-_NX;~`*a z2kX`1VB)Z)3Xh_1X6Ni(h0$r+uhd2aC9O%Y!&Ni!s1l8Y$dhmd%y`rA2Gy=#Ms9kJ zf)3x}L-bQaQ9t5{GGvo@3VN|a$H*oU@vC=yaQ^`GZA9PkM8>F-XiwWrH4zC#zqP+w zG9QJ{COFhi$2VGOmD?6?pRT1hkDf@MXVJkL(*R@qq8bD6syy0%giUEf(cniM)F0tm z4D$RV4c`w8oHObYj!^5%;9$%ydX?D7*xl~u>3fdiqc0i64kM?aq-lt!IC9w=32~WBc;U-|oX_#efW2D>$EyPE7IxKbHUY=)O7Dmoh zdUKt%bZp?EGeUrlK8%D#IZ;(H;p_L);Q9H~y0noRo?#Eizs6O5|M(|4L(ZVoUoHUe zs3cx!Aa>Sw(y5(cy2JwK`m399WfoUHb%#=xVQtt(-!wKe)lV2i*VC6|_uB9j@SV;h3duwt+ z@oQGN=x&?2GDN;*bs1%#5DQoE9HJin^R(R&KDM0(I1WR%@F4o*>rbXrx7Jf_{TSye zpuxHNRkZAXnrds;N#oVDIQR_dnbV{;E4#Vq?lWzQo9(S|cLLbBJSGG{Q*u;k8P)x4 zr!&8~kY;}N37FQ^bmHn6)XH<|!tQRmOMiR7aNjcwwh>TW*mevB#u94*+-)$RJPML+ z_>|i%qxncRM!vj-&Z!Zn2apW;!VRnL{svCqoN+&w!Cy8W{;9jhUpu2vs3%~1EvHPiIrVdeJ2&`DuOB(X zT8$+Rl4LVtMe9tZ(1?tXxy&STz(`)DuVpR70Z%@1yHs%t_)~$h{{e^mVdi0|9S2rc z+QcpU{c{j`&49N8B}5 zdW#To2oUc;7CRjOhY=|%*0c~lge3-V(K-1hZ!aL~q2`sT$M!42itnZ)LysK-hdO}i zm{?|7*{;(8Ljn_jJn~{TqTwFN0gu8?cXp7rSWwh$G2*YcVnuTAiJL6ILJi!13^Kn9 ztzfS*JYe?>ir<%yq`}X&~n!fX6C)00zi34Z^TppnoK%S%0 z0OPp~bq*dmLSsB&S^GL?-!HH#+;j1VgemoG0@{ULHWPIw%o3>TJ9uo=JMQ?E5xG=1 z{|Q8-6mLnzv?&BVaAo3Q>Q=r&lr~bT>b-<3@x&*mYt#yvp9{|T%sV}dY#IE;>X*j2 zy6HeJA;{>aKRhwMtu7asO2&KO*pT!&ay}NzwMlo8TlNyC#-+zkL_+R}`0|4l_T96n zZ*zrP0e_1+{$14Q{WN%rdjsBQjX-*ld}RdYUW5RwVRZA+2#LmT?Lw(hdms_yfcx}3 z&v$tg5ZS7R)~9iCA^jxR5&xH~@1~V^I_c~-w-X{|zj3O;Ay1^mf-{-&@}^rosTcZ@ zqH~<JSU#?4%%`L?Jq}Er56K=Z3*1a>5K{{1Wyii_(TH zkn!qWO#E^P8x+E`6{Nt!Y&z;LxrU!i55Da2M_2&UUHI9S8u- zHu54MM8G^PM^HGz38M&EjP{atE=Tq;o+?vM^^(AvT_$=}86h-GyJw;!p;Z0O>22NF zSqKmwY(wfa?xyuGyr0Iuw360Iz(Z)8Eb1DbV@CQ}gq2S$aX1TQ?c7W|Uto3jm(QhR zy{FQza*x3J9HY|_LI8X!1b_lE**6*Ssk&@46>=JFz`bRzCPfZ#ka_4hS zGE7y#V6_$lM+tAa&w;AjFQ?^?NL9wh4Xf=`wYh0j-Uc;@M7nf%BPiIpNNu{#4O=Bn zdyxSD7Bd^#lZ32)w^>G_df-snDZ&vb;4mz3M+s>M1%TM*h`;m0GJ~G?vXhqg%lF7{ zfqGgJrXAF^X3lPhx!zWKfl~u_(I6#uQ;Z*c9TtBrjh9ZRTb*ap()y?QK1tTxHNZkX z*KCkN1|`4ZTad#Yi-GocLPyDY$!z$|jaw!$9R4l{SHLuTUE0L+8{&TR6U9vvuy1v_BMcZuPf^lUf>0GP6CAU+QKkdLrb#(tKWyzdNm z_*U=;kzhkHs=pvHC3<$Fpn^D{A9TrohLZOYp2UGjOPHekL=)NQC;Hr4WS0eJIy`9a zrp{O9)5_mIo!XyWOe2K!_BMjrcR9HEcem2!cWxmNdI{0JG<>F$-u{`{^v(KKT8RTd zC^riM*H@3Q`v5JTl}^3~%zkkXIR7vo8Lvnvko{pX@jIOF!-C}+IpCA~mO%b6fR_9| zJkx2s(LUYo2LO0PJ{}nXKt?iT3(_DC_(#%p)NK!g3))$uF=enkfsp#eJU?!+pbxqV zGI=eg#6<)fxZMi^)n>@CKrduDLAnhCQ?6F6ZkVzP+(bqAoTu5ExA;lkj_}PJdyqIX z2($$T1qY`lqz3kP34a6FHNpI=G;>~6{O~J37WsMLT$^16UH0UU*&^nMtIi=V zZAOXSMCYT>((ytN-aWPl-dJK(?%BL(e3!T9)4dDz^zJWk_zWX+x8kV?W}QCi_tGx= z_!C!(&U2lw7rBhN-+XJaoqmfzV{f#PSCB_89B=r56um+Ivm9j89~R&CsjQW9lsa_EBQsikV|vFhQJIFe0;}k@bLB3RwK*n!Bod$aXJ%i; zHHdzA`aIO=XSiuk9HgOL@I^nL+?a%N>V-cw714%zRpH|;ZUSp83m*LFnf^9yTDvPtp{ib&lPq8dp-Ta{gi(0Ml1cVr`XxT9?s9-W1$}aa6pg0 zw*Xqtz3OZS%RNT@&8!n;w;$y?33vQto(k|(U9L>s>@&LKq!z(`7JZHHV(dS*TlfK? z|JJ_7n#-od%kCda;Pp1K%It?DzWo@+ZJ7*HrU#?{hF^v=1b`|DPXGxJj3P``A8*0& zJJ1&|25U1#)`Rg!diIN$(-00G`ndh$9wYfb_&59MOP@KHPJQKc>T@R47<}pOZZj#v z4UwNjYWMwby(xzlo^@s2` zZNkS03hHV7S`|1KS{r%fpDLVbW&eUB(feq#ebv7`)ksx}}KWLB`#xFT$52x#kA;Aa9j8p}~dpN`aIkM0cn znw!jf_n=HN@(5-q2`L|?6PcAXQ{)R(ewBS767I$hnyN$bBfl7{0)yRSf^!js7-ohk z!KXg}k=y+sm<);6kFkoJq2mggy)MjllUsG_JHSy9P*JxITGj%{|t*Q`!JS{@xAC?6LrB` zK&wIogG0!2DrNxT_vE`7=KftU79EnZMTw(saZtQ+W3Er$mMil8xpz2r_acmSw3wPZzfZNl zL0N95-ulaFZTwW)JAu&pIhe}JE2+K2v;lj-CzWF*ntQ3Jz%2v|nT&?K+m9bfg}|`{RBr==h9YpAzglOD?Rr<*BKvgF-b5? z$2j?qIj(4%D0HNs%Wk{a@2m$B%yq!qZ}BxOzIdpFcnhj^Wq5;3n;_%_upgRgsbj;~ zr%FB%t;8*k(+L%OK@`42qgdP{EfU~ArZae(jIhO9J5N>10k>7HTjn*|vT?y@6a+%# z*5xgPq-`a$2uv9J6G7q|Nmij^`^KQMWMI=Tyad{q8oZ#LLI(>GBdPepS7F zu9jVxWyFcLU~Vfl*={z1;Ha|o%Gx$+=N5v%3Oa!Xr^a#UB0JNz(+0N*-ZlPoxemeR z_C{-UIN-Fi5z`6OcN3=I$$7Cqni5+#RaNHLrKo&Gu_FcQ5BF$8!{b&pd#*>4teRoc z-wlJTkqL&?hJ$Vz+a~Oldua>(N}l_HbBf!c$dR+yeMda#eXhrJ;rEojRTan;RvU8dg+U=T~31?R_HTzGq?3M z>i@O0!)os3;b&8O^-LQ7`^VGp{03=rLMDqlJ^qy+3RW_X+;FQnOjLxpX&Bs^o2m8+ z_+Fn)JO9_6G{3`o1FjUVbN%l){R-ip=4qoLc#vivaH|{(T2IX$Pj5A^r1RIgL*T;E z^ajG=UaY(ohsYrEAlj5E(D^m;IMXWcNDlYX&m&C{YRb6YrM?U$I`?hbOaBOyV`~>SvowrGJdHqn7hyg;(DK+4`g=)I z=jbBSKt|S)$N0t#EursG`t|x=`em-qJk{lZX4fr&AeX2Z6X{IoGg*=7m zeT~jRkV?3Nu7CCQVKwmR2mr1awsTn`ahKPANB1k6;s##~q*vngR<}! zMr1IZ3U>XWG^$&-XS=BTIN$@qytl-@eD;+ubJ31>wWu!lpghkJZ?YG72La&vC(m(5 zIBB2;Ok?(TvnX(v3hc2vV2(TJM=+g%5m8Z&TTFa#Yt3u}HL^$2GS9of;r-VZNQZO9 z(;{cne~6L6c>qk;#h9>>IHXA}3Y8Go_7*BR6@M}pid(4*gW1fn2&}4$(sV|ZZd!ZU zO&ftAq%r!#8pHwT%y9MJ4{vfYqKpLisnV}r#Esy^dE#GC!8{`k)5H>KX(xXL%e-45 z`w7oJ+zJ4=Q_V7rfRQ^Rwh_9_QVoF2(=BsrJLB~3ZZCcA=1w~O#@)2J^io>uT%ta{ z1rxBGF#pC2sm|Jr@fkBlh3|<=O{Q*?f=p0O#(xF?2Z7ez0xGEab``B#XJIt;R~A#_ z?(ww!8`sznc!Ho82<$e|Kj7i)f7{(8OVoUm5=s4 zVn+X-zIJ%^qVUn)m`4_*^0S{pxEJiJl|ry4(AB4PRX@9 zHQYyyU8WT7@oc5%nhWWRbdX)v`J@g|lFeQE?9MC)HL*rxs;VNZNt=-6M{Oug#i!TPX!pogPG#e8zSdgh+;-kuCw@np^icTAaH~yub5si zK6Yj+GG8+y(REXU;UdH)ZEbsd+GEgDJ*!-8g7})W1zsW%{{;$prlv3->GI)kVKSQU zIMcxZ5M9HTk(O!ZIe*=#V=}P)V&2z?0Sw#h80qa$AnU0WxHx7D7f185{qjq1vJ(I9 z3@ZM!>F76pmDtyb_!-FlSJLRrkyQUQpWI&&^D~aXmsI=OjsanQhn;W&9L9IC6V5%I zhN*F9!}F)ogKvK$4exSh1Cj8ez{697c2F^h&zJeFb0Pvdg_t>OJi?GI*4n+A#&$Knu z+6en5>uTwv^|Xoh?iN>L$MT9O2q_f+MA|T{8DKHb_vlZbnV(M!!%k|!#9OZIKns|_ zkq&}(Ki!6Ay@pos9Rz_!+Sq+H9UZ@#E^!CPxQRvxA^zUlOnUq1LVD`HVn~(?h1Ih9 zgzyUd3R$+9W$_V`qB+ShmEY`dfM{dh)<)36z@;2+-_P&LU#n%EGGs+M zE8r;~`vE>+D|uG24)~WeBFvOez+K9MyV)2frkbY0eVnb2i~!&cSm#>f$Xk)LLJIMu z9I`6jxE55^2Nw{IGI8{FJ z^70r*UIt`r$^qz^F~M8l{{lqSrRa~44_JTl5pieL7VbT z_%Ku7rfilv1rM1jfNs-%8FsZ#xbtKC`=@Zw1h84OH1{|sVGjvF`!2$k1oEkIq!)FVP%n+vRynlmAcc(zi`;%GJwa`?L$T4kn z7E;e5fQ`5|q_#AlmL6YCqkEh#=uL#_b3AYGyv?(bYWF*-!F0sj3oE3`NY=t}kH8nc zvzdP92{x0murUOjQmK>SG|fXFaBYWuOYi7sSPN9vkM8!<=#o_!KEs56WtT@5K4~z%8 zB^jMPrhL#4?Q!r@f94`g6g=)dd~ z02aH9ixq|0eqh}O7+CD1hiOz4;;()_)-~|R2mpb>A}L54JurGbAMw2}5FBhRIszt$ zJ8>OeNQ8}JIzh+6J%8IVyqPaLCY~}f#{#jzE8;}#oMn}csoLM|TzQ%RSaP-jr~Zd& zu-b1o=@w0!j4%6pK*5zCmb@ttR1DQAfd9&P*x!SvI|Esg8 zZ=MyxdvXFhnua{uD_bmi7ROXFMbksfop=_v%PMruFCS`OBDuriPeqSl+MsTMcDRb0pR7J>pq zY(c@v%qIoMF7q#i;&|WYGc5jNuYY=AK+gyGGS4NR`{|_WXW)I}%8_(`cAd6nZ3lYn zrQ2Jnc8t;gJgRB($wENIIEg6;rsgb@ZylolPQf9xy4&W43I7WC5-0b^945jW#l(vt zpm-6zGv4@J5_g5^ zPGbi+6X>L~y?;g4XH6XtCqW~`BraJBd!!|*R5+Z`cX zZFi5GMmJKo3zm3XG$%sV7B8~h<`TC(BD5g{9-H~2wAy?*y?=Ek{bu7-+US5M2$x$+ zoX$Ad0k*}oPCL#q)zqf1^u;8>ZjSOr!%jQ(;1HTZEzvG>i#i|V3$F@d#YuMUDWmiQ~?hJc;mTLp zE_=dY7VK`Axu2e@ZPEE5*fmxO+reR|j)vO!&#)NG-OAmW76Jy07p=i=doI035_e_n zvAu};7x?m6Io1L5zuO2G5AHBmeC}L&@BZ~zjCiER^|eG9xFFDIRvgCP8e5f0U*0D$ zGE6+FCk3x2ZS7#G$+szt5l)y!&4x0oMQ}lz^(VPt0KfgO=&g$HP97{kYQNvES`O^qEG zr>}FZ@~J0NdI>e}|MY8VcFYR@P{}jbvqX!wnDt z>Hi!V!~#L=cgcT7^$c0aWGQYCULs>~z?gL=@O<-Mv6l#!{R~sW<}d*+*3bya$W28@ zOsV6r^P}2b)R1|5wUGmISHH*Yfuw7R;({J z-G>0+JsC1|bWrTKr&RFvUW8*KC3r8>@utE)>prd>olW;xn-h|%lv!j~Y8McgeN>E1 z3w(Wc-CUzUFEWz%6v*CyZJU&9mg$$#(mIz(-C)5n3xY=oYeSCh9=(umZm*`7=3Y-v z&74f%+GZMnw%(YVM|+6CW*(He0o|KnnrxeFL-@2l6rL%u2UUfT$rNB8p`*#RYnwzA z$%YodrAkN833bGr(22q{t0oPYMow8ercEaWGkn@arSYkgz{fd9WuP?&E{ zXBt_4u#`o+OgBqhdz2;90b#u1Yx=l|0l~6c>sy zvRP*l;ZOhKm(#o7{CZ+TMcTf}R02>sEg|u>bHyl(Tw4%D^R`l^=iH(LQnk_ymDCa+ z?IGk){)od5GE20_2m+}fz)Vf}DR7K9W54dyjfCECW^>3`=Hj8fd>d96ieCj}d>cks z!bF={_MEQ)A|1Z>>7DRak7cT8mxA6brzd(}e3w1@eNG3O-yNlu`~CD;+NS>eN_waB zdRq9?TL=ixFpY8s{?JP;uI7y+`~@JmEbjoAp9ujJBENYBz*XVIH&rAdKvix8iGXSe zSnH8qTuY;OH<2$))N~XAZW3oNy->fw5kVX2rCTd#spmPR4h_+GhY{sm>Tt58xT*0s z+OT>TP_jFFuw5!yWu9G@WS1qW&t3FQG4VXlm==% z`=eDKMK>Noqv`o=UM2m&&1rZ1iuS^A8ifPX%~KJ4iEBJFyJ%0i=67-FIBSPy(k9bv zma&U)yvkNdmfWQ4bfHbvimbfCE(cTW3MwUKw5)yY~y)8-J5$Qy|?yeI(>H|9q+8C?fGHasd1hp z0yy_{<-$ilL1;$W9`?vUJ}s;r@cRq@$h5Y> zZV9nXNX5N6b?RH7D~j?L4#{6u1|LGT|FXdO|DJCU4q8y0jh3|Y*rJuOqfUK>s|2H% z0|Ed6Bkbg7Dqv(##hOYm6CWLTB6vX|N6}@pjW^P@C*9enV#I5dD*5;}Avu5y5xc}k zT5d22nej?^Y!fqRGm3z2bUE&Gi|K$mi+jeXz8eove>0M(bv$sWm0o{&DZO*yL>erz zM{E8bX^qkNt6tg9gOq~)M<$Vgf>yXQh!R!55-SKENd#WYLK zWCDKa@ZntfS&pD%CV@SgXewUumt06jL_J4#@z#?_g_IaAVAeuHjzprl0o((i05Jps z1%Nr+oGxJ;&m}Iy^uq2^I=?lOZn91D>;L0FP5-VdI@1%>&KVX{at!Q~d~;Q72GR^F zAa!uHp~eDe1kOU)F|0J|Ddx0vQ(6is1$TipEbBkg6C40_7QDHXkD|@T17E`PBGW*Z z(iv8q?~RV7^Xw?`{K2T-nn`i=`dCN?-#~`e7FEHh4HvKbWwivcmfNq?r zbWB5c!S-#s;cwsJTjO``gBds6mKt|`ASHT3rbRWLDtm$Qt`6};I85I<@ErmGpOz(R zQ#b4d2J;I)+SHRny|O1(al-7~v)`mYwOM>OTwO}DZTbVWUi+fdLTW3(G0m4+57O^EcO*S?|CRJh?|(CGFK~zyUAfh7aTpa?=Q<$DV!c@Zo(kHW zZKs``9kgy44uz*&8GLJWugtUnu-_WRcmMri0>HleRHFOQJ`!8>OWHYDK6r2cw~8AG zZKKd0y0|j8|3{MHk60p)jsQTbMJKfrV?@kpv1D50lZdI{nUcbY5eqgU@TxBjWE!#X5O`V$0dWpJjyP8#%)_Am95S@Y;c^`c5n2ebF2m;H z!^}HF`%v*;lL_7{*kltpMnblokVuG}t{V0GaXZ{1ZpU`|=>l4SHM9Why}Rjazw*23 zsq5=#V@?y3Rrsje`|R&d=a~j@K__X2+F!vcfEEy*ENNg5?f^33=57I*ztb=X8?k5* z>OMx0kO|5RWAd7|=Y*lucnP-i9*Ybikep7y^AgVnu)dql*Dj^^>W{GmVU}Zp+Ud&d zO6u%vq!S1&*AYyCs$!Ozp!#j)Pcu;4I#l}9fjB+!(_Y25o~GS_oOFzr!w@w3R-~_& z-~7qMLKTg`!w%Wt)=oP}1jS;Vyb8P(41t|5^gHv3#kvtUhY{7y)9~@;JK*sR5LT-# z*|f2}(wXxwMlRxP&2jP2p)Db8@@XRk+(PghFe;yG&T&Q$ma0p|n>4EeV5#i5 zJ~c52h%LJV*66qwz^3h?1!Dy#ZMm4b93A*6`ulqv3UqtK zAwj_OJI}wAe&XFr>FmaR(rc!f{zCMf0TQ%yNPKoGa^&3(XdY_dieS9l3#u&4V_ye+Z6j|kyLmx*`CmB0it=^v3R;-x=sCB5*@PMMfsFD8<%J`5kcmUopz_;3*$^XZJt znfDiU+b34i9=(5;PTQgLUOwGUfB3IGmo7V(&kFJJ98Et!-0$zEb>{xx0^ke7Bk2@` zqcyvgX1c5#7C=<{4k+Buttx#40q}6QW!4VVVbbd?UJ6L;w~EfilmhXsir@49kCMi< z*Dt2UXV0Xq^{upk=AeV5;o#i4{Mf~zO4iD3qPB!UYM0Sd1X?(glOhB}R=L@uPULY$ zRhtdHW!~9okbE9}CR9L*zkF~g5kq@XMs)>jD>d4VH$e{?EBwzfhF4Y#q6xr`U_eF=cH*?KEK)nZMaWU<^!qp7-1FoEpEEZq7W;UURO7=IH0e$Ihlb!YNdaSXUBR z0*-I%U0e<(;x~_VFBA8z&{o>UclcM~a^K47Os7@SH!0JYHYHq1GxDVZymAqyraM!$y{+N;$YriB@V1^V|M41Mf04eI|r&VAVFZ=~J&CKpNV(r@RG zL7+#&lgNgg&A{TICx>sRFLA2luhw2-*T77=$~MBwXm`$kgMQsX^-uYCXY%HWQ7zPAwo+`0ubc2Q8R-HuN0NWH}*v#Xff zEbQX+QQRfXqy;fOU@Sr@AX5fAXO5=+LOZQrypfJR|2Rj+Zl$&3b7_aIgEAOZijLe{ z2rZr~Fy=Vp#w?4CDj^`4dm+S9V>+%x$oe)Lg@TSFS3h|CwwTVxszTSpKMAe}5e>;B zZa@U|X`?^?LDY3OIgM@_zl%K_rho9LvJXZU1PlkOCX>-BKH&sC(t=@xkb_$uS7jU? zP`^%J9NQYDb9?Qy#HnxZbg!q4!A;fyG*GjzS?d8y2O(_!85RR7jKwe*AAvML=xX^0 zVW2=q*`p%(MsQ3V=!7oX9e*tJ@^YF{y2-q~C(OCvb2$zXA~6qgIswVx(4Vx0x;d^1 zK226yVoN2-Uge~$jFKnJ? z5cyD!l7Id#>Eliu=a3r+%8vZo;xCDNmP3>FtigaAOvS9IuvEUlu9@00(|1oNMH5Jy^2th+Fol|KI))6$(DAdOY(j!iE>|>{~ z0f(cT@1&>LI@;!-rw&5l(Q9|uFmMg6TOV3M8*!BApt%jap83E!0^>qlQk4nm^ai z#HSSywc@WZS&jSE?G$m)hPVobI;zadN1BG#ntKtAW#Vg`64!T5Q}mxi66Y|Y5FJ(I3S?$nLuWP3KyK6VPiE#2 zH`4WWFu#&lxI?>1Z698P+7nsP>}Yof&HjvZ`aMSfP39gs0qg;znZ1=2=9F8Z4T#a8 z+l4qp1eTv(M!Wk$XONy_)z3+9jTz3T+CnGIp@VIB<}cECpAr9<`Nk0%fo1YxToD2Q zdkhgK075kZL}F3}(?zfvPRyr{3juo%n1kHm)H4nqVsWCX?=f4W`bho_MwpAtVYcqA zr^fPeRwy4$n=Ce*WmVHWs?s3@&i&GLsB92F%59dA*r@ISl#1%9>J;9_mZpZPZCt!$ z#UqW~dKJ4cYO~Z$Ua&=gFu5IzJ+p1wNfY>xt&vw{J@B#_q6&Dsa^LSzDdQN^5rm#p z3w}cFwlG&)!_#>aP|bm%C`gNy-JjTkKr;=q*Y0qY^84K1-eu|ClN`q*e2%KwCb$Nk zKgqXh6ajD@0A-~i1q8bdz=Jh31V&Xl3Pat)&j+;qD5gc2K0-5c51gd7(lT{a_q=%+ zVvl2m8%H^G<`k{}K2t?ZKR`Np&huR0c{4pd_-m80CO5h_IG%c& zqX}kMaSn(~K{RN?MhF%%9GO`jecOIf=Q0us6a54?D>LkX9SdU%_*SysJPHIBG0eo` zmu+F5)RqEmNRzlyUwt_n_-(~S|B_)Fg^Bbbd+zP7vIvuNDy>hwFezt<3bqWF-+6Wa zfL37#2j}tEnKo+x)R=x(GaveBpK~|1(8Aqej%t-@o;Lm+Qol=@!&9u@^|cElkw2 zKWYZ(Pn--&wWSf4lI1aU777I0;^g+k4NK5m5MPaxXlub8`AaNq<5;P0yV&`xKl}qH zEVr62aYEXu4~hE@^2!^a`KB##_bIreR|K>T^`k!OXg#}}n{w>@OKIukDb}&jP6$Oa z^dT=*6CQCR(jc1WIy@@okr4o*RUg(ax``S4uZ)O%7q7}Y!uTV?=o#+(O#AEgVJ6~5 zs>A_dvH^J}k?aPdr3U|S_eWOXs{{nuWCoSVX-UNFG&mlD$@rNm(&3%l1YtEbMryYp z?%#WX!#N_F$v#fr74ng1yd-IPl;#)*eSV67mwxmt=6wk0lsA)Hb zDq9*mWhTDD1``$*tx{f%b_4oM8{EfBGimGCVrnsWd6xraX66^ua2Mi2)CQ_j4-;zB zX@?tIse6;v?J$Wt_Y}-6Ek(J#J*CkTf^h3Z#Etz*=f z3SN*W0tk#`BqlU+qzSB3fJfmDc~9jti85s||1jlUeOf&-w%o|Pi$4*VRi*8mhsKe@ zI8g5cV~sQ1pWGd!1$J5t&{pg^THM)7Peb-kzDfAfO(Ow zfLYN}M+7w&jg4XKYDsKQ?~E9MH!>^hxEB>4*5f_JX0FOctAV%P{DdQ*Zc)j<~v-DX&XC3LK z3SW6eCReI7V9~qESGYobP2U}{=ua&`oG23Knozd>%l~n!QBhs)QrMK?-{n{ z#e5s#Gxs#+L1SSsc@Zq|y2X-x{w)YYALOaAn4pi=Z#1|D-rPpX1=Gi`iIA|(je+Ai z)a#VyC>nsZoxAB>?g`k27QB0AEq!A1cv{&72U!QuL+I?yY=v;FpY{64zmFy*_v3tp5Ia$}dTL+?qXgYMI#$9?9X!^G$YtEyg=j)B4u1o7DMhJX4@ z;u%29tc(O|(qSMn`D_3@O^;yuDWRK>DnUu?fTMeN`kQIA#ao`s4T57H=Sl4onRq&fNiGsZRFOcC zd!lu7xPJUL$kFArb8;!IoH~JemGH#fc486cJdXg-rYs&7(?&DUSXfAVn`jDH5VZh- z8_jSTGX%2_^KlekNBXy{){%_C$Le&@n>#tbq(AIKgcSgSSL#MNVXkqSphOz5rcGu9 z*+qu&Fk?PG4>iD1j6noZ6G95`M6?Q4-9wAvD)6XRPIO~b-)GL>ogyo2ySsn}W6bLQ zO|&jIxo-F5UL!pN^XH65NR~^Mc2nc{QmQ|T+J_NcWH2FIWJI39^ktYZe3=RxJP;BQ z6nQ(xtw5#F=q`yCM8D4(0PdMduYnJHwE5>y2_J{(FH*@3v?hJvbsa#L?WLnVPyRz7 z5Z}_194@tr*5W`b zYj%VyKh~Kz?xPXss9Aq1W6#NLQsU@Q+Q@jyJ+iDAp&H+QBfjBL2RUTYM#!lW5$A1NQH0Q1uxaBxl=2UE%FRFEs5|zA0HL-$Or&&&=2hs=qP@HM1e@O z)SyDf+=w74$YtYZ#%hC#aQjUvYly>HB8`KMZTrU)NIDg1Kl&kCU<@hRAy9kr8L{Y4 z89u*ad=ktxPeo(`HAx5p6tI$WFnWg)n9ven^{8VztY_`3=A30A;4Gchi;=pwchlM5 zyOQodem32F1r;L)1r1psbY^ceeR>aKz|_f{tMVZ{CWrGFy&%>_*siR6(B6Hm1!J%z$<WkE&7Ubf+7Bij?KLJx~ZT!H@#&X7;&&@(zt7 zq|iQK*GZBrLo%FkLXF8gOyHve8-xZX*kqMtlg4tPz07}stCr6#%%_HX$0^GL;M}}_ zC;dD}w;l_H@iu9)7K2uyGPtff~o;wgZifry4_H`R+}7k zAv{(AZ|ls*$_{>pl@ew*{P&Hujj<{Di#UARx1v6HDPW0j!OV#pULb{QPdpPs*+^r5 z3fc|>gf9aV!iq3~OrZfFOlY72lE{)Uq(K;yPyndQxX{>$Yjv1?Xjus3IsNvV8k#A$ z2oBM3ZPP}JMDnhR4FtKg#wAPQfQhN((O5hmjpYuz!UTQYfWaks(N1;RtT%d(Ynd;l z&CX4(1=xsH`-^jH>B!OJ?B=;gew;}_Jg3x}?&MH_-)^US^C!~v)f4H~%6kaS^hX+H z4WVHchVKpqcLXSH^wcttr`nV-z+<@tOge;!uB;Pqwq3)cAfyBiJ<;E~k00K#y8>7g zB7!zkhFo}r4`~u&Wiip;h&(+=(jIqiM5E=9K~t@ksHf}}ai+dYn!bn7f?kcSkq|#c zQ!%JRU+l7eqDhBTJ78f-8X3+HNo+ddkBR+}5dbC)Ug71W{%Nb2V-I9uaSjBoLFWtg zl$?Qxfi!le$^^1u*Ciy;N#Z#`@vnk^-V9u?26z&*mG-AHf+=g@_#`BtJ)bTZl{@#EUgrwbuBG#aMZa3`G0mgI$kNj<+sx(~*vczJbCC~jGS$F(1Zx-~IY#oXBl%nOF6oY#_isUb8%%6ElJZ{jJ}abAW(FaVu^QI5 z^&%72Db&lvk2jUuWl!}iglhnyQveI%OvoNYIJS7Pi@|x>VkX4J1pCZt}F>}KtFdRH9o`QI`(tNq4G*Sax=>XlMlNpWjP>zV*~+5h>mb#Eeh`% zZgtvK89^XB2!HpuTUN%jiu;{~L3)=_{|$zlJJ&|(v1`qA4`E~Z*minwMUuG^u(-sm z+j%kd7JiSbX|JVcSort7+jE@%@Dj`pW<$T5r%kufdpFo^;Cy+XwsFcK7(DsQKz(8) zub^o)>93ppm$!D&;Gw396MlpWA_C0h@w^$8ay;r_c6t2y!NZ zj1+ny0Asu0($D+T+ z9;$ydJe)DmL;Aa+lo1KRY8$KR&2vwuCvUuyUb?ZHme4kB&N>A+X6+Db8fYtp<~}%K zzW!Ik2LBK4`MvQH*KH)XQ6UTeTP&LHOZd z)mY>bzO<+Qxy>_f#R4nJI;0L72qSY4x>}HP{7&2!5+wLj~YY$pR#*d)R6pY5OvkPnZToM zKxjD1&VW^bzr%{~cUec$V>{WWc4pE`cfk{d!&AV!!rc%XqjUtl1j{?A%k$hmc^YBy zW;(WcJw3U1GF{t&#jes0)N@nZf$8>`3K)Vnt|lL#*(pqa@&m2&57; zB*J1BZ&l3?oW=ofZ9i%nycLfKP2LVDTw~y>Pc;kM)WZzZUgMWI)4>WMKEqBk!OnBo zm?;{RrSPN0NYAoLVVaC&R~k>TE_am^K#wBWZO~R|`M{bUX9Lt&=sRLvNgd{YX>Xh^ zdP&p@8EvsPVE%mi=TBcqSC_t&e*SwL*}VlK)o-WO2kYs=b~mjsUFewHsSx|0U}${D z1K9kB03h79r8{sejwm^OnkSzhYC@}7eC<3BHTIqC;X4?8^_@G{zF&rl@8W+by#_R) zHQFH!7domN5ni;Sp%OLmhc&{XL_Wr!kBk6dCy4n&J9jj8ZgGEg(uj;<_gg#IV;7C> zEqcm$2>^Okaj>CYnkFW8027MqaZHCte@6<%WIk@rl`$`@N|rLLFud#-h7BR3!f4^k zZaJ8MN<56^Jp4o`F;CMa-q2Ar%+&tUW{Xi5!oCUj94`!6`wJ1+x(3s8#Wnj;%G8;!nByLF$0NI( zVpwCFnwPLx?fhp`wSgb~7%2jH@w5z>@>$-MbNCfJ(NAd;m3tEOcNCF}|VRs{7I$P7R+x5@nf*g1dOiHj3PEFNqO*^UWr?jQ`jvA`{a2wU%= zmGC}{=UFV^mblkv7_B;kv^Gk|fUnDFeo4IeCfcU^E0ltJBv{KnW^~z~g#m3|PEXWM zr9a%e%WZ<(BnYGJa<%XbJ4x=M?P;M}c7b)zX#gJ1If^L>N5TX4H1lYj?Sg~wAmaHTByrC zdRJ^TeDq54cRJDVx;TbxvSrg90Q6AW$78MWA<8Z*{}*C*D168 z&uyHn_Xs1LBDbHmqu=%~@==gl!)?HHiugWYK3?Hxe#q2}YiO9bAED*r*sfsKZb6)( z?zhRW&N`3U&b>6)rN3=GV3)^LjtTE^U&9L9bQjZWr_ZE|i>K3*+!*+`F8yMMD@3cj_0c;&_WH``A6DQ3ZhO5a1WFj< zfF25K2W$aAz>a!-#5w;h&i0-`4ZhH1As{2I5y!c%Z7^Rv+ex<|1ar`#MQ)8-g+j?_ zcJJ2GV^5z+OU=y?d|D7YRj01NA28cHs4>Fm$pJU!uMr|zjJn#iA-`1YN;v2GuGH>p25~Q7iiH#g*l_H@y)m%?z^X zXaw6e6~3b^zWIn^MZ4v1or}M;$&(0+$wV!G+4K@XaZ@aGWGlaRJJAdjam2X|Ew*IN zqiTP^V!36`KD)-g`+6DH{c zxB8(?68A$p0p7=2A$@@xu>ve*dcn009S-4vd04h!et@~jG}c@P;d8*ge9q@rpdO4#B6c z^+TK{nwL6T-R$ShYn^nK#l3$GZQpOV-cPq?5F$Ga5W0^M_I~Qlx6`M;_j)?_`rGN~ z7RNs9F(hD6(V;AHFp%Y!i33N$>7rF}VW&WcKx8>8V|#{FM|m={#e<+0jn)ywu=z!$ zR(4*|Ci?C_IlgVGw*l}o8n$w~X9;_?p1su2Sjv}0l+icHQ?rhz4v7&}zFt8n>iAB6&0 za110o6sCmzzHj11KIApY58Mg}WU9qLoZ{1bcx3vXWp3ut8MHE@vR7`?qCKXC$B61R z)a*S5@t2OnK;SrIn8Xu1opb~)aJx5~PH=M2?0h#JC(bg2qkr?gG&;vU0qEmhP!wwu zAS&!2g7mo$+FNeOlf7wC*mlz%XpAg84c5o+>1zsX1ZMP03WPV#57;t*!bn5=@ zY`VhLr#p`wO-FCtj**qeuR2=l!}uJrn~svX=lBmuU*^9TYEz0yWm){Fd|ugD32G}; z^Jiu|>lQjR3H1TH9#~hf#|UnNh_w?(QwPGohgy@1YNKBZwn=KVdoF?R1CD>rw|vK< zmNcdrPF$@@0MnBhw7{_%D)+bs8Q5BkG?ysj5Y0$!k0Y01lDDX=pL~0mK68f=IxDru zZRYv8#<{=40YE?X40k<9oKUTM=CNxWU?eq0k2Dd~9??l{Kva@IA;_`DqXrX)JFqYe zjSbAn^Ca3d?prL({0A_*6Hx7EI2!f@cvWNZC(HNJMFmHMle?{j^xBbDdK$GjOF7w| zxsdAIJv8DFDJF8#%;#TCoB!Zmn%j7X(Qo{K=>j zTv`Gdt}lT=Mb{mFpf zYlAi(EzG4JD)2dk-01t{AFJF6vS+7b9ZHKj>7yO#;y0wjx$^~dBne=&;l&lwq)9=0 z@R+hh7Wm29WQOt3By=4#ANBOaBAT%Eopfi7#he#B)t^8iff#_tcGPIs51~7V07C7H zG#DF|S5==K(!X#`Jbk)q2PD`Whp3Prs8; zNZ=jO!`4t^qWXt9?!Y0i$AJ7DQ7C;)z9jSLRSWBK<=@`e~}{0Xdj^xM81eZ!ThF-+Zo$ zWg2$b-3Y8YZZRD*hXBxMbI9*mj`m@{{fn1J?7iPhYgf0^k6)k7bCHe9|1W!Q8YJ0u z-T9r|x5})2ukNbeS2TbIKrAFgkb<~~5-Exnr5T4g_JnMY9k#=c7(2od6aMP(H-B=( z#Q1|}M&lXJqLD?7Xps^r4nTqgK`f2#M(<1Q`;wJexo7(KKQCW(brYm$NfRTr@w&6} z<$L$tcb9X|e$FK^6V9Al*orR@2>1%vIYq`sN&VuqaNKM6AZeoc5nw2W-L)t%If@8y zfkvu-O|~LC1ly>JmDv$Zzip!~juUb@jvoFb^Vl0#k7CuG-n2ZPzgm5(1 zATd$dhH_~eLDP^+Y}hu@3WFD)wB}1+wB{cnht@u@Y?F%O4 zU)Dz9NOL%KZuG`+GE3SXrW$1)tTXrcojI?lBtbs6;TY4)@KnPs*m!UpJlEZWzq_TF z9FgKj4{4ARIeZ%Ei{L{7xR0OO0}+)|ZDS7xEngrp5)1?F%c3!fNHSy&#Xw;j<6HVE z+D-w1xqM5sb*>T(dCme}t+Il&I{rC()E_0^h1PK!=Ukh>KS#}zIIVB0^$%YTU^s+g zhaomSZbw*$uK=5U8U>_nF#w@y&^I7bC<*=t4V*{S>m+7A?JE&q!^!u9HA@%smTVMQ zS2Y{xY}?^RKbk@q&dOaoeEScruka)$n<4w&snhnE_ujEH8+S-T#l8Y)f@1ikh%J^j zLGvY(T#Wo41H(hMeRtN)Y=MT8ZfHRqI6P`VYkPtVCL{J0#C4FjOLa>6M9 z@EOyR7m;`{Rl2O-`Z#^>&a5U?$UsgznpM@rwB5h4X%mOJp6jy|s#|FTYLZ$B)$5Dn zlqF^TLz<0`7GuTNnJmpWcx~7F%oiD;ObW{BI-ekKzk5Oxa!DIm-Bbiw^Ble;@5nF+Y9G^D*qrXYd&q!O?IK zZGw`%RM=m32glOgmkKuU)~C$Y=dE7&9rzLm*H+P9Sopy1jUKb#KYQ3lcLvDqfLa-? z!X6Hx@O@UPl9HD9>~UT3@$QquJksi5y!jiBxxh44ye~l~c+KRvtN2 zybeQ~87x?18%HoP9xza{^VhIQe0%PT)NB|=W_tRF?3@wm`@Q%_bJe8`G~1i_+y=M)tK&98@5+q&MqE9^-lpUC>^RuA2m*hMNum9hvEI_ zh=}L?W}Z3(@ke#P(YCeAn-<#yU+}6L z^H|0V)K+v){Fcom57}?tzGrV(*}gS4ZHKBP0IDbK2Pco%bNA+$V|M>z4Q1ISutl~` zLAuoBVg`UPLg(@Qpsk+K(c7&~2Xm%*bD_$wcr$@TtzoTgFYM@{d;4d8B{7pISctd( z%yJ*y{P{=V@nHbeXTe)F|g4u7#6i#Ql1!tCK6s;p3NYNIMPRn z)xHCA>p^-PTv@lJDpDInJ61%^jD$ZK-L-z=?eY0yL4&j;s$-|EZB=cqK4(J%CClOr zm`rb>mM1l6qQ@qaV-N^J!e~$yRcH&9{Uq)AD#-KiLv*$jK&|%CCO>GNA6m1HIgv!M zHE$9Un7F-XiHm1#ZRrO{R>a;4F;FOhQ=15;?0M1%G*E5VB&|CGK;5bXtK~!UyA|EY?2f?Wu83tkV>?!T%!n!^7>E-5+8$#6s>L)agg~-f*(Q zBKl2ze&}347v9rFpTNMpQREyyM3fKgtT>gma$npI;fR%n0IxK*?F%1N?IJ-MQB>{i z%nlFZ%-A3=z~B71*)e=L@?b%1=t@~CZ~6?N;3-X?Y$!H;sj$`jtuKyG4&Pa1$NZ7s?rjGam-~t~7`jXFe$VJeAc+f%a zr)0;*L2(b_*wH3GDU|w1oP~=#|HTn&{8McH(H}w_|1E7Ff)UuUPcB`xg~twi6jEr6jmyYNu0jAZ%(*g=!On0Gc5s{?cHsQK}C2`NOngXMg_%w>R-*j_QsG< zCt+wm55Z4;;^(`e_qy;$y_yEF?f!#)T`;e!y#K=?XutSh%ag~30T8m#VwBY83zu(k zngTdS9s)=qkuG3rt!YBTdjY}aS%Ayu{HIN)Bbh%xC>(rJ&GCbax*ih!;M#+duHvGs zGywubIRH8r%!+C_5(bDt%(6YkGT8cxMcYNiT0JspMO20}tJ}7`wP%}WkJ`d~-TE6V zmL+cZ40*%TaQC}Wyv-qwSpASH5lFD**C8;aO}vP45NfyA*#|sj`QCnr!fCGIG3?_E zvc%D-Ks!D~b9Yd;{|*aVrUCDf#5JuaF##HI9qP2O<@1NFLC*h1lD!Igb2hS0C>t}_ zf$+AFP~>)~us}IVX-{*RGUz1L^6gB8Zc>-(<3BoCKkpPv2vle&xLu{Z_5g_B^AJOQ zUCb0LK13=VoW7*S6T=~bvwu8ZiOalqA zOh&v!A!YXpRqLyk?Uy#9b`3)L3v*@r%w0mmG;oArMXPnIuH-E7YcE;i2{v~cE}^}_ zadHneKVI8y2q=M)m~mj=`f#2^x|bHX1tX+*dxUf1!ez(i#W>$>llgIzxo<*r+YtX< z<|t0z62Q;0KmbN=GCP2D4?$$R=u?Sa97;(k&3r^5GFMUikMTyCnL1fQ7V#8_qebe? zu<^}m#Fl@FZGSKzXTEowu(Lb#0!B1)%chrZ+h?zxuuHE_5#pJ)Qx#GOk%o2$g2co4 zr$7)PLIsGN$br(@227LYC!8Zj&xiHk*m8US(DeiN_OFM}g%=)_0le^=K||D!YJ9Uy z0F(5p`rs-P@F9!*N?H7T$qo}@ysINdDWp)tm z63$TKp*F<7v_-Vf_Nr&kjIbT}!oqc&HksQdFhRST%wGg1Q+rJGlYOuOgYZUs$i9;r zvuD$rHjTsZ=&dVcob1?hz|Q4yvOCbuG`J%HASvpgLM{RMm_*^s#c0QBXeYHKSx?$X zs;i!3Ks0B9b`JWYB@^@}_^pxA8jQohqQB(g!H*t#_|RPs?FBB>Z`Jpgarj#`j{H-V z&RBHo_VnT6TDT*$AC%?$FX`QX8SVdz*74Xd08ah^D1C=BByKTSBrHRKRUBPtFVbZC zjCUs&H0ZI>E^X@`1@!DeFJpg1*Fy-h9!C834zXdFm3lHC@sQbg!`n)KVJVORR zF!$hzps?T5g5#rYKb6N^asQH2wV2{~b+;hsn|QZR6mQx4Ls|RQi%0C-8@D|U_{x)~ zEkVrvUVMm5d*lf(RqWc$w!L%`E}U>dMa-iWv^WH!(`Lcv<^2s}#9KM3Jo7C42zv(Z z;xLqqpo)fY?1?xt-o4m|duY~|Xq%ATKjvF0PH801vFt&blAc_w*EK=i7!uX_L+l^3 zWTlHIt@+LOz0NXDOJZ)6e53?&OxD;4Cfb!$j-xv2e|A!@Lgd!wD7Q^~CI(_Ub~@zJ2lq zTSs#+!DMb>3ZRrVM*4xPnnw}v3U>^2x-W(g^F(ln^V5d{;q)A2w7R7}{OiE_JUBO0 zrHF~`0VHOeLMNq~XKiglm^x5&g}o`Sy>GY6MmHW^A?a0`aA3_HS5>u4WWaqHCVY+D z8~N3=^Ft` z_NMLJoww!uF}s*TF(-q-a<&p3ZKH)@N(;DDV>vX7$mM&jBr}l8AjHa@yMHGHh4gfD5JmUCT z75RI3CJ@e4u?pyYA%5>Z(5+wZ&=J1df9P7+D8J`d{f`7I=it1I-lNH2-lY=enjRB+ z(OVRjDxa&zUbaouJE}B zPf@H}cleIro-Q5$#D^aEDc314;_ra^(~2CdhjU+CLK+-V7Qerf#i0#+woE~YS`e6B zm;mj-mjS73+uS%BfrKP*-dC$*7GS%zE-E}pBXXeX*kM-qXF*Ck48_+$I#sog zwz*WGf!9Y62QBpD3=UB1*c)@FY~|hiHndr>9YPY15PMv~d{N@PQ4r@I_U*PrSWyNc zMu%F6f}{*BQcVcn+N3z+8qaachGrbtN}(bpoZ~8Zx?2HfEApcIbE-sBCI;oh4z*iH zIIZ0=cWe+|3-5bD{^*^$7arXzyyH^ml1|r#ao}Tb28^{3#kw8FtkQRD$(lPi?Q6Hw zgw7EYAH9xrU$aIbZRsyPVe!)wBvDG+D!P?zB>ztDsO=5_w`|)y&L$=!c+^*k>0Cw4 zTP5+%P!B|g{oEy*H54n3c1L?eNK(tf2&9nSglYx%J2-Z&0{d@ddhK6#@;Iqs52CEF zbsu44|BTZ)5Z)KG>mzFa5&H3Mq}T+Gl|88amBlcIouJ8F#mQV)IFx{>Bh-KGWvjl; zyMA|#XvJ&14bx10)xP+~U2NYo_WF?-_^_m%pq>^g-~}{H+ml(_#py1>R>L@*FqX`* zq+pG`8t-GPKR&tPC_nrij(<4zANre%y6$CEH|G%LB7{4IwhWfoH86WzleC?LnXPOp z9u5W)#sJU7;CV5*s)(ja!y^J#=Tjq%a|VXBu|rrOniPh{{WaEr=?Yu^)~Cq%|LPL~ zUjxJCZ3$r~{ALWrXFNyqPH@hKk7(M+M?hgIbELHqfp@pi38j}9>B)_C7p!8y?^mwxo&&*?{!Z?iJ#Z}+=tZSqD^X& ze{@bi6szBc0cb;@*Mo&gEwtqcx)b8fD?EDN; z<$BJF3!BzEgvW3euVfs@c99fXXb7}1Pay^2MBafMWecP6wnlXFr3OVJF`gvcI zx$C9;Iuh)voTZSQUHvZQb@q*nFL%Ta1|jcsd5pG$j6Tt>ifyQ7%A-3;#`^v~a| z3}0gQJYJ55h>7^Hew*PvlphvB;@mYrlBP7S`8nce7^2!CsiW3{HPl9P!vK zQO;^qeFwjf%Sg-F{-mvCOPD;!?nd4R0t^T-gh@kXzsp?2fRi*@4=E#xXmHr~%-$!A z>ra^9I=e+e_L&>n3^L!A?VrtjlF7W?$4qent;;%Qo=Xsu3yG>7W^VvXu{kvAdnK`{ z#|Wn@z!V(I5%$+-BR!wM98MB*k9W`Mgp=7OVu>;(he9p~_e#$EHEqdOJBZ+?Ue)bI00-(h(z zAm*-vIb{v~{s8Lp9GvzRekV1a>%r*1hm)YEqDHggZP2`m%M|es=h_)w_yZPbs;|$g zFtkYokQN^?2vz)PR(e@;uRcvbsYls}v<}oI!L&e8^&yl*2wVLm@BTFRk5lge_xgEd zfpJ2iKvC&BVfwuvtQS)K%0v@K69h!K6sru6QM6{t6_u=#_=?PpOYdH_(IzE%(R}@TI8hrTR(#1pzZBpLZHGAj)RQG12Cex z13=}A#^3wkx&5;bxvmAKdZmVxx>1Q_)O@vF9`mhUkh|>vrv@Rs4Vyu-{L@d2Vf)5h zuo@+nd)ST_*X@(22G@|*vLNjgx%p$HbFFS|A=R_V3=V_YOb_8tcxe+Kor59BA58H|@WJU_Sc?HyB%4c!AX<84Siu-?s5HNU*5wwec;^(9}Y&&r*G1z6a zGD{3LQHVB#?f+$JGK`g6})YVDS_{^Z+C7k&gV5=VCL+gE;+wI5$N zX?F*DF(t7>C8j30PqZY=Q-V*7ya(b^L~MMXG_H)P1KOjL!#y1;7aotUpRQUryzSrN z{KFJEBMCO_V2aq`y(vBt26lO6FK{2j`LD4pEdWk?Vt%C&Ljxj4iHH5a&pUrmp4kR- zm0xCSWHF^^t%{gos`zKLUOH>`#3X&=F)iq@D}os@5Q^Y{={d!_KaX1d6xWBj#^%|^ zrhSY(nIc7OS^GlX;(;4P^F%OfrKI{tE1?8S9qJ%&C%Hgi0H`-Z;-9(IC6a=TQO6ie z-Kf3y!`E%4f7oWg0ps^>kpxDW!F0K)-+cLew)s7a+BoOPcGzWlF<^oJb|SOTt~vWI~3 z!^-Pg&<2RT7CuN#h(O6nlq@HUO*mdFp#sNCw=ZTBTPy4+SF<-}`Urt*;e<3mVwpR3 zZgUQa8t-gG?5(X;8_oAa=0v^!l%>i2{Le9L=F2i5FPqRtN<`mKJdXn3#t*#2)X&x?h`o$&K5 zqSFV`oPfc>(b3;U1Eq61y0KtKZ~V~y&XFNI|IdB^#gOA7%vfa8?32fB@8VHQK68p^ zhi&1#4{RN^{tk|eH?j#^#DO+OK!@T_|<_0Fh z(x`1$xqc6VcBvA#qcAz2#Gjy#F=|Cnpfh)4Fd&;ao6Rn?ZSrq@$&$cPWcdc3qKlR- zuGn*Pz)F&LJaNiyki(!&bb|YI$mS0dYDbZfcVO6xv}p^%OOpVK0EWa|b=Qm!d-z9l z63)w`_k9k#1yv@T$8cW#IsV|+&>SDI)`Bz^irEk9blkDTwHfA;=m|H51D|esQe`1E zRJ}C6g{fyJzeHXN)ZA$1R?yaTKKp{%XPye+gJs}E(qV;jh`oG!Vu<%IBdELzJkKz$ zL#&}*U_J%RD`}OSLnZwSv&rZj3ya{3d+vZ-&yC|# zLL`IQtSQike;UGIKW81UpbcxDI|A@mJA< z&mVa7XPyerC?rLTQ2Emn>>Q^+w`v)Q1ungY@DykB4zieAXEbGp?r+*=fq3iOvRxyF zzmBudFSKsa{w4a8LBMO-eZq#O4rgtA2n!=>%jOQx;$hxDFu*<`OIBnr`mu4;*K`WM ze>XEk2{BWjee9gM5>i4xB8i+qon*|l_30qbM98r6ZIIcf5R!<$cY*@0GS^@-5x;!F z+N&k2&8?fgauTV2+iv~-WlQuT`{1n}*nq`wP*x0pZ1}%$b0Z zLBu!mvn#6(1kyQFnpA)Q9=Y^A`^rGup875(fVT_QN;7e7vLarXwB2V1ZTHNujXrzc z)*ycCjHhoNg-|qM(pp8@+<<6kY)Jg$hHI&~)o)Uz;ttPvX>K7q z7_~1m_m?45WGJ$iFfW`yztD?^;T{np%M>3=tlH3!_E#IRdmAJU`Y(Ug(*MgpwZTP* zdU_S>&zzmVhuWDrh(43FZ;a7@v=<(4rbVOq9pM^kdG<#(TY-fX(Zm7K2U-jF(L>Bv zc-?P@W%yP0*6ulWj$@ib$t zRNEOkE&rMX^(&`;l8-aoJjNOA5OaoSKWnOv8AuT`QQ9E9*Q!ajCG?Lm-rHLt>*o+! zp<&ugZi&@dJMs&kuthXKo5fl1#xR;0qS2xUeaz?infr@Gof5DafoT|=w8F+c zLUP&DAskw@;inu2830Y`&+*7lsp+H3|E@+rAozG3NDkUr4H+cp576c~+Yy3%0I+Mb zShQ?hea>~yqz72)-%moPUk&Q=wJlSEmRwiL)OT<<_)FFMIA*_YZgDPL@nMJO6IT(c zm2j-ObqFUR*n9nIvDB*iue|OFoZadL6656Kq+BJ@4Fq@dJn4IJ>>1u*i`D@=m64`W z%Sd2{rfj5K#9=6Dx!pyZjw?Kgo&F;I5Cjl0xN9U!9lI4E5Gz~IQv7h}vn$&?Jz zpC2Hlye}q71E;|Xx(njtf|!EeeBSK;`fhMee)`;c-Lez3)z#)8eh~$wYZFJO0nt=1 ztq{F{XEc};S3#W7+H9q(Ip*F3>e{ns@DBiKOP!4U+7G=~wq?CD)Bpr`4*bc4FM}WY$X%GkAI z?7fNO*6$I%wTXQ`!rl+Q$qs7aC76Nf7U6@uKI#lW#J<5ueGsL6B3J@Wagy2@jy8Eg z90eUHWxkLdUSsZk<~K~%2AYLU2!BgiSY)hPbPM)Foyp| zPd}>u=k4TgZP?P^zh>iTk8F*-8hVR%dh-KoUhB1!qsQ&uFk_fO_1~mzFeSby7_%6A z4&>8y+uqzn8vs)!uR2YS+OCgsVQ3zFt?N1;9p2{Pl-`FY56XEN{^*uqON*|1-oa<2 zwXs9a1N;+snV10WBxn#sI|(za@>S+}8?A`oNMk7GNn@(*lwrW6uAjvLw=erWbrP_pdCW}@WUBG^-upM5Duhuh|;zeeN_}hOqwO$Exg)R9C7!MaNq(0 zZ54i_3$#b;MBnNM9rAwAknTEC-yAh`%ljO1O}Iqo(!lCD2|?j;;FNMIse#gKf`8o~ zdLyU2Ux(J}emVM~x^;zj1zKLp9>5KHr=E`M-QDN-`Pcq8;hm=+%s6dd|SYWLp` zZvH!%I{?F>MpLR%5B~b83UOZxOZPlX#Lt3~;rs`8gC_+1-MaxTP*bSGRfn91N7y?3 z)`AF*{S$A#%1A@4`|6g1EEknMYn9k*IRS~7wIGZgq>>#}8SCeVY;6|Dr3^&3ADm6j z_%c#N4(aUl7J0g{MNT#DkpKvrYYtWZ>K0ka1}t+rfm1rpNg%t8l`ZSX-kwMOw?|BH zrMzpoo<8ek^k$bLSMim9O^9}br#jO5$|rl_gzbqwzSY7!<^K=LbmkV1FeNHHJCcmc}E3B#Wnhw7zXGz4Z;@fMrz>KsHuhn8tjWf*GY8Lwj*MMIz^kncD#T|V&#)h zTVeK9t9eQAjDLqQrZcL7Kob9vRmDZ~g;Z`60~ekFO8PIvnvXRQ)2X9|PQe zo!e*d+h7*4_eU+y0!ae<3C=O^Yvr-t^bv&ffMA|`=}gWlG$V;mh_>LZ3fx=dHAwWK z&c_KeL7*i}vJ5!&^&Q3&W5PfWPNiFH{FAWWRKIO+lk;N;e}Q7tR&xWmIK=GY`UW7d zOEeE*qofDU5*C=LZn|krnjppgds(WF*MJb*Z@!*Ftn((U|d2IkiPj- z5^y_!3mUjC=M@opKLjA%WB-j^pwVxA^o|FB9F~6Y?SQOue1s45@oE7`#hXgv&_Ce} zZKv8q1zeeAYgG_-Vi&vObiv*`lCxbT_>EH&_S(@t)PIDI5zezz7_f=On>KrHl9=Q@ zn=Uu3hdALR@!pGhV!DglR@=eZsSvULVfL0MjI2ZGkTwqKA;vr@p8z=f7OOTxSfRq_ zV@O`f)G#4VWB9W5(?29!EeFQeIdAWSdd@Zau8m#Pqof!BI+5jjh>yGnhPYMYQ&t)G zlIA%fa(VkBeWkwlz71{Q%!D)I7WGGmMsRKeTs@*d*qHO%3#eq}U4Xrx!PBmRJ=jte zc~FCx+S8}Z6uw1*7sm@eAG}ZBF70*C_50Beh&7}y{;l#4ebmhZ&uJw|XDjIt4UM6W z4g3^nDGADL*HkU3P;Y`I%X_Sm}+>9--e_nDg=r1~71j^}YsI|Koq zi}%~D1}23D$&b=`+ksHbvV&8Ly&ZffCx=qn$M_Opu5bbBg=p7(A{lETQ_ksTAh~j{ z%LF*_SLPeWy^f#39qimi&Rbym5`?a~SGGaIFMGI_L)9d|fED(r&kpGK>mRa(k9f@4v|k_HL_xDJo3dnIAh2fb%HHs zM(3*V!#?3XpR?{Wt_2F8=Uey0Y=+t)b$fHi{Yf-Nbs|W7cSSj*;=5rGPa`bTmen2# z>mYB;@vVF$9?;3UZNad&(ZICG21yVBnJ@=EoiCex!BUZ%}IM|>p zov08x?93u#rS!>+6yJQ*F8(}(NA=rrsjjR4I)b^^1!*!K9KrSd=Y<3Lc%96TU#GYa zWA~x`vi#Pv4Sx%#OY^JW`{k)CID&5ELzo45YK zgR6gi&j7K<qS zPKSscfZ^AuPt2>{r7QjnMfO7p1$g?f&{Wr>pFoX8TOoYmPK2`UYdHzg#6bo^lG+&I8;1t0c5T6icJP2EENp0tO=Bi$HyIAy&MK+?`W=!R)sdmWxm;F~ z6e69hNGM$A7cWB0xVVBiO1jbjb5c;aCPeUh7w-=cKi|yN!{=f7?zfi>&-JZmy1&I3 zwt3G}6A&xOT8tY}e$`!@Jlc9`%1)dEB*MgFM)EU|Ppspe4eGKO%lQS3T;NR5bcXUCJ%r zY8Xc|49>ip85L)$c_+@S4YRPw+S`H{j4{5Wj7+AOBAqahH40wQ15fegIq8*&i3NRV9%dg?i#*DL1 zh0mkU8ST-$hI1)muXD|F$h7I&{(N`udl{AE6F$3t&Om5f*ER^DgW<*;qVBSknLifK z*(uan(xmX-S*@`5DPF}A+87fv$ff$2mJd8h>Zeew?Y0(E{BqWc{0oMHTx zrdwGxmF8DbH!0ezH6tcKL|5xYAI**9CQh`VvPXxdC?2lLP_z7F$fRJ3_k4@{;~aBrgRSvrh~24?nZA!>I<#%*qJ#=cJa z?zPc8ekaNbNEa|cwa^;L>Y+VB^oal`;l@PtGB^ZJI%u=ck=oBk)AoSCQpx^tfB_Ip zdSCgbh(Duw3h{6EnGkpTPwwe=_}Q;k5Bv3?p6<`!nnqFW(L2->)vw}u*#FRCwS_}k zVIs;F|ItSbfRD#tnLoci_QgO000MHgJ1q+dvjdtIh$hqj2n3l>2(Olq4gpL^{QZK; za}q2xzY_fE0*3=cw>}3A03@iXTdeyYzvw{4-GkTo>C6Mg#Q>;W3pT%oB&Td`Vp+6h zY#glP`L$_?aBx&oQS5KS>yb+h-2B% zojKI+NXR(&L`mpQ3=`uho|Vt|&G$WXKBc$ZB0L?AFxBVhHgXh*%P&8lds_hIGFj+4IhW*M8*N+xXpk*k(R489w^22} z2bVSvGjI=StdkVA*1qh#%vJShHc~|!MEV{@s_*R-EDsUdsAg@w$cDX`1KM(!1JS2D zyD)XT_yyqfAtLV%Z!jn#?jn*JZ;ihk1(Z|49qqWTs0mWXLh-o9b$yF>&>Ue)4$7$v z^)i7#I*!xsyBO$q7(93{7 zq2j34J>^3Iv6j zTD6t8B3mMGN6HP#ga+fl*Z#SNK-YbM^v&TgqZ4lKA7Bs41>xJAGf$42^{mSKdu&KJe!IwsUBVG{3}rj}M`ChpR`8Ji~%*AgN8PT*gt0Bq2x%{n*3P zja3K=;YGO6_h9FbU~A6cKqxJ60>m02)Gm$`o$nolF+e))dj_Up2%Jj+D1n$1Nf=;0 zIz>nHrQXqT=_`~oLD~yB0xDxbib0h`Bcw(udd%Vxn}paMLMoA_WDWY$l08)^206E> z0Yb7MW~-}KBGcI@Ht`&>^W&()8*_8m$Vr$3@^A3*ghafvD)sy4k^U7qfCC*t-87YJ zK2Ovsq7dp+MFgC9=$ns-qwf1hWx3`bo%0Ea1()~h<5YHW$I{$SEi7AZY|KOMhE{Ld z|M>mixBvd__pEJM{|bh<=yls7ln zW7ok&fB-N{w%~~v;5Qt9dKH1}p_TWZNhCUH`%8OAD6XA9t z&A*=GI%9a4{06^D4F3fr{~LZXcRw3Wd*Te#4J50E`8MJ-M>sDW+XZM?a(NyHHVd0%kYQ>%d+CBx+#Xf&C49 z1lZcyKL0$s4C34=jiLNe7_SU#?kfQPucN)W2xmOO*!1wpFm5@m4;B!c6T8Y^YJYkB zi$`_P6a&FJ@^EHfW!%t`)4HR8uSwojUcb$h5x1|<&4wH`F-5?{l`v(%qF}xw0n^SV zkD>+Gq72^sdn9y9fm71$v|WPu_h7y_JG;P^;WZm9@7WWpD|Ufg4lgX-hY4U~AT(T2 zv|T;CHx4rt7bD<=SJ1<%S9|nfT*Gh8h0mE99Ul9{xI2GbmxS$3jVePOLei=yK-NKD zb$*~0e@9rQm+!uR|JuQm{dYd7fmU|mN$=E0@8Q3nAADHg@eu>?@QC~s`SnFi01^2B zut-7ztrg;gT?>C70!6I5a~xn1zbAsF8P~TwTjgxyL=7Ma)>v>Npm6D5PdS7Hzq>|; zpbpjJ_kwrrdzexs7O8+JN?Oo=K5CG(gR=AMJJ-(!EU4QyK_siF;jr+z7|a z(#TfQAWWW3OKk_UaOLE%oxQVwLlyD7wN+au)7K!e*YSACX71j#!7-fq7Ovo!C$}?H zjWuHVsoA%Zh4Cn`O|IG!C(UmXCPWB3iZ(*K%q7tT42_;cikxJjfTYDN>E+ZT^NRlY zn6mg~hu3Nf9Vj)7QeR4op%@K)K-)+Sm=(}`DC61~_l{F;n(In+DtcN4NjHW44)`B!A5G#BiMAzIAPz`Yc zK%I_I!tY?-10v8p=T_iS&9~yx<&jP=1R~BEs*kAD!G3&bti)m@EJ^%G2No@F*dfy1 z{@1U6%l`8#Z&)wVdfg{qK#mKT_?Mrv>m?751O7%6e$$$P&3iMX_9n*w1*K^k{Weh>K4U> zLs}fmF_+$6>9f^zIo$EI#VM=!e-U=J`}O{iHrA>Two=K*GXVoKjpJnv0@#cror_3D zk#<`!TEK-gF)yNR_!Qayl*op}x?HDz4INBgq|=8Mg4Dq>WAx1z9hO`*ss5L(R z;WzK~28ij?HD}_efK&f1SP-1~H`jHpGAgfcnC0x;2Fx_Wz`Y*kH-j0jj3nK9>L>t9 zIizoU`<6vuPKSh^9kfX%G3C!l zu_m1GQ>7gLoDOk6#6|LB>cGUw3=qY9AT68A-a=3?Zxs?OHFoZ@4+G}I#3Ab&KFtO} zt0alK=ldBHCMRw4{wfR?;hHdw2|WKd2_ft-&v6_MTHulhA;VdceGP7|z^GMi7<1Lg z&YIQ8{@GhywXx<7ey#xlj>8BhdAA%C8EF5foCCqw2e?mn{7`2oLD%@p!HIaukd%9A zU!&aPx@&LUyrjPI;hr-S-RHFggG0C`u=D$rt9IR^j$n?1Z^ywtpg~>T!Sy;-m*3Yj zyelZH=fbkxc2X`141oX80Nnlul=y3ZXmvam$FEF@0fz?A-1YO(629qP5gO3j|VciD1q@2<0}*L~q(w+_s4$aA zoe_{yhE!uM)Wj*0&dC0%u(6D?!O^=tZ5s?@5Y^(x>$`(}Q9YMALeU8=u(C+IBzYZT z00Q3&A!;OaT~|N(xH?yegD2$pFIDjbb21H2a|#4^hWDN1_t`RuZB~$)K~74eI(%)# z`p{^!NJtY$_1Fh8&cZ+_`HXKA3K46*@+9U1YE<-qq6FFyGre1o?hJtDqWf`Vuj>!m z=7@vaPTak`q)e3;GJxo7$0Ttua)8smu&PTEFbJ8!LCEdc%+8$s&u@Lle(T2DmLU3~ z%x-$(Y9u|Z50Yi=GatA9nWte;$fUQlZoNm2S(NCD7VS~m*bYvfH5#0y-3g?%Df)Vt zYt`Bwd6Njqf@rrrjVyDc@Isk5Am}kd&N4{z+AcSP_MnXfFGu}|Fmw$!ao{kq0I>#1 zqxeN?C2kx>DvDn~c6Ar0M0Oq|*So8lJTo8X8%t!DWQ!k?@QC?5wZ2L|2l#>vUhUdX ze(M$~n71K#BX%B!9T}hxFj~?OxfVy|_yB`cO24So8Ebws zrNP{A{~%+awdnKBegEdSuI*p%!h*vHMbL`Lye8k75?Yr5whY#m6kGVtvA`xtZGW}OHG6qSRF&$R{HS$fB+zWHC5%00?;S%EfH)TW_Z&G5izthauvzr zztDk+D)x~qj2t0DFb9b|^T)U+Sr=+^l(nqYFWeT4xwrV?Ka>$hP!T8kK{x56u1jd6 zGvfT!N5)xvTL(B6`~(_+gVkwlG#)yFu?o)hS98xjjUT^2Bf&?oA%?>5=|2Cv>t2~v z-A{{eKc`%yTDKr`qb5 zgl;yliuxZyJIeO3t00tx)BX0&CnoF-VvlD~ZBG#Xc4`Y-9IDwP5P(C(`CpmJqn7(c zd*a$GlFCiXzO!KG$BtNYaft*Tp`TPh-W)osv1UK%2b7yUNp3Oo?BP_LIn*{WcPOnX$dCt;3>;doNO~ZX< zsq5hY@XY`POc64i28S}*9wN81sKd<=W|ou}+1A%-uJdmOJeG8H!MEkYf?3vt~3 ztxs6v^|vkh?iI@ro7~T!CKy1C?IH|OWf)>VGePwlbw1SNL_w3G*FJi5i|t?5ZylN_ zGUU0M9gT>q(&ZS(c)7$cskQ}CCn1lk!$I&QR_7fhdujKU{SUX_vsZ83Ai55MhJ4pz zqFYFcTd1>hU;eE196E!bzb zpw%KAvz%kU2ckdgBLnK?d29Qv9KOpn{O3CsoL zcYonuUa{KK$L$ACkJ+*J7Hxpg#J%?}+cQ0**6ts)Z;p@Ho#zH|x-VIStzpNY)V)Zs zLz@jdGQU6u=AwNt0U<1JTKihsPHez<4q{?xF54%EH|!Mm3v3-5MVled^aNug=g~F} zkun8&G9fXp44rJ^M@CQE+?T6%87;!`_i+r|gJ~0^MZCWVPk-l{{mKXL+sL^K_K!wB zW`DpO%o4W7zQ^AxDNqq|gGY6c>dAJvTWALAR+m_$IW*@t7&d&XvPg`t)^w@+fUSN%% zg83Rm^*#;UPU2KM4MUV=KBbG%o=_=ZUD_ad-@BtQ^X;ZUN%*WK5v!=+jQ$D2ot$w` z3uO;ISp%&{2MtIXMlM0t#_}SJSRKiIX410kN(eTLRcMVvQ7>PMs(oMJ~S8l&nIxBp#NQb_-8--QHj6E2nfO+o5KN6YX?9Q zUm1J?VyjMaIRI9^g(Lhz_@$c%4&C=Me2eQ2&I4c%i-z}A4`|5C=$e2yq&E)G{1D?Q zCRD^u+JUB!P4J&5y3cY=jDfVXEo_C5Nq?@lh_~%~q~yIFw`(Mc`QZ~oYJBrorty>CD;hx?-J9L;VeuM``P=7~}xZ)t5$_}Z5A^zK_;nPTbXP$bB4TO|v z1eGEC#^zKlMcvLLOG*IUm+&q z1cl(kLzASR;U~NK<{s_5be?AC#gQP2I%YW$$Hh%1)MczlXy#s^u(kbgdb(H$Lo)qM5=%D4% zj%^X%TZTZc?QUD82BIY=eu14EXV~+gpQw(tE3;%bgcKA}8$(dj%+CO3f(XfZYK+;Q zq?jLHf&jJHqL!F^Qqv~y2L2mpg!0&sbE|W$?IW=~;uI}R zV+_!qDC=Mi+Wq`Hq_DnOwoj8s|92)X*pKq$7Et=N|o(wkf2uwi~Nx)O(K6F6mOPc(@r_<(QB0l>X@+6Wm1V=x&tv;=kb zYe*B((lUlMY|vCE zMuY1DQrTWbT-^Ck)k|vQk`{%Cns5l3cNPjZYPbP;5l2AS3Z>C%R$cO2Q2pV%gpafU zwb&a-9CZ)}yTjV(X4^(Kw(P|0hDBiz{_x2G`{oQ@;uDj$&IUde2#7+jmf3M^apsU6 zV)wXa8I>*S;0!kCNVNpP#e@Kdk;C5V8NAqXHs71KUN&XvMQV&At?VHktb**<*6}Bx zY?GbDifb|3UPgjpo6zC$b2fAOB0?DHPEkRVf!s$TM&HCz$irW1Jx6FD#9%Rt$Om22 z6!^#OxMHe;z6(Ld^kM9q2|N{$eo4oIXEP>?e9bPRp*RaObZBDKW=6R381n^eYvcD;E2Ti5ugVf&5iUdw=Br8<``Qqp55 zFpB^vK_z(QM@Y>!-CVW5vGA__-Ak|Ae{%Ib%U5nQp-5(X5XV%?%7b`hL&yuyylSJT z+1>9liHnG{kAD1=6`nXE{7>D$2}r)hSk`EB84v6Z^S-jPVY{gP%V+@R-@8JLHn|(# zxo-J4@7ciZP4XnvtWY6Z14Jx!X_fZY@mz0XGD-4o-_{n5Kn%DTXKwncO~!|WFbe%F z)h*Aptb80GiU^6!7uU9TY~t3u^;~+-PApYyd==&sGej@j!47O~!n9#VSZdnDyWh7{ z_kL{s^LH$|G;eY2^{LG{_JKe%lRFFy<#3>fcmvBN_I=NibSjOngSvbkX=S8v-Z<@79H;wS0Z^4ZQjJ{xuO+&AZ?P2AHZ++5&eLcfHvlqEKa=I(*Y(+ zemFIvDfWm?C~mM-F#9QN13TN>n^u8vH<{;-)&o-!A(sT%Kxhu{W{e`-Q=cRFD>TUJ z*up1c2lG~Kdc;nUOXPTIo3Ksr4}?B0Vvn{WFtY|jMz22f;B8bIe0(}sZ$g#(co$(r zxU6#(01u{BtLd5CKYd~@&_J=-GHUj^hK zRd5DF-vACfYEgwOe~5;5Y?b)xKb^M;9OD*_PTIXAd3(3eZ);=t0}uj~tAY${(%vD= z?8zHHwq!q2`Wj(^qySBbxFUvEH5M$kOcr%58f%i?}N` z0L;fNu?i4AjaL^LOZ%ZsXX2=N50U101Bq^qeeKiay&qzW(p>~SMKL_z&e`EI9_7!S zvuoqiuHJ0jU9geby50QX76XdT5`y6&WLo)-X?Ra~_zKq#jjBH3A?(4S`yXVZx`$)ii5G0( z#PimJwtjyF8+{4+ zPv#f7gc2UdcLHC81hJzPBst~!*LX6^P6%{ANBJ`9e-AU|bq(NICHhEOv(5xY=u?)I zzdbOfy52@hl!Pc_t!Mr^Hi`roNo^9kh&rD(jT4a(g`l<2Bn?+KYy!B--7DK>Z{4n3 zf#JDF%z0+mQlp10Hkh%Ii9s|0BI1lGgiZ7BM3(taQjaoAS|w(qXh$~wZoLP`P{L6s=63BjSKhTx7w*{mBUASE$!Yuj$tn7RFFg!J8O;k- zGlvW!Q|HkSIL`XK${e7N-l;-P8G*ByMgLELO71ZF@Cz-D!V}#udT5*QJ zJ?t(Ck`fh|jUoIKMp&!^n9>dtp>iUhB4+_fgE0NnNPr~!b{|Zbn0|LPp#QE3q^)YE z7@{!5Pa6U?cwbWt07PG?$I*}07`UfO4uZSyU$R-uOen@|X6Cfzd&Yg8)mSIxjWt`E zzh(7p5+=bcG#U_9;3GeR76&-k0Zx?_JI-#N9vWDMS%hJQ{d7U32@}wg@t-w=HxjrC zzZEtEo6Aqwxs7==Mpdp&`B2eVUpMO1&v*C=1g3-oHBNdw#D_Zf z@%j0x`l!^OV+5G#$LA2>IPe?<9DvG+JF6%Frhu4gId#!}FaQF)5O}$Y4hSH`Nolfua`>%7U;f*F@tX@O0bGeQ^r!3SYq@cqzSYI#eSrw{v{AF1Ty)86^?xx z6)OnyJex6{?6)R+1oX&>P^JlbuS1^Vp0=9z?qD8RSY5O>ec8Eo&&nU%xBAU_OKcU9 z+F`KBzh4C|nh;<)UMBI&5EGEaHr~cruY=mX1>vbfZEB)85IG_HBw=bPRKLnIA&6}< z-*W7Wp?Qeo{of{vp^4hJfzx7|D2fU&(#qG-7BHU)9Q}}pGmLRPzJqj_Me~qCT1UN) zgWEQmg-nC6&G(A-)(5|5iK)Xj^}@$(?3r^&!Dw?}aI}$>TI_%gf9Q^(sF-7z3ff4q zW!l;^ebUlj`8Drdt-Rq-XeZ|flQbUnFl0x!`S%5YK~p=z{G26y)C-N7>UY?mI+p{o8wN87HQcs z%Pl)qCUfPnqgEV_;}cP~;W=_?z~J>U_r2_xH-VGp>B$j0$Nmj1q~;jTbsBb3YH=<` z7zUj7i>O8z=Lk%m@Pg)B8bZ(bi8L)Q|0d?1hN3Sx7xU%+9GUPN4FOYYC(b-&!y`ks zc>g`S@`LZ&0P+7BV4=dcz-hvH50f>scN#ZLboyImv=gkSIIz}4tFnoPVvY31J4Jq1 z0u6_p2-}pACKA76Tw5@8!34zcaVgV_d+ZW;JvU-67hvpHU@l|(hIx#M8s$}SX zHo?|nb1ccXj^MY1F`em<0PvS{g!RxFKf>P+*RHEUiST~-{cyR*`PXB^0QkBVl?34n zkhQ2KL3LPIQm+LnpAaCpEx^-ar@UxHF!kYmU38QYq* zUhK+Q;&|hdlu$_&370G3LlEHwLr6F+HeZQ#cGw7rcg<$@4XcpQ06<^#kE6jH;=yH;^AzO^`FYoFR3kLBhkO z1)T0mc8*ZWv&7_|Va&!s^n;j3ltHlpL({M-p$2pi&Z6__JKz=C^Nu7*kl(A0ZSQlw-p_PFc0;7uhYof)$dW=~J$sHmb z$L!#4CA%AaNc?k_nbA8vge()o*Jh8t7q(6N&NqT)80U=bb^-yQL@$^CKlQ6an z9jHoZJ3|k;eqbGv|L@rk?+B0{H_Qc|x zeHF2IwotIQnD-wJ4%ih;TkGUf*kb!@rB_xCgeLGO!s`qlYssTGAZQ*|xQ8Zb?vA}e zXJ1bDTMvn~CXOYnzVWUt{pb$Sk~KR*7R(wF{YC=^+|$QNv_r_|NFN&(!Td>uP1Vvw z)X~1gA^s7FsRRH9*H!PXfrKe(5m})0!Y$@f3I|9obI5q&QzF9}dP3GnK)3;3HwkDX z9z`__!Q5wN&XWlfAAprr*BT9sjoQ@l(=ZT3$q;?efVt`r#S_CJF$(dQ*1tDPVlNzc zw=ogOG}(bc*<;Sv@n?uIH?Y#SM5^gKm=s3w!MK(0v8%@??L?Dxuz1IIG3Crp9~c>CSqHG> z(L}_o`f*N%rJPS&RO!&)m$DdUj^$g`yIXBgV|34g$ z4FjOb+Mic(^Bf}30U3V)K*0nv-2y6~bd?qH37*k&0hk281+x!STOu04U6u1Uc7Liq z2i`6K33yeLGNv|h!V;3?3tK+vZ-4(aJA`C2gv2O8P=NR7p*0{0Nws)7U>236h8)gj zWr(QS+aYGRd|}*%vF{IEy=l8|zKvAQj$II>HV8EhQOOg#9Ra&!iRX@@A&8tmYmKS1 z5KI^l;>xSo1WOR`4v0MlhFB`i+7jR|=ew)euT$BI4U?OHYT_{IfXA$d-2=}a8)IXj zLEE@>kFcU5h)k+V+2io+=N%bRj3#gn%oAcL`*U-bEPixI8jLn6gjV_!o38Er1uVUXD0 z!g1@BwOLysL@R-i_z5Un=Pw-^%*k>gA5L(<_8Ad0+4BqR~-=Y0y1be6~AsRJPwRJq-j;x!1BNIJ5o zBUSkmByl85!eEIB>@>G2TXNG(l=%@^YT_5sf(em#hnQ0(bxI@E){%U*zEU_#rWm6L z%;(0{8+Mg8o?yJQ=g1|2NkBY|+7M$L;LmA4D-j|TqpuM+VURa)oa~jq@nzcvK>9CT zvm_3vZG1uYhG7&)a^nn?%IZ)kAaInxRz9`OXRB%-yI-=eC6{d*ZPpTE^NoI@NYHF- zk=b$1Q&d^UMkOIk3QcNnJ^lO<);tfPYC^1tz=5o*)fiI7RM( zG*bLJ{zC7=tf%n{X`v;88^j@#FvhMAYY$~gVv5P?+0ICdL{g$%X@!7sHjR31I%gX= z6rW_Lq0e5qZU6GbwADtZ?bj|}x5>o?>mw>fJ{@JWnicvaK}(t~$4x#lKGB@%n>p4H z@a?}Ha=GB&BEZ3+D}VN(I&_5J-KY2p{RbYtAC?Wv2Y0ooeC>EW58n%0)-CziemzzU zKsZazfkG5;)^K1v2vkm}nRsUe_UHUyiLjR7TTDV5sucKvr10W1*{mE}hNP2G^1R_aIw%iJfoYz== zVbj*gzP3wpnc~n896Lw^&-bI)%(H~)yg8h*Jsf?sD5uuu?CABktRG`!Y377wM*C4? zB5Q*jny4MC?EWXmqbLYLx&ARkvQyx=^`XY!#q)n_^$wogWBAxjS&h1HUcUxnAO zxMYg7IRLRMLo^p~8asacsIB6m98YCz9Fsu=hCpeBJH+%yLB>Lc$OZ0~AfAnp217_1 z6v2Wpfz;IR7!d$Hy#-6*g{%mMNS^kB{F=(Z1~EX42T3x%sKLv4wnrdfqxmuGCuQ*t zJDSz*{fI<5+crk%+>;=|VbEDH&R~IP-VOKgp4Kmr&ws4(9{Ntv7c@5sz{#- zZ&DbK2*IOAP)krxcux*<4J4No$g>v)B2kpLG|5{|W=I9B)GO@TH=40a0||Q*DX

    LU?Jf0{jBQ2bGMtG-8g+BQNs8Utu;lr}c8 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
      Ep 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

      4k~;3id2G8~1I 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�RaE>&Abjzq*T2#tKKJSb>!Id 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±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

      j@3(|Ni}H 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%0DO-v>Y2RyHw)bo2S&t`a2K_2C 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

      !A!2|Z^dxq>4;?e-H4}#q8 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%0AY- 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%0Agz2|FM1TU;bBi3?*G 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 H_j&R^#wIk> diff --git a/front-end/src/assets/logo/logo.png b/front-end/src/assets/logo/logo.png deleted file mode 100644 index d96c95141a7cfd5880c81df74358861235d8787e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 918232 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

      %?zTu->P(tYoFkGH>7Jf5 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 z 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

      EB^1V2ef_OyFN-i;Je%% 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

      r)Wcgt@$oxWE?s!x}$pu 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 void; - createType?: DatabaseTypeCode; - editId?: number; - submitCallback?: Function; -} - -export default function CreateConnection(props: IProps) { - const { className, closeCreateConnection, createType, editId, submitCallback } = props; - const [baseInfoForm] = Form.useForm(); - const [sshForm] = Form.useForm(); - const [backfillData, setBackfillData] = useState(); - const [loadings, setLoading] = useState({ - confirmButton: false, - testButton: false, - }); - - const [currentType, setCurrentType] = useState(createType || DatabaseTypeCode.MYSQL); - - useEffect(() => { - if (editId) { - getConnectionDetails(editId); - } - }, [editId]) - - function getConnectionDetails(id: number) { - setBackfillData(undefined); - connectionService.getDetails({ id }).then((res) => { - if (res.user) { - res.authentication = 1; - } else { - res.authentication = 2; - } - setTimeout(() => { - setBackfillData(res); - }, 300); - setCurrentType(res.type); - }) - } - - - const getItems = () => [ - { - key: 'ssh', - label: 'SSH Configuration', - children:

      - }, - { - key: 'extendInfo', - label: 'Advanced Configuration', - children:
      - -
      - }, - ]; - - // 测试、保存、修改连接 - function saveConnection(type: submitType) { - const ssh = sshForm.getFieldsValue(); - const baseInfo = baseInfoForm.getFieldsValue(); - const extendInfo: any = []; - const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton'; - extendTableData.map((t: any) => { - if (t.label || t.value) { - extendInfo.push({ - key: t.label, - value: t.value - }) - } - }); - - let p: any = { - ssh, - ...baseInfo, - extendInfo, - // ...values, - ConnectionEnvType: ConnectionEnvType.DAILY, - type: createType! - } - - if (type !== submitType.SAVE) { - p.id = editId; - } - - const api: any = connectionService[type](p); - - setLoading({ - ...loadings, - [loadingsButton]: true - }) - - api.then((res: any) => { - if (type === submitType.TEST) { - message.success(res === false ? i18n('connection.message.testConnectResult', i18n('common.text.failure')) : i18n('connection.message.testConnectResult', i18n('common.text.successful'))); - } else { - submitCallback?.(); - message.success(type === submitType.UPDATE ? i18n('common.message.modifySuccessfully') : i18n('common.message.addedSuccessfully')) - } - }).finally(() => { - setLoading({ - ...loadings, - [loadingsButton]: false - }) - }) - } - - function onCancel() { - closeCreateConnection() - // setEditDataSourceData(false) - } - - function testSSH() { - let p = sshForm.getFieldsValue(); - connectionService.testSSH(p).then(res => { - message.success(i18n('connection.message.testConnectResult', i18n('common.text.successful'))) - }) - } - - return
      - -
      -
      - -
      - {`${editId ? i18n('connection.title.editConnection') : i18n('connection.title.createConnection')} ${databaseMap[currentType]?.name}`} -
      -
      -
      - -
      - -
      -
      - { - - } -
      -
      - - -
      -
      -
      -
      -
      -} - -interface IRenderFormProps { - editId: number | undefined, - databaseType: DatabaseTypeCode, - tab: ITabsType; - form: any; - backfillData: IConnectionDetails; -} - -function RenderForm(props: IRenderFormProps) { - const { editId, databaseType, tab, form, backfillData } = props; - - let aliasChanged = false; - - const dataSourceFormConfigMemo = useMemo(() => { - return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { - return t.type === databaseType - }) - }, []) - - const [dataSourceFormConfig, setDataSourceFormConfig] = useState(dataSourceFormConfigMemo); - - const initialValuesMemo = useMemo(() => { - return initialFormData(dataSourceFormConfigMemo[tab].items) - }, []) - - const [initialValues] = useState(initialValuesMemo); - - useEffect(() => { - if (!backfillData) { - return - } - if (tab === 'baseInfo') { - selectChange({ name: 'authentication', value: backfillData.user ? 1 : 2 }); - regEXFormatting({ url: backfillData.url }, backfillData) - } - - if (tab === 'ssh') { - regEXFormatting({}, backfillData.ssh || {}) - } - }, [backfillData]) - - function initialFormData(dataSourceFormConfig: IFormItem[] | undefined) { - let initValue: any = {}; - dataSourceFormConfig?.map(t => { - initValue[t.name] = t.defaultValue - if (t.selects?.length) { - t.selects?.map(item => { - if (item.value === t.defaultValue) { - initValue = { - ...initValue, - ...initialFormData(item.items) - } - } - }) - } - }) - return initValue - } - - function selectChange(t: { name: string, value: any }) { - dataSourceFormConfig[tab].items.map((j, i) => { - if (j.name === t.name) { - j.defaultValue = t.value - } - }) - setDataSourceFormConfig({ ...dataSourceFormConfig }) - } - - function onFieldsChange(data: any, datas: any) { - // 将antd的格式转换为正常的对象格式 - if (!data.length) { - return - } - const keyName = data[0].name[0]; - const keyValue = data[0].value; - const variableData = { - [keyName]: keyValue - } - const dataObj: any = {} - datas.map((t: any) => { - dataObj[t.name[0]] = t.value - }) - // 正则拆分url/组建url - if (tab === 'baseInfo') { - regEXFormatting(variableData, dataObj); - } - } - - function extractObj(url: any) { - const { template, pattern } = dataSourceFormConfig.baseInfo - // 提取关键词对应的内容 value - const matches = url.match(pattern)!; - // 提取花括号内的关键词 key - const reg = /{(.*?)}/g; - let match; - const arr = []; - while ((match = reg.exec(template)) !== null) { - arr.push(match[1]); - } - // key与value一一对应 - const newExtract: any = {} - arr.map((t, i) => { - newExtract[t] = t === 'database' ? (matches[i + 2] || '') : matches[i + 1] - }) - return newExtract - } - - function regEXFormatting(variableData: { [key: string]: any }, dataObj: { [key: string]: any }) { - const { template, pattern } = dataSourceFormConfig.baseInfo - const keyName = Object.keys(variableData)[0] - const keyValue = variableData[Object.keys(variableData)[0]] - let newData: any = {} - if (keyName === 'url') { - //先判断url是否符合规定的正则 - if (pattern.test(keyValue)) { - newData = extractObj(keyValue); - } - } else if (keyName === 'alias') { - aliasChanged = true - } else { - // 改变上边url动 - let url = template; - Object.keys(dataObj).map(t => { - url = url.replace(`{${t}}`, dataObj[t]) - }) - newData = { - url - } - } - if (keyName === 'host' && !aliasChanged) { - newData.alias = '@' + keyValue - } - console.log({ - ...dataObj, - ...newData, - }) - form.setFieldsValue({ - ...dataObj, - ...newData, - }); - } - - function renderFormItem(t: IFormItem): React.ReactNode { - const label = isEn ? t.labelNameEN : t.labelNameCN; - const name = t.name; - const width = t?.styles?.width || '100%'; - const labelWidth = isEn ? (t?.styles?.labelWidthEN || '100px') : (t?.styles?.labelWidthCN || '70px'); - const labelAlign = t?.styles?.labelAlign || 'left'; - - const FormItemTypes: { [key in InputType]: () => React.ReactNode } = { - [InputType.INPUT]: () => - - , - - [InputType.SELECT]: () => - - , - - [InputType.PASSWORD]: () => - - - } - - return -
      - {FormItemTypes[t.inputType]()} -
      - { - t.selects?.map(item => { - if (t.defaultValue === item.value) { - return item.items?.map(t => renderFormItem(t)) - } - }) - } -
      - } - - return - {dataSourceFormConfig[tab]!.items.map((t => renderFormItem(t)))} - -} -interface IRenderExtendTableProps { - databaseType: DatabaseTypeCode; - backfillData: IConnectionDetails; -} - -let extendTableData: any = [] - -function RenderExtendTable(props: IRenderExtendTableProps) { - const { databaseType, backfillData } = props; - const dataSourceFormConfigMemo = useMemo(() => { - return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { - return t.type === databaseType - }) - }, []) - - const extendInfo = dataSourceFormConfigMemo.extendInfo?.map((t, i) => { - return { - key: i, - label: t.key, - value: t.value - } - }) || [] - - const [data, setData] = useState([...extendInfo, { key: extendInfo.length, label: '', value: '' }]) - - useEffect(() => { - const backfillDataExtendInfo = backfillData?.extendInfo.map((t, i) => { - return { - key: i, - label: t.key, - value: t.value - } - }) || [] - setData([...backfillDataExtendInfo, { key: extendInfo.length, label: '', value: '' }]) - }, [backfillData]) - - useEffect(() => { - extendTableData = data - }, [data]) - - const columns: any = [ - { - title: i18n('connection.tableHeader.name'), - dataIndex: 'label', - width: '60%', - render: (value: any, row: any, index: number) => { - let isCustomLabel = true - - dataSourceFormConfigMemo.extendInfo?.map(item => { - if (item.key === row.label) { - isCustomLabel = false - } - }) - - function change(e: any) { - const newData = [...data] - newData[index] = { - key: index, - label: e.target.value, - value: '' - } - setData(newData) - } - - function blur() { - const newData = [] - data.map(t => { - if (t.label) { - newData.push(t) - } - }) - if (index === data.length - 1 && row.label) { - newData[index] = { - key: index, - label: row.label, - value: '' - } - } - setData([...newData, { key: newData.length, label: '', value: '' }]) - } - - if (index === data.length - 1 || isCustomLabel) { - return - } else { - return {value} - } - } - }, - { - title: i18n('connection.tableHeader.value'), - dataIndex: 'value', - width: '40%', - render: (value: any, row: any, index: number) => { - function change(e: any) { - const newData = [...data] - newData[index] = { - key: index, - label: row.label, - value: e.target.value - } - setData(newData) - } - - function blur() { - - } - - if (index === data.length - 1) { - return - } else { - return - } - } - }, - ]; - - return
      -
- -} \ No newline at end of file diff --git a/front-end/src/pages/main/connections/index.tsx b/front-end/src/pages/main/connections/index.tsx deleted file mode 100644 index c72390a95..000000000 --- a/front-end/src/pages/main/connections/index.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { Fragment, memo, useEffect, useMemo, useRef, useState } from 'react'; -import classnames from 'classnames'; -import i18n from '@/i18n'; - -import CreateConnection from '@/components/CreateConnection'; -import Iconfont from '@/components/Iconfont'; - -import connectionService from '@/service/connection'; -import { DatabaseTypeCode, databaseMap } from '@/constants/database'; -import { databaseTypeList } from '@/constants/database'; -import { IDatabase } from '@/typings/database'; -import { IConnectionDetails } from '@/typings/connection'; - -import type { MenuProps } from 'antd'; -import { Button, Menu, Dropdown } from 'antd'; -import styles from './index.less'; - -type MenuItem = Required['items'][number]; - -function getItem( - label: React.ReactNode, - key: React.Key, - icon?: React.ReactNode, - children?: MenuItem[], - type?: 'group', -): MenuItem { - return { - key, - icon, - children, - label, - type, - } as MenuItem; -} - -interface IProps { -} - -export default memo(function Connections(props) { - const volatileRef = useRef(); - const [createConnectionType, setCreateConnectionType] = useState(); - const [connectionList, setConnectionList] = useState(); - const [checkedConnection, setCheckedConnection] = useState(); - - useEffect(() => { - getDataSource(); - }, []) - - function getDataSource() { - let p = { - pageNo: 1, - pageSize: 999 - } - connectionService.getList(p).then(res => { - setConnectionList(res.data); - }) - } - - function handleCreateConnections(database: IDatabase) { - setCreateConnectionType(database.code); - setCheckedConnection(undefined); - } - - function changeMenu(e: any) { - setCheckedConnection(e.key); - setCreateConnectionType(undefined); - } - - const menuItems: any = useMemo(() => connectionList?.map((t, i) => { - return getItem(t.alias, t.id!, ) - }), [connectionList]); - - return
-
-
- {i18n('connection.title.connections')} -
-
- -
- {/* */} -
-
- { - (createConnectionType || checkedConnection) ? -
- { setCreateConnectionType(undefined); setCheckedConnection(undefined); }} - submitCallback={getDataSource} - /> -
- : -
- { - databaseTypeList.map(t => { - return
-
-
-
- -
- {t.name} -
-
- -
-
-
- }) - } -
-
-
- } -
-
-}); - diff --git a/front-end/src/typings/database.ts b/front-end/src/typings/database.ts deleted file mode 100644 index bb7572bf3..000000000 --- a/front-end/src/typings/database.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { DatabaseTypeCode } from '@/constants/database'; - -export interface IDatabase { - name: string; - code: DatabaseTypeCode; - img: string; - icon: string; -} From 825629786ed14ae8253f11bf77f7dedd5ce0ad64 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 23 Jun 2023 01:25:26 +0800 Subject: [PATCH 0002/1069] feat: style optimization --- .../src/components/CreateConnection/index.tsx | 2 +- .../workspace/components/WorkspaceRight/index.less | 12 +++++++++--- .../workspace/components/WorkspaceRight/index.tsx | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 2c55daea2..70a60ace8 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -474,7 +474,7 @@ function RenderExtendTable(props: IRenderExtendTableProps) { useEffect(() => { const backfillDataExtendInfo = - backfillData?.extendInfo.map((t, i) => { + (backfillData?.extendInfo || []).map((t, i) => { return { key: i, label: t.key, 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 f14f20776..f62ffab07 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -6,9 +6,15 @@ left: 0; right: 0; bottom: 0; + :global { + .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn { + color: #000 !important; + font-weight: bold; + } + } } -.consoleBox{ +.consoleBox { position: absolute; top: 40px; left: 0; @@ -17,11 +23,11 @@ display: none; } -.tabBox{ +.tabBox { padding: 10px 10px 0px; } -.activeConsoleBox{ +.activeConsoleBox { display: block; } 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 ab8cba1ec..db4f24b16 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -194,7 +194,7 @@ export default memo(function WorkspaceRight(props) { onChange={onChange} onEdit={onEdit} type="editable-card" - items={consoleList?.map((t, i) => { + items={(consoleList || [])?.map((t, i) => { return { label: t.name, key: t.id + '', From fdb85d603f8aa7f3b02b86959f39a87e7949db2f Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 11:04:31 +0800 Subject: [PATCH 0003/1069] Optimize startup speed --- .../main/java/ai/chat2db/server/start/Application.java | 2 ++ .../start/config/listener/ManageApplicationListener.java | 6 +++--- .../src/main/resources/application.yml | 2 ++ .../ai/chat2db/server/start/test/TestApplication.java | 2 ++ .../chat2db-server-tools-common/pom.xml | 6 +++++- chat2db-server/pom.xml | 8 ++++++++ 6 files changed, 22 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 92eea0892..c4e752593 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -4,6 +4,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.ComponentScan; +import org.springframework.stereotype.Indexed; /** * 启动类 @@ -13,6 +14,7 @@ @SpringBootApplication @ComponentScan(value = {"ai.chat2db.server"}) @MapperScan("ai.chat2db.server.domain.repository.mapper") +@Indexed public class Application { public static void main(String[] args) { diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java index dc14fbc4d..20ad6e877 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java @@ -2,10 +2,10 @@ import java.time.Duration; -import ai.chat2db.server.tools.base.enums.SystemEnvironmentEnum; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import com.alibaba.fastjson2.JSON; +import ai.chat2db.server.tools.base.enums.SystemEnvironmentEnum; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import com.dtflys.forest.Forest; import com.dtflys.forest.utils.TypeReference; import lombok.extern.slf4j.Slf4j; @@ -37,7 +37,7 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { try { dataResult = Forest.get("http://127.0.0.1:" + serverPort + "/api/system/get-version-a") .connectTimeout(Duration.ofMillis(50)) - .readTimeout(Duration.ofSeconds(1)) + .readTimeout(Duration.ofMillis(100)) .execute(new TypeReference<>() {}); } catch (Exception e) { // 抛出异常 代表没有旧的启动 或者旧的不靠谱 diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application.yml b/chat2db-server/chat2db-server-start/src/main/resources/application.yml index 1d9c19a1a..10369fd62 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application.yml @@ -2,6 +2,8 @@ spring: # 默认开发环境 profiles: active: dev + main: + lazy-initialization: true jpa: # 展示sql show-sql: true diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java index f4defffeb..8a74a8b12 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/TestApplication.java @@ -5,6 +5,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.stereotype.Indexed; /** * 本地环境的启动。 @@ -14,6 +15,7 @@ */ @SpringBootTest(classes = {Application.class}, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j +@Indexed public class TestApplication { public static void main(String[] args) { diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml index e6f3daea1..bd807a8d1 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml @@ -65,10 +65,14 @@ spring-boot-starter - org.zalando logbook-spring-boot-starter + + + org.springframework + spring-context-indexer + diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 6920d6e70..88c5f7f7e 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -209,6 +209,14 @@ logbook-spring-boot-starter 3.1.0 + + + + org.springframework + spring-context-indexer + 6.0.10 + optional + From c8fca8d211389c503f17d5d41dda42d339ce49ea Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 11:27:39 +0800 Subject: [PATCH 0004/1069] Optimize startup speed --- .../src/main/resources/application-dev.yml | 3 - .../main/resources/application-release.yml | 3 - .../src/main/resources/application-test.yml | 4 -- .../src/main/resources/application.yml | 5 +- ...3\345\214\226\344\277\241\346\201\257.sql" | 0 ..._0_2__\344\277\256\346\224\271Console.sql" | 0 .../V1_0_3__\345\242\236\345\212\240SSH.sql" | 0 ...6\345\212\240\346\212\245\350\241\250.sql" | 0 ...0\345\242\236\347\224\250\346\210\267.sql" | 13 ---- ...3\345\214\226\344\277\241\346\201\257.sql" | 44 ------------ ...5\347\275\256\344\277\241\346\201\257.sql" | 11 --- ...V1_0_3_0__\345\242\236\345\212\240SSH.sql" | 9 --- ...3\345\214\226\344\277\241\346\201\257.sql" | 70 ------------------- ..._0_2__\344\277\256\346\224\271Console.sql" | 1 - .../V1_0_3__\345\242\236\345\212\240SSH.sql" | 9 --- 15 files changed, 3 insertions(+), 169 deletions(-) rename "chat2db-server/chat2db-server-start/src/main/resources/db/migration/prerelease/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" => "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" (100%) rename "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_2__\344\277\256\346\224\271Console.sql" => "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_2__\344\277\256\346\224\271Console.sql" (100%) rename "chat2db-server/chat2db-server-start/src/main/resources/db/migration/prerelease/V1_0_3__\345\242\236\345\212\240SSH.sql" => "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_3__\345\242\236\345\212\240SSH.sql" (100%) rename "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_4__\345\242\236\345\212\240\346\212\245\350\241\250.sql" => "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_4__\345\242\236\345\212\240\346\212\245\350\241\250.sql" (100%) delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0_1__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257-\346\226\260\345\242\236\347\224\250\346\210\267.sql" delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_1__\351\205\215\347\275\256\344\277\241\346\201\257.sql" delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_3_0__\345\242\236\345\212\240SSH.sql" delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_2__\344\277\256\346\224\271Console.sql" delete mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_3__\345\242\236\345\212\240SSH.sql" 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 1133e78b1..a907fe231 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 @@ -3,9 +3,6 @@ spring: # 配置自带数据库的相对路径 url: jdbc:h2:~/.chat2db/db/chat2db_dev;MODE=MYSQL driver-class-name: org.h2.Driver - # 用于数据库表结构版本管理 - flyway: - locations: classpath:db/migration/dev h2: console: enabled: true diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application-release.yml b/chat2db-server/chat2db-server-start/src/main/resources/application-release.yml index 0d1a789e4..1d546dcc5 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application-release.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application-release.yml @@ -3,9 +3,6 @@ spring: # 配置自带数据库的相对路径 url: jdbc:h2:~/.chat2db/db/chat2db;MODE=MYSQL driver-class-name: org.h2.Driver - # 用于数据库表结构版本管理 - flyway: - locations: classpath:db/migration/release # 端口号 server: port: 10824 \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application-test.yml b/chat2db-server/chat2db-server-start/src/main/resources/application-test.yml index 9119c0242..b4f3ac2a7 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application-test.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application-test.yml @@ -3,10 +3,6 @@ spring: # 配置自带数据库的相对路径 url: jdbc:h2:~/.chat2db/db/chat2db_test;MODE=MYSQL driver-class-name: org.h2.Driver - # 用于数据库表结构版本管理 - # 测试的表结构还是用dev的 ,但是有自己单独的库 确保多个客户端运行 - flyway: - locations: classpath:db/migration/dev h2: console: enabled: true diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application.yml b/chat2db-server/chat2db-server-start/src/main/resources/application.yml index 10369fd62..d68e9c7d2 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application.yml @@ -2,8 +2,6 @@ spring: # 默认开发环境 profiles: active: dev - main: - lazy-initialization: true jpa: # 展示sql show-sql: true @@ -27,6 +25,9 @@ spring: web: resources: static-locations[0]: classpath:/static/ + # 用于数据库表结构版本管理 + flyway: + locations: classpath:db/migration ali: dbhub: version: 1.0.0 diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/prerelease/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" similarity index 100% rename from "chat2db-server/chat2db-server-start/src/main/resources/db/migration/prerelease/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" rename to "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_2__\344\277\256\346\224\271Console.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_2__\344\277\256\346\224\271Console.sql" similarity index 100% rename from "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_2__\344\277\256\346\224\271Console.sql" rename to "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_2__\344\277\256\346\224\271Console.sql" diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/prerelease/V1_0_3__\345\242\236\345\212\240SSH.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_3__\345\242\236\345\212\240SSH.sql" similarity index 100% rename from "chat2db-server/chat2db-server-start/src/main/resources/db/migration/prerelease/V1_0_3__\345\242\236\345\212\240SSH.sql" rename to "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_3__\345\242\236\345\212\240SSH.sql" diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_4__\345\242\236\345\212\240\346\212\245\350\241\250.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_4__\345\242\236\345\212\240\346\212\245\350\241\250.sql" similarity index 100% rename from "chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_4__\345\242\236\345\212\240\346\212\245\350\241\250.sql" rename to "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_4__\345\242\236\345\212\240\346\212\245\350\241\250.sql" diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0_1__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257-\346\226\260\345\242\236\347\224\250\346\210\267.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0_1__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257-\346\226\260\345\242\236\347\224\250\346\210\267.sql" deleted file mode 100644 index 2032373e6..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0_1__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257-\346\226\260\345\242\236\347\224\250\346\210\267.sql" +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS `dbhub_user` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `user_name` varchar(32) NOT NULL COMMENT '用户名', - `password` varchar(256) DEFAULT NULL COMMENT '密码', - `nick_name` varchar(256) DEFAULT NULL COMMENT '昵称', - `email` varchar(256) DEFAULT NULL COMMENT '邮箱', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据源连接表' -; - -INSERT INTO `dbhub_user` (`user_name`,`password`,`nick_name`) VALUES ('dbhub','$2a$10$yElafjDHPoPHSaCo6cjJGuWmtXWNVz/cOOOtDg99eNfvUfalzfane','管理员'); \ No newline at end of file diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" deleted file mode 100644 index 9a01271e5..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" +++ /dev/null @@ -1,44 +0,0 @@ -CREATE TABLE IF NOT EXISTS `data_source` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL COMMENT '创建时间', - `gmt_modified` datetime NOT NULL COMMENT '修改时间', - `alias` varchar(128) DEFAULT NULL COMMENT '别名', - `url` varchar(1024) DEFAULT NULL COMMENT '连接地址', - `user_name` varchar(128) DEFAULT NULL COMMENT '用户名', - `password` varchar(256) DEFAULT NULL COMMENT '密码', - `type` varchar(32) DEFAULT NULL COMMENT '数据库类型', - `env_type` varchar(32) DEFAULT NULL COMMENT '环境类型', - `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='数据源连接表' -; - -CREATE TABLE IF NOT EXISTS `operation_log` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', - `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', - `type` varchar(32) NOT NULL COMMENT '数据库类型', - `ddl` text DEFAULT NULL COMMENT 'ddl内容', - `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='我的执行记录表' -; - -CREATE TABLE IF NOT EXISTS `operation_saved` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', - `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', - `name` varchar(128) DEFAULT NULL COMMENT '保存名称', - `type` varchar(32) NOT NULL COMMENT '数据库类型', - `status` varchar(32) NOT NULL COMMENT 'ddl语句状态:DRAFT/RELEASE', - `ddl` text DEFAULT NULL COMMENT 'ddl内容', - `tab_opened` text DEFAULT NULL COMMENT '是否在tab中被打开,y表示打开,n表示未打开', - `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='我的保存表' -; - diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_1__\351\205\215\347\275\256\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_1__\351\205\215\347\275\256\344\277\241\346\201\257.sql" deleted file mode 100644 index dedacb0d7..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_1__\351\205\215\347\275\256\344\277\241\346\201\257.sql" +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE IF NOT EXISTS `system_config` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `code` varchar(32) NOT NULL COMMENT '配置项编码', - `content` varchar(256) DEFAULT NULL COMMENT '配置项内容', - `summary` varchar(256) DEFAULT NULL COMMENT '配置项说明', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置中心表' -; -create UNIQUE INDEX uk_code on system_config(code) ; \ No newline at end of file diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_3_0__\345\242\236\345\212\240SSH.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_3_0__\345\242\236\345\212\240SSH.sql" deleted file mode 100644 index cf53fdeca..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/dev/V1_0_3_0__\345\242\236\345\212\240SSH.sql" +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE `data_source` ADD COLUMN `host` varchar(128) NULL COMMENT 'host地址'; -ALTER TABLE `data_source` ADD COLUMN `port` varchar(128) NULL COMMENT '端口'; -ALTER TABLE `data_source` ADD COLUMN `ssh` varchar(1024) NULL COMMENT 'ssh配置信息json'; -ALTER TABLE `data_source` ADD COLUMN `ssl` varchar(1024) NULL COMMENT 'ssl配置信息json'; -ALTER TABLE `data_source` ADD COLUMN `sid` varchar(32) NULL COMMENT 'sid'; -ALTER TABLE `data_source` ADD COLUMN `driver` varchar(128) NULL COMMENT '驱动信息'; -ALTER TABLE `data_source` ADD COLUMN `jdbc` varchar(128) NULL COMMENT 'jdbc版本'; -ALTER TABLE `data_source` ADD COLUMN `extend_info` varchar(4096) NULL COMMENT '自定义扩展字段json'; -create INDEX idx_user_id on data_source(user_id) ; \ No newline at end of file diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" deleted file mode 100644 index ffd95012a..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_0__\345\210\235\345\247\213\345\214\226\344\277\241\346\201\257.sql" +++ /dev/null @@ -1,70 +0,0 @@ -CREATE TABLE IF NOT EXISTS `data_source` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL COMMENT '创建时间', - `gmt_modified` datetime NOT NULL COMMENT '修改时间', - `alias` varchar(128) DEFAULT NULL COMMENT '别名', - `url` varchar(1024) DEFAULT NULL COMMENT '连接地址', - `user_name` varchar(128) DEFAULT NULL COMMENT '用户名', - `password` varchar(256) DEFAULT NULL COMMENT '密码', - `type` varchar(32) DEFAULT NULL COMMENT '数据库类型', - `env_type` varchar(32) DEFAULT NULL COMMENT '环境类型', - `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='数据源连接表' -; - -CREATE TABLE IF NOT EXISTS `operation_log` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', - `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', - `type` varchar(32) NOT NULL COMMENT '数据库类型', - `ddl` text DEFAULT NULL COMMENT 'ddl内容', - `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='我的执行记录表' -; - -CREATE TABLE IF NOT EXISTS `operation_saved` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', - `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', - `name` varchar(128) DEFAULT NULL COMMENT '保存名称', - `type` varchar(32) NOT NULL COMMENT '数据库类型', - `status` varchar(32) NOT NULL COMMENT 'ddl语句状态:DRAFT/RELEASE', - `ddl` text DEFAULT NULL COMMENT 'ddl内容', - `tab_opened` text DEFAULT NULL COMMENT '是否在tab中被打开,y表示打开,n表示未打开', - `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='我的保存表' -; - - -CREATE TABLE IF NOT EXISTS `dbhub_user` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `user_name` varchar(32) NOT NULL COMMENT '用户名', - `password` varchar(256) DEFAULT NULL COMMENT '密码', - `nick_name` varchar(256) DEFAULT NULL COMMENT '昵称', - `email` varchar(256) DEFAULT NULL COMMENT '邮箱', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据源连接表' -; - -CREATE TABLE IF NOT EXISTS `system_config` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `code` varchar(32) NOT NULL COMMENT '配置项编码', - `content` varchar(256) DEFAULT NULL COMMENT '配置项内容', - `summary` varchar(256) DEFAULT NULL COMMENT '配置项说明', - PRIMARY KEY (`id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='配置中心表' -; -create UNIQUE INDEX uk_code on system_config(code) ; - -INSERT INTO `dbhub_user` (`user_name`,`password`,`nick_name`) VALUES ('dbhub','$2a$10$yElafjDHPoPHSaCo6cjJGuWmtXWNVz/cOOOtDg99eNfvUfalzfane','管理员'); \ No newline at end of file diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_2__\344\277\256\346\224\271Console.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_2__\344\277\256\346\224\271Console.sql" deleted file mode 100644 index 8888da649..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_2__\344\277\256\346\224\271Console.sql" +++ /dev/null @@ -1 +0,0 @@ -ALTER TABLE `operation_saved` ADD COLUMN `db_schema_name` varchar(128) NULL COMMENT 'schema名称'; diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_3__\345\242\236\345\212\240SSH.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_3__\345\242\236\345\212\240SSH.sql" deleted file mode 100644 index cf53fdeca..000000000 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/release/V1_0_3__\345\242\236\345\212\240SSH.sql" +++ /dev/null @@ -1,9 +0,0 @@ -ALTER TABLE `data_source` ADD COLUMN `host` varchar(128) NULL COMMENT 'host地址'; -ALTER TABLE `data_source` ADD COLUMN `port` varchar(128) NULL COMMENT '端口'; -ALTER TABLE `data_source` ADD COLUMN `ssh` varchar(1024) NULL COMMENT 'ssh配置信息json'; -ALTER TABLE `data_source` ADD COLUMN `ssl` varchar(1024) NULL COMMENT 'ssl配置信息json'; -ALTER TABLE `data_source` ADD COLUMN `sid` varchar(32) NULL COMMENT 'sid'; -ALTER TABLE `data_source` ADD COLUMN `driver` varchar(128) NULL COMMENT '驱动信息'; -ALTER TABLE `data_source` ADD COLUMN `jdbc` varchar(128) NULL COMMENT 'jdbc版本'; -ALTER TABLE `data_source` ADD COLUMN `extend_info` varchar(4096) NULL COMMENT '自定义扩展字段json'; -create INDEX idx_user_id on data_source(user_id) ; \ No newline at end of file From 70a534ef1f187133105ed4667199b97f5af3fa75 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 14:40:13 +0800 Subject: [PATCH 0005/1069] test --- .github/workflows/test.yml | 81 ++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f395f6abc..c1a3686d0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,42 +1,39 @@ -#name: Test -# -#on: [ push, pull_request ] -# -#jobs: -# test: -# strategy: -# fail-fast: false -# matrix: -# include: -# - os: windows-latest -## - os: macos-latest -## arch: amd64 -## - os: macos-latest -## arch: arm64 -## variant: v8 -# runs-on: ${{ matrix.os }} -# steps: -# - name: Check out Git repository -# uses: actions/checkout@main -# -# # 安装jre Windows -# - name: Install Jre for Windows -# uses: actions/setup-java@main -# with: -# java-version: "17" -# distribution: "temurin" -# java-package: "jre" -# -# # java.security 开放tls1 Windows -# - name: Enable tls1 -# run: | -# sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" -# cat "${{ env.JAVA_HOME }}/conf/security/java.security" -# -## -## # 编译服务端java版本 -## - name: Build Java -## run: mvn clean package -B '-Dmaven.test.skip=true' -f ali-dbhub-server/pom.xml -# -## - name: Test with Maven -## run: mvn test -B -Dmaven.test.skip=false -pl ali-dbhub-server-test -am -f ali-dbhub-server/pom.xml \ No newline at end of file +name: Test + +on: [ push, pull_request ] + +jobs: + test: + strategy: + fail-fast: false + matrix: + include: + - os: windows-latest + - os: macos-latest + runs-on: ${{ matrix.os }} + steps: + - name: Check out Git repository + uses: actions/checkout@main + + # 安装jre Windows + - name: Install Jre for Windows + 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" + cat "${{ 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 + cat $JAVA_HOME/conf/security/java.security \ No newline at end of file From 78d77b59bbab265c0184056a4eb14d23524c47dd Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 14:45:08 +0800 Subject: [PATCH 0006/1069] Supports tls1.0 --- .github/workflows/release.yml | 12 ++++++ .github/workflows/test.yml | 78 +++++++++++++++++------------------ 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8bfaf8cf1..17b2c207a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,6 +72,18 @@ jobs: java-package: "jre" architecture: "aarch64" + # 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 + # 复制jre Windows - name: Copy Jre for Windows if: ${{ runner.os == 'Windows' }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1a3686d0..4b353c6db 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,39 +1,39 @@ -name: Test - -on: [ push, pull_request ] - -jobs: - test: - strategy: - fail-fast: false - matrix: - include: - - os: windows-latest - - os: macos-latest - runs-on: ${{ matrix.os }} - steps: - - name: Check out Git repository - uses: actions/checkout@main - - # 安装jre Windows - - name: Install Jre for Windows - 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" - cat "${{ 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 - cat $JAVA_HOME/conf/security/java.security \ No newline at end of file +#name: Test +# +#on: [ push, pull_request ] +# +#jobs: +# test: +# strategy: +# fail-fast: false +# matrix: +# include: +# - os: windows-latest +# - os: macos-latest +# runs-on: ${{ matrix.os }} +# steps: +# - name: Check out Git repository +# uses: actions/checkout@main +# +# # 安装jre Windows +# - name: Install Jre for Windows +# 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" +# cat "${{ 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 +# cat $JAVA_HOME/conf/security/java.security \ No newline at end of file From 50d6253ce7a96bd522c4299da77322ea24a66b54 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 14:53:22 +0800 Subject: [PATCH 0007/1069] @Valid --- .../api/controller/rdb/RdbDdlController.java | 35 ++++++++++--------- .../rdb/request/TableDetailQueryRequest.java | 4 +-- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java index 740cb8da7..c09758b9b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java @@ -41,6 +41,7 @@ import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import com.google.common.collect.Lists; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -79,7 +80,7 @@ public class RdbDdlController { * @return */ @GetMapping("/list") - public WebPageResult list(TableBriefQueryRequest request) { + public WebPageResult list(@Valid TableBriefQueryRequest request) { TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(false); @@ -98,7 +99,7 @@ public WebPageResult list(TableBriefQueryRequest request) { * @return */ @GetMapping("/schema_list") - public ListResult schemaList(DataSourceBaseRequest request) { + public ListResult schemaList(@Valid DataSourceBaseRequest request) { SchemaQueryParam queryParam = SchemaQueryParam.builder().dataBaseName(request.getDatabaseName()).build(); ListResult tableColumns = databaseService.querySchema(queryParam); List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); @@ -112,7 +113,7 @@ public ListResult schemaList(DataSourceBaseRequest request) { * @return */ @PostMapping("/delete_database") - public ActionResult deleteDatabase(@RequestBody DataSourceBaseRequest request) { + public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest request) { DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); return databaseService.deleteDatabase(param); } @@ -124,7 +125,7 @@ public ActionResult deleteDatabase(@RequestBody DataSourceBaseRequest request) { * @return */ @PostMapping("/create_database") - public ActionResult createDatabase(@RequestBody DataSourceBaseRequest request) { + public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest request) { DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); return databaseService.createDatabase(param); } @@ -136,7 +137,7 @@ public ActionResult createDatabase(@RequestBody DataSourceBaseRequest request) { * @return */ @PostMapping("/modify_database") - public ActionResult modifyDatabase(@RequestBody UpdateDatabaseRequest request) { + public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()) .newDatabaseName(request.getNewDatabaseName()).build(); return databaseService.modifyDatabase(param); @@ -149,7 +150,7 @@ public ActionResult modifyDatabase(@RequestBody UpdateDatabaseRequest request) { * @return */ @PostMapping("/delete_schema") - public ActionResult deleteSchema(@RequestBody DataSourceBaseRequest request) { + public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).build(); return databaseService.deleteSchema(param); @@ -162,7 +163,7 @@ public ActionResult deleteSchema(@RequestBody DataSourceBaseRequest request) { * @return */ @PostMapping("/create_schema") - public ActionResult createSchema(@RequestBody DataSourceBaseRequest request) { + public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).build(); return databaseService.createSchema(param); @@ -175,7 +176,7 @@ public ActionResult createSchema(@RequestBody DataSourceBaseRequest request) { * @return */ @PostMapping("/modify_schema") - public ActionResult modifySchema(@RequestBody UpdateSchemaRequest request) { + public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); return databaseService.modifySchema(param); @@ -189,7 +190,7 @@ public ActionResult modifySchema(@RequestBody UpdateSchemaRequest request) { * @return */ @GetMapping("/column_list") - public ListResult columnList(TableDetailQueryRequest request) { + public ListResult columnList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableColumns = tableService.queryColumns(queryParam); List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); @@ -203,7 +204,7 @@ public ListResult columnList(TableDetailQueryRequest request) { * @return */ @GetMapping("/index_list") - public ListResult indexList(TableDetailQueryRequest request) { + public ListResult indexList(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); List tableIndices = tableService.queryIndexes(queryParam); List indexVOS = rdbWebConverter.indexDto2vo(tableIndices); @@ -217,7 +218,7 @@ public ListResult indexList(TableDetailQueryRequest request) { * @return */ @GetMapping("/key_list") - public ListResult keyList(TableDetailQueryRequest request) { + public ListResult keyList(@Valid TableDetailQueryRequest request) { // TODO 增加查询key实现 return ListResult.of(Lists.newArrayList()); } @@ -229,7 +230,7 @@ public ListResult keyList(TableDetailQueryRequest request) { * @return */ @GetMapping("/export") - public DataResult export(DdlExportRequest request) { + public DataResult export(@Valid DdlExportRequest request) { ShowCreateTableParam param = rdbWebConverter.ddlExport2showCreate(request); return tableService.showCreateTable(param); } @@ -241,7 +242,7 @@ public DataResult export(DdlExportRequest request) { * @return */ @GetMapping("/create/example") - public DataResult createExample(TableCreateDdlQueryRequest request) { + public DataResult createExample(@Valid TableCreateDdlQueryRequest request) { return tableService.createTableExample(request.getDbType()); } @@ -252,7 +253,7 @@ public DataResult createExample(TableCreateDdlQueryRequest request) { * @return */ @GetMapping("/update/example") - public DataResult updateExample(TableUpdateDdlQueryRequest request) { + public DataResult updateExample(@Valid TableUpdateDdlQueryRequest request) { return tableService.alterTableExample(request.getDbType()); } @@ -263,7 +264,7 @@ public DataResult updateExample(TableUpdateDdlQueryRequest request) { * @return */ @GetMapping("/query") - public DataResult query(TableDetailQueryRequest request) { + public DataResult query(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); @@ -280,7 +281,7 @@ public DataResult query(TableDetailQueryRequest request) { * @return */ @GetMapping("/modify/sql") - public ListResult modifySql(TableModifySqlRequest request) { + public ListResult modifySql(@Valid TableModifySqlRequest request) { return tableService.buildSql( rdbWebConverter.tableRequest2param(request.getOldTable()), rdbWebConverter.tableRequest2param(request.getNewTable())) @@ -294,7 +295,7 @@ public ListResult modifySql(TableModifySqlRequest request) { * @return */ @PostMapping("/delete") - public ActionResult delete(@RequestBody TableDeleteRequest request) { + public ActionResult delete(@Valid @RequestBody TableDeleteRequest request) { DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); return tableService.drop(dropParam); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDetailQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDetailQueryRequest.java index f78a1e364..073407242 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDetailQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableDetailQueryRequest.java @@ -1,9 +1,7 @@ package ai.chat2db.server.web.api.controller.rdb.request; -import jakarta.validation.constraints.NotNull; - import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; - +import jakarta.validation.constraints.NotNull; import lombok.Data; /** From db8d56a15ed83e0a0ab2d79e64d7915d58e9e739 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 15:00:49 +0800 Subject: [PATCH 0008/1069] Example Modify the verification prompt --- .../start/exception/convertor/ExceptionConvertorUtils.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ExceptionConvertorUtils.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ExceptionConvertorUtils.java index 10d828623..a66c5a504 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ExceptionConvertorUtils.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ExceptionConvertorUtils.java @@ -3,10 +3,10 @@ import java.util.List; import ai.chat2db.server.tools.base.constant.SymbolConstant; - import ai.chat2db.server.tools.common.util.I18nUtils; import org.springframework.util.CollectionUtils; import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; /** @@ -35,6 +35,10 @@ public static String buildMessage(BindingResult result) { msg.append(index++); // 得到错误消息 msg.append(SymbolConstant.DOT); + if (e instanceof FieldError fieldError) { + msg.append(fieldError.getField()); + msg.append(" : "); + } msg.append(e.getDefaultMessage()); msg.append(SymbolConstant.SEMICOLON); } From 52855872a126614bf8834e6adb8822da7bddd54e Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Fri, 23 Jun 2023 15:03:36 +0800 Subject: [PATCH 0009/1069] Example Modify the verification prompt --- .../server/domain/core/impl/DlTemplateServiceImpl.java | 8 +------- .../server/web/api/aspect/ConnectionInfoHandler.java | 9 ++++----- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 9e7a5eb85..4a0178b04 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -27,12 +27,6 @@ import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; -import com.alibaba.druid.DbType; -import com.alibaba.druid.sql.PagerUtils; -import com.alibaba.druid.sql.SQLUtils; -import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; -import com.alibaba.druid.sql.parser.SQLParserUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; @@ -123,7 +117,7 @@ public DataResult count(DlCountParam param) { // 解析sql分页 SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); if (!(sqlStatement instanceof SQLSelectStatement)) { - throw new BusinessException("当前sql不是查询语句"); + throw new BusinessException("dataSource.sqlAnalysisError"); } sql = PagerUtils.count(sql, dbType); ExecuteResult executeResult = execute(sql); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index 6747520ba..2029d3654 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -6,13 +6,12 @@ import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.service.DataSourceService; -import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.sql.ConnectInfo; -import ai.chat2db.server.tools.base.excption.SystemException; import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; - +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -62,7 +61,7 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); if (!result.success() && dataSource != null) { - throw new SystemException("dataSourceId ERROR"); + throw new ParamBusinessException("dataSourceId"); } ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setAlias(dataSource.getAlias()); From 64478ca4b507b863fd0cda150d9f7d33881d9e10 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Fri, 23 Jun 2023 15:05:42 +0800 Subject: [PATCH 0010/1069] Support for custom drivers --- .../domain/core/impl/DataSourceServiceImpl.java | 17 +++++++++-------- .../java/ai/chat2db/spi/sql/Chat2DBContext.java | 4 ++++ 2 files changed, 13 insertions(+), 8 deletions(-) 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 ae2284a62..db42e502d 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 @@ -114,17 +114,18 @@ public ListResult queryByIds(List ids) { } @Override - public ActionResult preConnect(DataSourcePreConnectParam param) { + public ActionResult preConnect(DataSourcePreConnectParam param) { DataSourceTestParam testParam - = dataSourceConverter.param2param(param); - - DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver(param.getDriver()); - driverConfig.setJdbcDriverClass(param.getDriver());//todo + = dataSourceConverter.param2param(param); +// todo +// DriverConfig driverConfig = new DriverConfig(); +// driverConfig.setJdbcDriver(param.getDriver()); +// driverConfig.setJdbcDriverClass(param.getDriver()); + DriverConfig driverConfig = Chat2DBContext.getDefaultDriverConfig(param.getType()); DataSourceConnect dataSourceConnect = JdbcUtils.testConnect(testParam.getUrl(), testParam.getHost(), - testParam.getPort(), - testParam.getUsername(), testParam.getPassword(),testParam.getDbType(), + testParam.getPort(), + testParam.getUsername(), testParam.getPassword(), testParam.getDbType(), driverConfig, param.getSsh(), KeyValue.toMap(param.getExtendInfo())); if (BooleanUtils.isNotTrue(dataSourceConnect.getSuccess())) { return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription()); 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 ba7a655db..241fd3dce 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 @@ -46,6 +46,10 @@ public class Chat2DBContext { } } + public static DriverConfig getDefaultDriverConfig(String dbType){ + return PLUGIN_MAP.get(dbType).getDBConfig().getDefaultDriverConfig(); + } + /** * 获取当前线程的ContentContext * From 02977da3a83f3025dc2fe647c91dbad6ecbb08ab Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Fri, 23 Jun 2023 15:54:53 +0800 Subject: [PATCH 0011/1069] Support for custom drivers --- .../ai/chat2db/spi/sql/Chat2DBContext.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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 241fd3dce..246031c8f 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 @@ -46,7 +46,7 @@ public class Chat2DBContext { } } - public static DriverConfig getDefaultDriverConfig(String dbType){ + public static DriverConfig getDefaultDriverConfig(String dbType) { return PLUGIN_MAP.get(dbType).getDBConfig().getDefaultDriverConfig(); } @@ -63,7 +63,7 @@ public static MetaData getMetaData() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getMetaData(); } - public static DBConfig getDBConfig(){ + public static DBConfig getDBConfig() { return PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBConfig(); } @@ -103,9 +103,15 @@ private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { if (session != null) { url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); } + DriverConfig config = connectInfo.getDriverConfig(); + if (config == null) { + config = getDefaultDriverConfig(connectInfo.getDbType()); + connectInfo.setDriverConfig(config); + } + connection = getConnect(url, host, port, connectInfo.getUser(), - connectInfo.getPassword(), connectInfo.getDbType(), - connectInfo.getDriverConfig(), ssh, connectInfo.getExtendMap()); + connectInfo.getPassword(), connectInfo.getDbType(), + connectInfo.getDriverConfig(), ssh, connectInfo.getExtendMap()); } catch (Exception e1) { log.error("getConnect error", e1); if (connection != null) { @@ -139,8 +145,8 @@ private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { * @return */ private static Connection getConnect(String url, String host, String port, - String userName, String password, String dbType, - DriverConfig jdbc, SSHInfo ssh, Map properties) throws SQLException { + String userName, String password, String dbType, + DriverConfig jdbc, SSHInfo ssh, Map properties) throws SQLException { // 创建连接 return IDriverManager.getConnection(url, userName, password, jdbc, properties); From 104364bcb10d2112d75a6695f5f7986e6e8570ad Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Fri, 23 Jun 2023 15:57:20 +0800 Subject: [PATCH 0012/1069] bugfix --- .../src/main/resources/i18n/messages.properties | 2 +- .../src/main/resources/i18n/messages_en_US.properties | 2 +- .../src/main/resources/i18n/messages_zh_CN.properties | 2 +- .../ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties index a2203e599..0148a15be 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties @@ -1,4 +1,4 @@ -common.businessError=请尝试重新提交或者刷新页面 +common.businessError=Please try resubmitting or refreshing the page later common.systemError=系统开小差啦,请尝试刷新页面或者联系管理员 common.needLoggedIn=需要登陆页面 common.redirect=重定向页面 diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index a2203e599..0148a15be 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -1,4 +1,4 @@ -common.businessError=请尝试重新提交或者刷新页面 +common.businessError=Please try resubmitting or refreshing the page later common.systemError=系统开小差啦,请尝试刷新页面或者联系管理员 common.needLoggedIn=需要登陆页面 common.redirect=重定向页面 diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index a2203e599..8dc0f5d2f 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -1,5 +1,5 @@ common.businessError=请尝试重新提交或者刷新页面 -common.systemError=系统开小差啦,请尝试刷新页面或者联系管理员 +common.systemError=系统发生异常,可在帮助中查看异常详情,如无法解决,可关注微信公众号Text2SQL咨询帮助 common.needLoggedIn=需要登陆页面 common.redirect=重定向页面 common.paramError=您输入的参数异常 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index 2029d3654..0871a2885 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -60,7 +60,7 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); - if (!result.success() && dataSource != null) { + if (!result.success() && dataSource == null) { throw new ParamBusinessException("dataSourceId"); } ConnectInfo connectInfo = new ConnectInfo(); From c99be63d2f9ba3d346bd2159aaa6ddb6fb0e8f2c Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Fri, 23 Jun 2023 16:35:26 +0800 Subject: [PATCH 0013/1069] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java | 2 +- .../src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java | 2 +- .../chat2db/server/tools/base/constant/EasyToolsConstant.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java index 8a83df251..a4ab7fd80 100644 --- a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java @@ -13,7 +13,7 @@ public void connectDatabase(String database) { return; } try { - SQLExecutor.getInstance().execute("use `" + database + "`;", null); + SQLExecutor.getInstance().execute("use `" + database + "`;"); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java index b22222e0c..958e13fe2 100644 --- a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java +++ b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java @@ -20,6 +20,6 @@ public MetaData getMetaData() { @Override public DBManage getDBManage() { - return null; + return new MysqlDBManage(); } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/constant/EasyToolsConstant.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/constant/EasyToolsConstant.java index 7d100cd16..4b0dece41 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/constant/EasyToolsConstant.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/constant/EasyToolsConstant.java @@ -15,7 +15,7 @@ public interface EasyToolsConstant { /** * 最大分页大小 */ - int MAX_PAGE_SIZE = 500; + int MAX_PAGE_SIZE = 1000; /** * 序列化id From a9ecc6f4ef9963d6d422962b2689f2d94f756975 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 23 Jun 2023 17:05:06 +0800 Subject: [PATCH 0014/1069] feat: FE Packages --- .vscode/settings.json | 14 ++++++ chat2db-client/.umirc.prod.ts | 15 +++--- chat2db-client/.umirc.ts | 5 ++ chat2db-client/package.json | 5 +- .../src/components/CreateConnection/index.tsx | 8 ++-- .../src/components/Iconfont/index.tsx | 22 +++++---- chat2db-client/src/main/constants.js | 4 +- chat2db-client/src/main/index.js | 47 +++++++++++++++++-- chat2db-client/src/main/menu.js | 9 ++-- chat2db-client/src/main/preload.js | 7 ++- chat2db-client/src/main/utils.js | 6 +-- chat2db-client/src/pages/document.ejs | 5 +- .../pages/main/dashboard/chart-item/index.tsx | 2 - .../server/domain/support/util/JdbcUtils.java | 3 +- 14 files changed, 110 insertions(+), 42 deletions(-) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..8f55d13bc --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,14 @@ +{ + "cSpell.words": [ + "ahooks", + "antd", + "asar", + "Dserver", + "Dspring", + "echarts", + "nsis", + "pgsql", + "Sercurity", + "sortablejs" + ] +} diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 5773e983b..9c0718d29 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -1,7 +1,7 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -const UMI_PublicPath = process.env.UMI_PublicPath || '/static/front/'; +const UMI_PublicPath = process.env.UMI_PublicPath || './'; const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ @@ -10,13 +10,16 @@ const chainWebpack = (config: any, { webpack }: any) => { }, ]); - config.plugin('define').use(require('webpack').DefinePlugin, [{ - __BUILD_TIME__: JSON.stringify(formatDate(new Date(),'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }]); + config.plugin('define').use(require('webpack').DefinePlugin, [ + { + __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), + __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), + }, + ]); }; export default defineConfig({ publicPath: UMI_PublicPath, - chainWebpack + chainWebpack, + headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], }); diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index d5e411d51..609f9b26c 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -19,8 +19,12 @@ const chainWebpack = (config: any, { webpack }: any) => { export default defineConfig({ title: 'Chat2DB', + history: { + type: 'hash', + }, base: '/', publicPath: '/', + hash: false, routes: [ { path: '/demo', component: '@/pages/demo' }, { path: '/connections', component: 'main' }, @@ -39,4 +43,5 @@ export default defineConfig({ changeOrigin: true, }, }, + headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index de469adbc..f3d846e46 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -8,7 +8,9 @@ "build": "npm run build:web && npm run build:main", "build:main": "cross-env NODE_ENV=development electron-builder", "build:main:prod": "cross-env NODE_ENV=production electron-builder", + "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:web": "umi build", + "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", "postinstall": "umi setup", "lint": "umi lint", "start": "concurrently \"npm run start:web\" \"npm run start:main\"", @@ -59,9 +61,10 @@ "output": "release/" }, "productName": "Chat2DB", - "asar": true, + "asar": false, "files": [ "dist/", + "static/", "src/main", "node_modules/", "package.json" diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 70a60ace8..71977def4 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -350,10 +350,10 @@ function RenderForm(props: IRenderFormProps) { if (keyName === 'host' && !aliasChanged) { newData.alias = '@' + keyValue; } - console.log({ - ...dataObj, - ...newData, - }); + // console.log({ + // ...dataObj, + // ...newData, + // }); form.setFieldsValue({ ...dataObj, ...newData, diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 7c6bff63c..dce65187b 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -1,14 +1,20 @@ import React, { PureComponent } from 'react'; import classnames from 'classnames'; -// import desktopStyle from './desktop.less'; -// TODO: windows端加载cdn资源报错 +import desktopStyle from './desktop.less'; import prodStyle from './prod.less'; -export default class Iconfont extends PureComponent<{ - code: string; -} & React.DetailedHTMLProps, HTMLElement>> { + +export default class Iconfont extends PureComponent< + { + code: string; + } & React.DetailedHTMLProps, HTMLElement> +> { render() { - // const styles = window._ENV !== 'prod' ? desktopStyle : prodStyle - const styles = prodStyle; - return {this.props.code} + const styles = window._ENV !== 'prod' ? desktopStyle : prodStyle; + // const styles = prodStyle; + return ( + + {this.props.code} + + ); } } diff --git a/chat2db-client/src/main/constants.js b/chat2db-client/src/main/constants.js index 8025c5a05..f2b037aac 100644 --- a/chat2db-client/src/main/constants.js +++ b/chat2db-client/src/main/constants.js @@ -1,12 +1,12 @@ const DEV_WEB_URL = 'http://localhost:8000/'; /** jar包名 */ -const JAVA_APP_NAME = 'ali-dbhub-server-start.jar'; +const JAVA_APP_NAME = 'chat2db-server-start.jar'; const JAVA_PATH = 'jre/bin/java'; module.exports = { DEV_WEB_URL, - + JAVA_APP_NAME, JAVA_PATH, }; diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index 63e188e79..ac5d0c96f 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -1,5 +1,7 @@ -const { app, BrowserWindow, Menu, ipcMain } = require('electron'); +const { app, BrowserWindow, Menu, shell, net, ipcMain, dialog } = require('electron'); const path = require('path'); +const os = require('os'); +const fs = require('fs'); const menuBar = require('./menu'); const { loadMainResource } = require('./utils'); let mainWindow = null; @@ -9,12 +11,25 @@ function createWindow() { minWidth: 1080, minHeight: 720, show: false, + webPreferences: { + webSercurity: false, + nodeIntegration: true, + contextIsolation: true, + preload: path.join(__dirname, 'preload.js'), + }, }); mainWindow.maximize(); mainWindow.show(); + // 加载应用----- loadMainResource(mainWindow); + // 关闭window时触发下列事件. + mainWindow.on('closed', function (event) { + event.preventDefault(); + mainWindow = null; + }); + // 监听打开新窗口事件 用默认浏览器打开 mainWindow.webContents.on('new-window', function (event, url) { event.preventDefault(); @@ -26,14 +41,16 @@ function createWindow() { }); } -const menu = Menu.buildFromTemplate(menuBar); -Menu.setApplicationMenu(menu); +// const menu = Menu.buildFromTemplate(menuBar); +// Menu.setApplicationMenu(menu); app.on('ready', () => { createWindow(); app.on('activate', function () { - if (BrowserWindow.getAllWindows().length === 0) createWindow(); + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } }); }); app.on('window-all-closed', () => { @@ -42,8 +59,30 @@ app.on('window-all-closed', () => { } }); +app.on('before-quit', (event) => { + const request = net.request({ + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + url: 'http://127.0.0.1:10824/api/system/stop', + }); + request.write(JSON.stringify({})); + request.on('response', (response) => { + response.on('data', (res) => { + let data = JSON.parse(res.toString()); + }); + response.on('end', () => {}); + }); + request.end(); +}); + ipcMain.handle('get-product-name', (event) => { const exePath = app.getPath('exe'); const { name } = path.parse(exePath); return name; }); + +module.exports = { + mainWindow, +}; diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index a81f60f93..54d049ac2 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -1,4 +1,7 @@ const i18n = require('./i18n'); +const { shell } = require('electron'); +const { mainWindow } = require('./index'); + const menuBar = [ { label: 'Chat2DB', @@ -37,8 +40,7 @@ const menuBar = [ submenu: [ { label: '打开日志', - accelerator: - process.platform === 'darwin' ? 'Cmd+Shift+L' : 'Ctrl+Shift+L', + accelerator: process.platform === 'darwin' ? 'Cmd+Shift+L' : 'Ctrl+Shift+L', click() { const fileName = '.chat2db/logs/application.log'; const url = path.join(os.homedir(), fileName); @@ -47,8 +49,7 @@ const menuBar = [ }, { label: '打开控制台', - accelerator: - process.platform === 'darwin' ? 'Cmd+Shift+I' : 'Ctrl+Shift+I', + accelerator: process.platform === 'darwin' ? 'Cmd+Shift+I' : 'Ctrl+Shift+I', click() { mainWindow && mainWindow.toggleDevTools(); }, diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index 487326fdb..a293e14f5 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -5,7 +5,7 @@ const path = require('path'); contextBridge.exposeInMainWorld('myAPI', { startServerForSpawn: async () => { - const path1 = path.join(__dirname, `app/${JAVA_APP_NAME}`); + const javaPath = path.join(__dirname, '../..', `./static/${JAVA_APP_NAME}`); const productName = await ipcRenderer.invoke('get-product-name'); @@ -13,12 +13,12 @@ contextBridge.exposeInMainWorld('myAPI', { console.log('productName:', productName, isTest); - const child = spawn(path.join(__dirname, JAVA_PATH), [ + const child = spawn(path.join(__dirname, '../..', `./static/${JAVA_PATH}`), [ '-jar', '-Xmx512M', `-Dspring.profiles.active=${isTest ? 'test' : 'release'}`, '-Dserver.address=127.0.0.1', - path1, + javaPath, ]); child.stdout.on('data', (buffer) => { @@ -30,7 +30,6 @@ contextBridge.exposeInMainWorld('myAPI', { }); child.stderr.on('data', (data) => { console.error(`stderr: ${data}`); - // alert('启动服务异常'); }); child.on('close', (code) => { console.log(`child process exited with code ${code}`); diff --git a/chat2db-client/src/main/utils.js b/chat2db-client/src/main/utils.js index fe2a71c1c..4095c11ae 100644 --- a/chat2db-client/src/main/utils.js +++ b/chat2db-client/src/main/utils.js @@ -1,6 +1,6 @@ -const electronReload = require('electron-reload'); const { DEV_WEB_URL } = require('./constants'); const path = require('path'); +const url = require('url'); /** * 加载主进程前端资源 @@ -12,11 +12,11 @@ function loadMainResource(mainWindow) { mainWindow.webContents.openDevTools(); // 监听应用程序根路径下的所有文件,当文件发生修改时,自动刷新应用程序 - electronReload(path.join(__dirname, '..')); + require('electron-reload')(path.join(__dirname, '..')); } else { mainWindow.loadURL( url.format({ - pathname: path.join(__dirname, './dist/index.html'), + pathname: path.join(__dirname, '../..', './dist/index.html'), protocol: 'file:', slashes: true, }), diff --git a/chat2db-client/src/pages/document.ejs b/chat2db-client/src/pages/document.ejs index 27a10c503..51f19c403 100644 --- a/chat2db-client/src/pages/document.ejs +++ b/chat2db-client/src/pages/document.ejs @@ -3,7 +3,7 @@ - + Chat2DB @@ -16,8 +16,7 @@
- - - \ No newline at end of file diff --git a/ali-dbhub-client/src/pages/error/index.less b/ali-dbhub-client/src/pages/error/index.less deleted file mode 100644 index f78482e15..000000000 --- a/ali-dbhub-client/src/pages/error/index.less +++ /dev/null @@ -1,72 +0,0 @@ -.page{ - height: 100vh; - display: flex; - justify-content: center; - align-items: center; - background-color: hsl(38, 69%, 59%); -} - -.box{ - width: 80%; - display: flex; - flex-wrap: wrap; -} - -.left{ - flex: 1; - padding-top: 40px; - font-size: 30px; - font-weight: 500; -} - -.button{ - display: inline-block; - margin-top: 30px; - padding: 10px 30px; - border-radius: 25px; - color: var(--color-bg-100) ; - font-size: 20px; - background-color: var(--custom-primary-color); - cursor: pointer; -} - -.right{ - flex: 1; - display: flex; - flex-direction: column; - align-items: center; -} - -.ghost{ - width: 100%; - animation: ghost-animation 1.8s infinite alternate; -} - -.shadow{ - width: 60%; - height: 24px; - background-color: hsla(38, 21%, 19%, .16); - margin: 0 auto; - border-radius: 50%; - filter: blur(7px); - animation: shadow-animation 1.8s infinite alternate; -} - -@keyframes ghost-animation { - 0% { - transform: translateY(0px); - } - 100% { - transform: translateY(15px); - } -} - -@keyframes shadow-animation { - 0% { - width: 60%; - } - 100% { - width: 50%; - } -} - diff --git a/ali-dbhub-client/src/pages/error/index.tsx b/ali-dbhub-client/src/pages/error/index.tsx deleted file mode 100644 index c8f50e82a..000000000 --- a/ali-dbhub-client/src/pages/error/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { memo } from 'react'; -import styles from './index.less'; -import { history } from 'umi'; -import ghostImg from '@/assets/ghost.png' - -export default function ErrorPage() { - const goHone = () => { - history.push('/') - } - return
-
-
-
-
ERROR 404
-
找不到页面:'/home/xxx'
-
-
-
- 返回首页 -
-
-
-
- -
-
-
-
-} \ No newline at end of file diff --git a/ali-dbhub-client/src/pages/login/index.less b/ali-dbhub-client/src/pages/login/index.less deleted file mode 100644 index 89f14e8f7..000000000 --- a/ali-dbhub-client/src/pages/login/index.less +++ /dev/null @@ -1,125 +0,0 @@ -@import '../../var.less'; - -.login { - display: flex; - align-items: center; - justify-content: center; - - .login-form { - margin-top: 4vh; - padding: 40px 20px; - border-radius: 8px; - border: 1px solid var(--color-box-shadow); - background-color: var(--color-bg-6); - width: 400px; - - & label { - color: var(--color-text); - } - - .logo { - text-align: center; - margin-bottom: 32px; - font-size: 32px; - } - - .login-form-button { - margin-top: 20px; - width: 100%; - } - } -} - -.box { - height: 100vh; - width: 100vw; - display: flex; - justify-content: center; - align-items: center; - transform: translateY(-10%); -} - -.form-box { - max-width: 500px; - background: var(--color-bg-200); - overflow: hidden; - border-radius: 16px; - color: var(--color-text-45); - border: 1px solid var(--color-border); -} - -.form { - position: relative; - display: flex; - flex-direction: column; - padding: 32px 24px 24px; - gap: 16px; - text-align: center; -} - -/*Form text*/ -.title { - font-weight: bold; - font-size: 1.6rem; -} - -.subtitle { - font-size: 1rem; - color: var(--color-text); -} - -/*Inputs box*/ -.form-container { - overflow: hidden; - border-radius: 8px; - background-color: var(--color-bg-200); - margin: 1rem 0 0.5rem; - width: 100%; -} - -.input { - background: none; - border: 0; - outline: 0; - height: 50px; - width: 100%; - border-bottom: 1px solid var(--color-border); - font-size: 0.9rem; - padding: 8px 15px; - margin-bottom: 20px; -} - -.form-section { - padding: 16px; - font-size: 0.85rem; - background-color: #e0ecfb; - box-shadow: rgb(0 0 0 / 8%) 0 -1px; -} - -.form-section a { - font-weight: bold; - color: #0066ff; - transition: color 0.3s ease; -} - -.form-section a:hover { - color: #005ce6; - text-decoration: underline; -} - -/*Button*/ -.form .button { - background-color: #0066ff; - color: #fff; - border: 0; - border-radius: 24px; - padding: 10px 16px; - font-size: 1rem; - font-weight: 600; - cursor: pointer; - transition: background-color 0.3s ease; -} - -.form .button:hover { - background-color: #005ce6; -} diff --git a/ali-dbhub-client/src/pages/login/index.tsx b/ali-dbhub-client/src/pages/login/index.tsx deleted file mode 100644 index 11031fa01..000000000 --- a/ali-dbhub-client/src/pages/login/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { Button, Form, Input } from 'antd'; -import { getLocationHash } from '@/utils'; -import './index.less'; -import { getUser, userLogin } from '@/service/user'; -import { history } from 'umi'; -const path = require('path'); - -interface IFormData { - userName: string; - password: string; -} - -const App: React.FC = () => { - // const handleLogin = useCallback(async (values: IFormData) => { - // let res = await userLogin(values); - // const params = getLocationHash(); - // const href = '#' + (params?.callback ?? ''); - // history.push('/'); - // }, []); - const [formData, setFormData] = useState<{ userName: string; password: string; } | {}>({}); - function handleLogin() { - userLogin(formData).then(res => { - const params = getLocationHash(); - if (window._ENV === 'desktop') { - history.push('/') - } else { - window.location.href = '/' - } - // console.log(path.join(__dirname, '#' + (params?.callback || '/'))) - }) - } - - // return ( - //
- //
{ - // handleLogin(values); - // }} - // > - //
ChatDB
- // - // - // - // - // - // - - // - // - //
- // ); - return
-
-
- 欢迎登陆Chat2DB - -
- { setFormData({ ...formData, userName: e.target.value }) }} className="input" placeholder="UserName" /> - { setFormData({ ...formData, password: e.target.value }) }} className="input" placeholder="Password" /> -
-
登陆
- -
-
-}; - -export default App; diff --git a/ali-dbhub-client/src/pages/manage/index.less b/ali-dbhub-client/src/pages/manage/index.less deleted file mode 100644 index 467b54448..000000000 --- a/ali-dbhub-client/src/pages/manage/index.less +++ /dev/null @@ -1,37 +0,0 @@ -@import '../../var.less'; - -.manage { - padding: 20px; - - .add-user { - text-align: right; - margin: 20px; - } -} - -.add-user-drawer { - :global { - .custom-form-item { - margin-bottom: 20px; - } - - .custom-form-item-label > label { - font-size: 16px; - } - - .custom-form-item-control-input-content > input { - height: 36px; - } - - .custom-form-item-control-input-content > select { - height: 36px; - } - - .custom-select-single .custom-select-selector .custom-select-selection-item, - .custom-select-single - .custom-select-selector - .custom-select-selection-placeholder { - font-size: 16px; - } - } -} diff --git a/ali-dbhub-client/src/pages/manage/index.tsx b/ali-dbhub-client/src/pages/manage/index.tsx deleted file mode 100644 index fc846758a..000000000 --- a/ali-dbhub-client/src/pages/manage/index.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { - createUser, - deleteUser, - getUserList, - updateUser, -} from '@/service/user'; -import { IRole, IUser } from '@/typings/user'; -import { - Button, - Table, - Drawer, - Form, - Input, - Select, - Space, - Row, - Col, - Tag, - message, -} from 'antd'; -import { ColumnsType } from 'antd/lib/table'; -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import styles from './index.less'; - -const RoleType: Record = { - admin: '管理者', - normal: '普通用户', -}; - -const initUser = { - userName: '', - nickName: '', - password: '123', - password2: '123', - email: '', -}; - -export default function Manage() { - const [visible, setVisible] = useState(false); - const [pageNo, setPageNo] = useState(1); - const [total, setTotal] = useState(0); - const [formData, setFormData] = useState(); - const [listData, setListData] = useState([]); - - useEffect(() => { - queryUserList(); - }, [pageNo]); - - const queryUserList = useCallback(async () => { - let res = await getUserList({ - pageNo, - pageSize: 10, - }); - setListData(res?.data); - setTotal(res?.total); - }, [pageNo]); - - const onOpen = useCallback(() => { - setVisible(true); - }, []); - const onClose = useCallback(() => { - setFormData(undefined); - setVisible(false); - }, []); - - /** 新增、编辑用户信息 */ - const addOrUpdateUser = async () => { - if (!formData) return; - let res; - if (formData?.id) { - res = await updateUser(formData); - } else { - res = await createUser(formData); - } - queryUserList(); - onClose(); - }; - - const columns: ColumnsType = useMemo( - () => [ - { - title: '用户名', - dataIndex: 'userName', - key: 'userName', - }, - { - title: '昵称', - dataIndex: 'nickName', - key: 'nickName', - }, - { - title: '邮箱', - dataIndex: 'email', - key: 'email', - }, - { - title: '角色', - dataIndex: 'role', - key: 'role', - render: (role?: IRole) => - role && ( - - {RoleType[role]} - - ), - }, - { - title: '操作', - key: 'operation', - render: (item: IUser, record: IUser, index: number) => ( -
- - -
- ), - }, - ], - [], - ); - - return ( -
-
- -
-
setPageNo(page), - }} - /> - - {visible && ( - - - - - } - > -
- setFormData({ ...formData, ...newData }) - } - scrollToFirstError - > - - - - - - - - - - - - - - - - ({ - validator(_, value: any) { - if (!value || getFieldValue('password') === value) { - return Promise.resolve(); - } - return Promise.reject(new Error('两次密码不一致')); - }, - }), - ]} - > - - - - -
pageQuery(TablePageQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); List
list = metaSchema.tables(param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + list = pinTable(list,param); if (CollectionUtils.isEmpty(list)) { return PageResult.of(list, 0L, param); } return PageResult.of(list, Long.valueOf(list.size()), param); } + private List
pinTable(List
list, TablePageQueryParam param) { + if (CollectionUtils.isEmpty(list)) { + return Lists.newArrayList(); + } + PinTableParam pinTableParam = pinTableConverter.toPinTableParam(param); + pinTableParam.setUserId(ContextUtils.getUserId()); + ListResult listResult = pinService.queryPinTables(pinTableParam); + if (!listResult.success() || CollectionUtils.isEmpty(listResult.getData())) { + return list; + } + List
tables = new ArrayList<>(); + Map tableMap = list.stream().collect(Collectors.toMap(Table::getName, Function.identity())); + for (String tableName : listResult.getData()) { + Table table = tableMap.get(tableName); + if (table != null) { + table.setPinned(true); + tables.add(table); + } + } + + for (Table table : list) { + if (table!=null && !tables.contains(table)) { + tables.add(table); + } + } + return tables; + } + + @Override public List queryColumns(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/PinTableDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/PinTableDO.java new file mode 100644 index 000000000..9d764d2fa --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/PinTableDO.java @@ -0,0 +1,61 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +@Getter +@Setter +@TableName("PIN_TABLE") +public class PinTableDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 数据源连接ID + */ + private Long dataSourceId; + + /** + * db名称 + */ + private String databaseName; + + + /** + * 保存名称 + */ + private String schemaName; + + /** + * userId + */ + private Long userId; + + /** + * tableName + */ + private String tableName; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/PinTableMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/PinTableMapper.java new file mode 100644 index 000000000..c9ae1bdf1 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/PinTableMapper.java @@ -0,0 +1,8 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.PinTableDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +public interface PinTableMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/PinTableMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/PinTableMapper.xml new file mode 100644 index 000000000..01eb63fd4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/PinTableMapper.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_5__\345\242\236\345\212\240\347\275\256\351\241\266\350\241\250.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_5__\345\242\236\345\212\240\347\275\256\351\241\266\350\241\250.sql" new file mode 100644 index 000000000..ba90e9323 --- /dev/null +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_5__\345\242\236\345\212\240\347\275\256\351\241\266\350\241\250.sql" @@ -0,0 +1,14 @@ +CREATE TABLE IF NOT EXISTS `pin_table` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', + `database_name` varchar(128) DEFAULT NULL COMMENT 'db名称', + `schema_name` varchar(128) DEFAULT NULL COMMENT 'schema名称', + `table_name` varchar(128) DEFAULT NULL COMMENT 'table_name', + `deleted` text DEFAULT NULL COMMENT '是否被删除,y表示删除,n表示未删除', + `user_id` bigint(20) unsigned NOT NULL DEFAULT 0 COMMENT '用户id', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='PIN TABLES' +; +create INDEX idx_user_id_data_source_id on pin_table(user_id,data_source_id) ; \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java new file mode 100644 index 000000000..d0b9dcf9e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.web.api.controller.pin; + +import ai.chat2db.server.domain.api.service.PinService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.web.api.controller.pin.converter.PinWebConverter; +import ai.chat2db.server.web.api.controller.pin.request.PinTableRequest; +import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/pin") +@RestController +public class PinController { + + @Autowired + private PinService pinService; + + @Autowired + private PinWebConverter pinWebConverter; + + @PostMapping("/table/add") + public ActionResult add(PinTableRequest request) { + return pinService.pinTable(pinWebConverter.req2param(request)); + } + + @PostMapping("/table/delete") + public ActionResult delete(PinTableRequest request) { + return pinService.deletePinTable(pinWebConverter.req2param(request)); + } + + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/converter/PinWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/converter/PinWebConverter.java new file mode 100644 index 000000000..4a3aa9f71 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/converter/PinWebConverter.java @@ -0,0 +1,12 @@ +package ai.chat2db.server.web.api.controller.pin.converter; + +import ai.chat2db.server.domain.api.param.PinTableParam; +import ai.chat2db.server.web.api.controller.pin.request.PinTableRequest; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public abstract class PinWebConverter { + + + public abstract PinTableParam req2param(PinTableRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/request/PinTableRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/request/PinTableRequest.java new file mode 100644 index 000000000..ab4a26d31 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/request/PinTableRequest.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.web.api.controller.pin.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class PinTableRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间 + */ + private String schemaName; + /** + * Pin table name + */ + private String tableName; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java index c09758b9b..d70d6c9f6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java @@ -2,21 +2,12 @@ import java.util.List; -import ai.chat2db.server.domain.api.param.DatabaseOperationParam; -import ai.chat2db.server.domain.api.param.DropParam; -import ai.chat2db.server.domain.api.param.SchemaOperationParam; -import ai.chat2db.server.domain.api.param.SchemaQueryParam; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; -import ai.chat2db.spi.model.Schema; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.server.web.api.controller.rdb.vo.*; +import ai.chat2db.spi.model.*; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -34,11 +25,6 @@ import ai.chat2db.server.web.api.controller.rdb.request.TableUpdateDdlQueryRequest; import ai.chat2db.server.web.api.controller.rdb.request.UpdateDatabaseRequest; import ai.chat2db.server.web.api.controller.rdb.request.UpdateSchemaRequest; -import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; -import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; -import ai.chat2db.server.web.api.controller.rdb.vo.SchemaVO; -import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; -import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import com.google.common.collect.Lists; import jakarta.validation.Valid; @@ -89,7 +75,7 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), - request.getPageSize()); + request.getPageSize()); } /** @@ -106,6 +92,20 @@ public ListResult schemaList(@Valid DataSourceBaseRequest request) { return ListResult.of(tableVOS); } + /** + * 查询数据库里包含的database_schema_list + * + * @param request + * @return + */ + @GetMapping("/database_schema_list") + public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { + MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()).build(); + DataResult result = databaseService.queryDatabaseSchema(queryParam); + MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); + return DataResult.of(schemaDto2vo); + } + /** * 删除数据库 * @@ -139,7 +139,7 @@ public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest req @PostMapping("/modify_database") public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()) - .newDatabaseName(request.getNewDatabaseName()).build(); + .newDatabaseName(request.getNewDatabaseName()).build(); return databaseService.modifyDatabase(param); } @@ -152,7 +152,7 @@ public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest req @PostMapping("/delete_schema") public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); + .schemaName(request.getSchemaName()).build(); return databaseService.deleteSchema(param); } @@ -165,7 +165,7 @@ public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest reque @PostMapping("/create_schema") public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); + .schemaName(request.getSchemaName()).build(); return databaseService.createSchema(param); } @@ -178,7 +178,7 @@ public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest reque @PostMapping("/modify_schema") public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); + .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); return databaseService.modifySchema(param); } @@ -283,9 +283,9 @@ public DataResult query(@Valid TableDetailQueryRequest request) { @GetMapping("/modify/sql") public ListResult modifySql(@Valid TableModifySqlRequest request) { return tableService.buildSql( - rdbWebConverter.tableRequest2param(request.getOldTable()), - rdbWebConverter.tableRequest2param(request.getNewTable())) - .map(rdbWebConverter::dto2vo); + rdbWebConverter.tableRequest2param(request.getOldTable()), + rdbWebConverter.tableRequest2param(request.getNewTable())) + .map(rdbWebConverter::dto2vo); } /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index 97079b052..2b7044879 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -8,12 +8,9 @@ import ai.chat2db.server.domain.api.param.ShowCreateTableParam; import ai.chat2db.server.domain.api.param.TablePageQueryParam; import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.spi.model.ExecuteResult; -import ai.chat2db.spi.model.Schema; -import ai.chat2db.spi.model.Sql; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.controller.rdb.vo.*; +import ai.chat2db.spi.model.*; import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; import ai.chat2db.server.web.api.controller.rdb.request.DdlRequest; @@ -22,12 +19,6 @@ import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; import ai.chat2db.server.web.api.controller.rdb.request.TableRequest; -import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; -import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; -import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; -import ai.chat2db.server.web.api.controller.rdb.vo.SchemaVO; -import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; -import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -199,4 +190,6 @@ public abstract class RdbWebConverter { * @return */ public abstract SchemaVO schemaDto2vo(Schema dto); + + public abstract MetaSchemaVO metaSchemaDto2vo(MetaSchema data); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/MetaSchemaVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/MetaSchemaVO.java new file mode 100644 index 000000000..59b3b2a3c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/MetaSchemaVO.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.web.api.controller.rdb.vo; + +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Schema; +import lombok.Data; + +import java.util.List; +@Data +public class MetaSchemaVO { + /** + * database list + */ + private List databases; + + /** + * schema list + */ + private List schemas; +} diff --git a/chat2db-server/chat2db-spi/pom.xml b/chat2db-server/chat2db-spi/pom.xml index 724e7979e..5f13dc85a 100644 --- a/chat2db-server/chat2db-spi/pom.xml +++ b/chat2db-server/chat2db-spi/pom.xml @@ -60,5 +60,9 @@ com.baomidou mybatis-plus-boot-starter + + commons-beanutils + commons-beanutils + \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 9bd8b9455..7d8ee0fe6 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -6,12 +6,7 @@ import java.util.List; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import jakarta.validation.constraints.NotEmpty; /** @@ -26,7 +21,7 @@ public interface MetaData { * * @return */ - List databases(); + List databases(); /** * Querying all schemas under a database @@ -34,7 +29,7 @@ public interface MetaData { * @param databaseName * @return */ - List schemas(String databaseName); + List schemas(String databaseName); /** * Querying DDL information diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 049bac11d..b54fe2288 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -4,16 +4,18 @@ */ package ai.chat2db.spi.jdbc; +import java.lang.reflect.InvocationTargetException; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import ai.chat2db.spi.MetaData; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import cn.hutool.json.JSON; +import com.google.common.collect.Lists; +import org.apache.commons.beanutils.BeanUtils; +import org.springframework.util.CollectionUtils; /** * @author jipengfei @@ -21,13 +23,32 @@ */ public class DefaultMetaService implements MetaData { @Override - public List databases() { - return SQLExecutor.getInstance().databases(); + public List databases() { + List dataBases = SQLExecutor.getInstance().databases(); + if (CollectionUtils.isEmpty(dataBases)) { + return Lists.newArrayList(); + } + return dataBases.stream().map(str -> Database.builder().name(str).build()).collect(Collectors.toList()); + } @Override - public List schemas(String databaseName) { - return SQLExecutor.getInstance().schemas(databaseName, null); + public List schemas(String databaseName) { + List> maps = SQLExecutor.getInstance().schemas(databaseName, null); + if (CollectionUtils.isEmpty(maps)) { + return Lists.newArrayList(); + } + return maps.stream().map(map -> map2Schema(map)).collect(Collectors.toList()); + + } + + private Schema map2Schema(Map map) { + Schema schema = new Schema(); + try { + BeanUtils.populate(schema, map); + } catch (Exception e) { + } + return schema; } @Override @@ -37,12 +58,12 @@ public String tableDDL(String databaseName, String schemaName, String tableName) @Override public List
tables(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(databaseName, schemaName, tableName, new String[] {"TABLE"}); + return SQLExecutor.getInstance().tables(databaseName, schemaName, tableName, new String[]{"TABLE"}); } @Override public List views(String databaseName, String schemaName) { - return SQLExecutor.getInstance().tables(databaseName, schemaName, null, new String[] {"VIEW"}); + return SQLExecutor.getInstance().tables(databaseName, schemaName, null, new String[]{"VIEW"}); } @Override @@ -67,7 +88,7 @@ public List columns(String databaseName, String schemaName, String @Override public List columns(String databaseName, String schemaName, String tableName, - String columnName) { + String columnName) { return SQLExecutor.getInstance().columns(databaseName, schemaName, tableName, columnName); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java index 6ca300646..216ce7b8d 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java @@ -5,6 +5,8 @@ import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; +import java.util.List; + /** * 数据库 * @@ -16,7 +18,12 @@ @AllArgsConstructor public class Database { /** - * 数据名字 + * 数据库名字 */ private String name; + + /** + * schema name + */ + private List schemas; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java new file mode 100644 index 000000000..0ac4b706b --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java @@ -0,0 +1,25 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class MetaSchema { + + /** + * database list + */ + private List databases; + + /** + * schema list + */ + private List schemas; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java index 0d0f0e7e4..ad61fde57 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java @@ -18,6 +18,11 @@ @NoArgsConstructor @AllArgsConstructor public class Schema { + + /** + * databaseName + */ + private String databaseName; /** * 数据名字 */ diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java index 84ef9ac68..ab6906f24 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java @@ -59,6 +59,9 @@ public class Table { */ private String type; - + /** + * 是否置顶 + */ + private boolean pinned; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 672dd8469..4e55c1a35 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -9,7 +9,9 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; @@ -203,13 +205,16 @@ public List databases() { * @param schemaName * @return */ - public List schemas(String databaseName, String schemaName) { - List schemaList = Lists.newArrayList(); + public List> schemas(String databaseName, String schemaName) { + List> schemaList = Lists.newArrayList(); try { ResultSet resultSet = getConnection().getMetaData().getSchemas(databaseName, schemaName); if (resultSet != null) { while (resultSet.next()) { - schemaList.add(resultSet.getString("TABLE_SCHEM")); + Map map = new HashMap<>(); + map.put("name",resultSet.getString("TABLE_SCHEM")); + map.put("databaseName",resultSet.getString("TABLE_CATALOG")); + schemaList.add(map); } } } catch (SQLException e) { diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 88c5f7f7e..b2b8bee3d 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -217,6 +217,11 @@ 6.0.10 optional + + commons-beanutils + commons-beanutils + 1.9.4 + From 699b956d3bc5e13d83aa22d4eae0d029f98593ac Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 24 Jun 2023 16:40:39 +0800 Subject: [PATCH 0042/1069] support PinService --- .../ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java index 0bdd27d36..45dfdc0b9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java @@ -32,4 +32,6 @@ public class TableVO { */ private List indexList; + + private boolean pinned; } From 2db1e445af7bcba78f9a7d122483151ea77064df Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 24 Jun 2023 17:01:42 +0800 Subject: [PATCH 0043/1069] fix close ResultSet --- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 74 +++++-------------- 1 file changed, 20 insertions(+), 54 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 4e55c1a35..030e13d68 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -71,32 +71,16 @@ public R executeSql(String sql, Function function) { return null; } log.info("execute:{}", sql); - Statement stmt = null; - try { - stmt = getConnection().createStatement(); + try (Statement stmt = getConnection().createStatement();) { boolean query = stmt.execute(sql); // 代表是查询 if (query) { - ResultSet rs = null; - try { - rs = stmt.getResultSet(); + try (ResultSet rs = stmt.getResultSet();) { return function.apply(rs); - } finally { - if (rs != null) { - rs.close(); - } } } } catch (SQLException e) { throw new RuntimeException(e); - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } } return null; } @@ -113,10 +97,8 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL log.info("execute:{}", sql); ExecuteResult executeResult = ExecuteResult.builder().sql(sql).success(Boolean.TRUE).build(); - Statement stmt = null; - try { - TimeInterval timeInterval=new TimeInterval(); - stmt = connection.createStatement(); + try (Statement stmt = connection.createStatement()) { + TimeInterval timeInterval = new TimeInterval(); boolean query = stmt.execute(sql.replaceFirst(";", "")); executeResult.setDescription("执行成功"); // 代表是查询 @@ -159,8 +141,6 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL // 修改或者其他 executeResult.setUpdateCount(stmt.getUpdateCount()); } - } finally { - JdbcUtils.closeStatement(stmt); } return executeResult; } @@ -184,15 +164,13 @@ public ExecuteResult execute(String sql) throws SQLException { */ public List databases() { List tables = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getCatalogs(); + try (ResultSet resultSet = getConnection().getMetaData().getCatalogs();) { if (resultSet != null) { while (resultSet.next()) { tables.add(resultSet.getString("TABLE_CAT")); } } } catch (SQLException e) { - close(); throw new RuntimeException(e); } return tables; @@ -205,20 +183,18 @@ public List databases() { * @param schemaName * @return */ - public List> schemas(String databaseName, String schemaName) { - List> schemaList = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getSchemas(databaseName, schemaName); + public List> schemas(String databaseName, String schemaName) { + List> schemaList = Lists.newArrayList(); + try (ResultSet resultSet = getConnection().getMetaData().getSchemas(databaseName, schemaName)) { if (resultSet != null) { while (resultSet.next()) { - Map map = new HashMap<>(); - map.put("name",resultSet.getString("TABLE_SCHEM")); - map.put("databaseName",resultSet.getString("TABLE_CATALOG")); + Map map = new HashMap<>(); + map.put("name", resultSet.getString("TABLE_SCHEM")); + map.put("databaseName", resultSet.getString("TABLE_CATALOG")); schemaList.add(map); } } } catch (SQLException e) { - close(); throw new RuntimeException(e); } return schemaList; @@ -235,16 +211,14 @@ public List> schemas(String databaseName, String schemaName) */ public List
tables(String databaseName, String schemaName, String tableName, String types[]) { List
tables = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getTables(databaseName, schemaName, tableName, - types); + try (ResultSet resultSet = getConnection().getMetaData().getTables(databaseName, schemaName, tableName, + types)) { if (resultSet != null) { while (resultSet.next()) { tables.add(buildTable(resultSet)); } } } catch (SQLException e) { - close(); throw new RuntimeException(e); } return tables; @@ -261,16 +235,14 @@ public List
tables(String databaseName, String schemaName, String tableNa */ public List columns(String databaseName, String schemaName, String tableName, String columnName) { List tableColumns = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getColumns(databaseName, schemaName, tableName, - columnName); + try (ResultSet resultSet = getConnection().getMetaData().getColumns(databaseName, schemaName, tableName, + columnName)) { if (resultSet != null) { while (resultSet.next()) { tableColumns.add(buildColumn(resultSet)); } } } catch (Exception e) { - close(); throw new RuntimeException(e); } return tableColumns; @@ -286,10 +258,9 @@ public List columns(String databaseName, String schemaName, String */ public List indexes(String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); - try { + try (ResultSet resultSet = getConnection().getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, + false)) { List tableIndexColumns = Lists.newArrayList(); - ResultSet resultSet = getConnection().getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, - false); while (resultSet != null && resultSet.next()) { tableIndexColumns.add(buildTableIndexColumn(resultSet)); @@ -309,7 +280,6 @@ public List indexes(String databaseName, String schemaName, String t tableIndices.add(tableIndex); }); } catch (SQLException e) { - close(); throw new RuntimeException(e); } return tableIndices; @@ -323,15 +293,13 @@ public List indexes(String databaseName, String schemaName, String t * @return */ public List functions(String databaseName, - String schemaName) { + String schemaName) { List functions = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getFunctions(databaseName, schemaName, null); + try (ResultSet resultSet = getConnection().getMetaData().getFunctions(databaseName, schemaName, null);) { while (resultSet != null && resultSet.next()) { functions.add(buildFunction(resultSet)); } } catch (Exception e) { - close(); throw new RuntimeException(e); } return functions; @@ -346,13 +314,11 @@ public List functions(String databaseName, */ public List procedures(String databaseName, String schemaName) { List procedures = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getProcedures(databaseName, schemaName, null); + try (ResultSet resultSet = getConnection().getMetaData().getProcedures(databaseName, schemaName, null)) { while (resultSet != null && resultSet.next()) { procedures.add(buildProcedure(resultSet)); } } catch (Exception e) { - close(); throw new RuntimeException(e); } return procedures; From 3c220f57d9221f05cbf59b4bff4a92cb50986476 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 24 Jun 2023 17:01:47 +0800 Subject: [PATCH 0044/1069] test release --- .github/workflows/release_test.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 367db59dc..5a3ae06bf 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -121,7 +121,6 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822 # windows - name: Build/release Electron app for Windows @@ -132,7 +131,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - build_script_name: "build:main:prod" + build_script_name: "build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822" args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --win --x64" # macos amd64 @@ -144,7 +143,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - build_script_name: "build:main:prod" + build_script_name: "build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822" args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --mac --x64" # amd64 notarization @@ -162,7 +161,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - build_script_name: "build:main:prod" + build_script_name: "build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822" args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --mac --arm64" # arm notarization From 37a12548b4e7acf96051d413db00547326daae6d Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 24 Jun 2023 17:21:00 +0800 Subject: [PATCH 0045/1069] test release --- .github/workflows/release_test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 5a3ae06bf..67834f183 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -121,6 +121,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client + yarn run build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822 # windows - name: Build/release Electron app for Windows @@ -131,7 +132,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - build_script_name: "build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822" + skip_build: true args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --win --x64" # macos amd64 @@ -143,7 +144,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - build_script_name: "build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822" + 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=1.0.${{ github.run_id }}-Test --mac --x64" # amd64 notarization @@ -161,7 +162,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} mac_certs: ${{ secrets.mac_certs }} mac_certs_password: ${{ secrets.mac_certs_password }} - build_script_name: "build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822" + skip_build: true args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --mac --arm64" # arm notarization From 74cc745fe70421ac2ed1d7b0a22879ae3d1cdce8 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 24 Jun 2023 15:44:19 +0800 Subject: [PATCH 0046/1069] test: test CI/CD --- chat2db-client/src/models/connection.ts | 10 +++++- .../src/pages/main/connections/index.tsx | 34 ++++++++++++++----- .../src/pages/main/dashboard/index.tsx | 28 ++++++++------- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index 921700933..24a496458 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -15,6 +15,12 @@ export default { reducers: { // 获取连接池列表 + setConnectionList(state: ConnectionState, { payload }: { payload: ConnectionState['connectionList'] }) { + return { + ...state, + connectionList: payload + } + }, // 设置当前选着的Connection setCurConnection( @@ -22,6 +28,8 @@ export default { { payload }: { payload: ConnectionState['curConnection'] }, ) { return { ...state, curConnection: payload } - } + }, + + } } \ No newline at end of file diff --git a/chat2db-client/src/pages/main/connections/index.tsx b/chat2db-client/src/pages/main/connections/index.tsx index a59990e6c..042bed8c3 100644 --- a/chat2db-client/src/pages/main/connections/index.tsx +++ b/chat2db-client/src/pages/main/connections/index.tsx @@ -12,7 +12,7 @@ import { IConnectionDetails } from '@/typings/connection'; import { Button, Dropdown, Modal } from 'antd'; import { MoreOutlined } from '@ant-design/icons'; import styles from './index.less'; -import { connect } from 'umi'; +import { connect, history } from 'umi'; import { ConnectionState } from '@/models/connection'; interface IMenu { @@ -21,25 +21,38 @@ interface IMenu { icon: React.ReactNode; meta: IConnectionDetails; } -interface IProps { } +interface IProps { + connectionList: IConnectionDetails[]; + curConnection: IConnectionDetails; + dispatch: (p: { type: string, payload: any }) => void + +} function Connections(props: IProps) { + const { connectionList } = props; const volatileRef = useRef(); - const [connectionList, setConnectionList] = useState(); + // const [connectionList, setConnectionList] = useState(); const [curConnection, setCurConnection] = useState>({}); + + useEffect(() => { getConnectionList(); }, []); - function getConnectionList() { + const getConnectionList = async () => { let p = { pageNo: 1, pageSize: 999, }; - connectionService.getList(p).then((res) => { - setConnectionList(res.data); - }); + let res = await connectionService.getList(p) + // setConnectionList(res.data); + + props.dispatch({ + type: 'connection/setConnectionList', + payload: res.data, + }) + } function handleCreateConnections(database: IDatabase) { @@ -87,7 +100,12 @@ function Connections(props: IProps) { key: 'EnterWorkSpace', label: i18n('connection.menu.enterToWorkSpace'), onClick: () => { - window.location.replace('/workspace'); + props.dispatch({ + type: 'connection/setCurConnection', + payload: menu.meta, + }) + history.push('/workspace') + // window.location.replace('/workspace'); }, }, { diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index bdbd7594a..d89fa91a6 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -289,17 +289,19 @@ export default connect(({ global }: { global: GlobalState }) => ({ { /* - */ + + + */ } From d98d75ec62330228c0b4d1cd0e37211548ac5791 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 24 Jun 2023 17:52:51 +0800 Subject: [PATCH 0047/1069] test release --- .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 67834f183..de263efc1 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -133,7 +133,7 @@ jobs: 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.extraMetadata.version=1.0.${{ github.run_id }}-Test --win --x64" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --win --x64" # macos amd64 - name: Build/release Electron app for MacOS X64 @@ -145,7 +145,7 @@ jobs: 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=1.0.${{ github.run_id }}-Test --mac --x64" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --mac --x64" # amd64 notarization - name: Notarization amd64 App @@ -163,7 +163,7 @@ jobs: 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.extraMetadata.version=1.0.${{ github.run_id }}-Test --mac --arm64" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=1.0.${{ github.run_id }}-Test --mac --arm64" # arm notarization - name: Notarization arm64 App From 9d658320f430fafcd1714ac267fe2013d87160ba Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 24 Jun 2023 18:55:13 +0800 Subject: [PATCH 0048/1069] test CI/CD --- chat2db-client/.umirc.desktop.ts | 28 ++++++++++++++ chat2db-client/.umirc.prod.ts | 4 +- chat2db-client/.umirc.ts | 1 + chat2db-client/config/config.desktop.js | 3 ++ chat2db-client/config/config.js | 3 ++ chat2db-client/config/config.prod.js | 3 ++ chat2db-client/package.json | 3 +- .../src/components/Iconfont/index.tsx | 38 +++++++++---------- chat2db-client/src/i18n/en-us/workspace.ts | 1 + chat2db-client/src/i18n/zh-cn/workspace.ts | 1 + chat2db-client/src/models/connection.ts | 3 +- .../main/dashboard/chart-item/index.less | 7 +++- chat2db-client/src/pages/main/index.tsx | 1 - .../components/WorkspaceLeft/index.less | 14 +++---- .../components/WorkspaceLeft/index.tsx | 18 ++++----- .../src/pages/main/workspace/index.less | 4 +- .../src/pages/main/workspace/index.tsx | 4 +- chat2db-client/typings.d.ts | 2 + 18 files changed, 92 insertions(+), 46 deletions(-) create mode 100644 chat2db-client/.umirc.desktop.ts create mode 100644 chat2db-client/config/config.desktop.js create mode 100644 chat2db-client/config/config.js create mode 100644 chat2db-client/config/config.prod.js diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts new file mode 100644 index 000000000..d715a73c7 --- /dev/null +++ b/chat2db-client/.umirc.desktop.ts @@ -0,0 +1,28 @@ +import { formatDate } from './src/utils/date'; +import { defineConfig } from 'umi'; +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); +const UMI_PublicPath = process.env.UMI_PublicPath || './'; + +const chainWebpack = (config: any, { webpack }: any) => { + config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ + { + languages: ['mysql', 'pgsql', 'sql'], + }, + ]); + + config.plugin('define').use(require('webpack').DefinePlugin, [ + { + __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), + __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), + }, + ]); +}; + +export default defineConfig({ + // publicPath: './', + chainWebpack, + headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], + define: { + 'process.env.UMI_ENV': 'desktop' + } +}); diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 9c0718d29..10da9963e 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -21,5 +21,7 @@ const chainWebpack = (config: any, { webpack }: any) => { export default defineConfig({ publicPath: UMI_PublicPath, chainWebpack, - headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], + define: { + 'process.env.UMI_ENV': 'prod' + } }); diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index b98dd8d6f..d78ed0b65 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -45,4 +45,5 @@ export default defineConfig({ }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], + }); diff --git a/chat2db-client/config/config.desktop.js b/chat2db-client/config/config.desktop.js new file mode 100644 index 000000000..7a6e78635 --- /dev/null +++ b/chat2db-client/config/config.desktop.js @@ -0,0 +1,3 @@ +export default { + 'process.env.UMI_ENV': 'desktop' +} \ No newline at end of file diff --git a/chat2db-client/config/config.js b/chat2db-client/config/config.js new file mode 100644 index 000000000..4106c379e --- /dev/null +++ b/chat2db-client/config/config.js @@ -0,0 +1,3 @@ +export default { + 'process.env.UMI_ENV': 'local' +} \ No newline at end of file diff --git a/chat2db-client/config/config.prod.js b/chat2db-client/config/config.prod.js new file mode 100644 index 000000000..45b14ad89 --- /dev/null +++ b/chat2db-client/config/config.prod.js @@ -0,0 +1,3 @@ +export default { + 'process.env.UMI_ENV': 'prod' +} \ No newline at end of file diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 670574fdf..0a5083caa 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,7 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "umi dev" + "start:web": "cross-env umi dev" }, "dependencies": { "ahooks": "^3.7.7", @@ -72,7 +72,6 @@ "dist/", "static/", "src/main", - "node_modules/", "package.json" ], "nsis": { diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index a270c9cdb..61146a261 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -4,25 +4,25 @@ import classnames from 'classnames'; import styles from './index.less'; let container = '' -if (window._ENV === 'desktop') { - container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), - url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); - } - ` -} else { - container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); - } - ` -} +container = ` + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), + url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); + } +` +// if (process.env.UMI_ENV === 'desktop') { +// } else { +// container = ` +// @font-face { +// font-family: 'iconfont'; /* Project id 3633546 */ +// src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), +// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), +// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); +// } +// ` +// } let style = document.createElement("style"); style.type = "text/css"; document.head.appendChild(style); diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 96c17f15c..71ec49d0e 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -1,3 +1,4 @@ export default { + 'workspace.cascader.placeholder': 'Select Here', 'workspace.ai.input.placeholder': 'Enter your plain text statement here', }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 7059d10cc..ceed2934b 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -1,3 +1,4 @@ export default { + 'workspace.cascader.placeholder': '请选择', 'workspace.ai.input.placeholder': 'Enter your plain text statement here' } \ No newline at end of file diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index 24a496458..45911b486 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -21,7 +21,7 @@ export default { connectionList: payload } }, - + // 设置当前选着的Connection setCurConnection( state: ConnectionState, @@ -31,5 +31,6 @@ export default { }, + } } \ No newline at end of file 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 d1c294115..e7e7c7b95 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -20,6 +20,7 @@ .edit { cursor: pointer; + } @@ -114,11 +115,13 @@ .editorBlock { display: flex; border: 1px solid #eee; - flex-wrap: wrap ; + flex-wrap: wrap; } .editor { flex: 1; border-right: 1px solid #eee; - min-width: 320px + min-width: 320px; + min-height: 320px; + } \ No newline at end of file diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 7f88b0875..731f136e8 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -39,7 +39,6 @@ const navConfig: INavItem[] = [ ]; const initPageIndex = navConfig.findIndex(t => `/${t.key}` === window.location.pathname); - function MainPage() { const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > 0 ? initPageIndex : 0]); function switchingNav(item: INavItem) { 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 a4fb92c9d..a64c45022 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -16,17 +16,17 @@ margin-bottom: 40px; } -.box_left_save { +.boxLeftSave { } -.box_left_connection { +.boxLeftConnection { } -.select_database_box { +.selectDatabaseBox { display: flex; align-items: center; width: 100%; - .current_database { + .currentDatabase { flex: 1; width: 0px; display: flex; @@ -42,11 +42,11 @@ } } - .other_operations { + .otherOperations { flex-shrink: 0; } - .icon_box { + .iconBox { width: 30px; height: 30px; display: flex; @@ -55,7 +55,7 @@ } } -.cascader_popup { +.cascaderPopup { :global { .ant-cascader-menu-item-content { font-weight: 400 !important; 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 9860c3a89..4078ed792 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -12,6 +12,7 @@ import { TreeNodeType } from '@/constants/tree'; import { ITreeNode } from '@/typings/tree'; import { useReducerContext } from '../../index'; import { workspaceActionType } from '../../context'; +import i18n from '@/i18n'; interface IProps { className?: string; } @@ -22,11 +23,11 @@ export default memo(function WorkspaceLeft(props) { return (
- +
Save
- +
); }); @@ -140,23 +141,22 @@ function RenderSelectDatabase() { } return ( -
+
-
-
{renderCurrentSelected()}
+
+
{renderCurrentSelected() || {i18n('workspace.cascader.placeholder')}}
-
-
+
+
diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index fb79bf746..9caeadbdc 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -5,13 +5,13 @@ height: 100%; } -.box_left { +.boxLeft { width: 260px; height: 100%; overflow: hidden; } -.box_right { +.boxRight { position: relative; z-index: 1; //为了覆盖左侧 Cascader flex: 1; diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index e9ba65a0e..68cd1a92d 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -29,10 +29,10 @@ export default memo(function workspace(props) { return ( -
+
-
+
diff --git a/chat2db-client/typings.d.ts b/chat2db-client/typings.d.ts index 9ae2fc424..3b03b2904 100644 --- a/chat2db-client/typings.d.ts +++ b/chat2db-client/typings.d.ts @@ -1 +1,3 @@ import 'umi/typings'; + +// declare const process.env.UMI_ENV: string; \ No newline at end of file From 444faa13bb71048ec4a7f8b5fcfcaeaebd39aa12 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 24 Jun 2023 19:13:57 +0800 Subject: [PATCH 0049/1069] delete lombok --- .../chat2db-server-tools/chat2db-server-tools-common/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml index bd807a8d1..138bffa5c 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml @@ -33,6 +33,7 @@ org.projectlombok lombok + provided com.google.guava From 620c321b4a531823730dd6ebb142076441f9672c Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 24 Jun 2023 19:32:12 +0800 Subject: [PATCH 0050/1069] Reduce jar packages --- chat2db-server/chat2db-server-start/pom.xml | 6 +++ .../chat2db-server-tools-common/pom.xml | 5 --- chat2db-server/pom.xml | 44 +++++++++++++++++++ 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-start/pom.xml b/chat2db-server/chat2db-server-start/pom.xml index bfb2e686d..d55f5162d 100644 --- a/chat2db-server/chat2db-server-start/pom.xml +++ b/chat2db-server/chat2db-server-start/pom.xml @@ -17,6 +17,12 @@ org.springframework.boot spring-boot-starter-web + + + log4j-api + org.apache.logging.log4j + + ai.chat2db diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml index 138bffa5c..011d43bc4 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml @@ -30,11 +30,6 @@ org.slf4j slf4j-api - - org.projectlombok - lombok - provided - com.google.guava guava diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index b2b8bee3d..6b27a8b28 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -122,6 +122,12 @@ mapstruct-processor 1.5.5.Final + + + org.projectlombok + lombok-mapstruct-binding + 0.2.0 + @@ -165,6 +171,12 @@ cn.dev33 sa-token-jwt 1.34.0 + + + hutool-jwt + cn.hutool + + @@ -172,6 +184,24 @@ com.dtflys.forest forest-spring-boot-starter 1.5.32 + + + httpclient + org.apache.httpcomponents + + + httpclient-cache + org.apache.httpcomponents + + + httpcore + org.apache.httpcomponents + + + httpmime + org.apache.httpcomponents + + @@ -225,6 +255,20 @@ + + + org.projectlombok + lombok + provided + + + + org.projectlombok + lombok-mapstruct-binding + provided + + + From ac1b79c0ffa9a1928afe11e95a5826a5a1cc7782 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 24 Jun 2023 19:38:54 +0800 Subject: [PATCH 0051/1069] fix: icon bug --- chat2db-client/.umirc.desktop.ts | 4 +-- chat2db-client/.umirc.prod.ts | 2 +- chat2db-client/.umirc.ts | 4 ++- chat2db-client/config/config.desktop.js | 3 --- chat2db-client/config/config.js | 3 --- chat2db-client/config/config.prod.js | 3 --- chat2db-client/package.json | 4 +-- .../src/components/Iconfont/index.less | 10 ++++++- .../src/components/Iconfont/index.tsx | 27 ++++++++++--------- .../pages/main/dashboard/chart-item/index.tsx | 22 ++++++++++++++- chat2db-client/src/pages/main/index.tsx | 2 +- chat2db-client/typings.d.ts | 7 ++++- 12 files changed, 59 insertions(+), 32 deletions(-) delete mode 100644 chat2db-client/config/config.desktop.js delete mode 100644 chat2db-client/config/config.js delete mode 100644 chat2db-client/config/config.prod.js diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index d715a73c7..53c37d1f2 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -19,10 +19,10 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - // publicPath: './', + publicPath: UMI_PublicPath, chainWebpack, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], define: { - 'process.env.UMI_ENV': 'desktop' + 'process.env.UMI_ENV': process.env.UMI_ENV, } }); diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 10da9963e..b2045040a 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -22,6 +22,6 @@ export default defineConfig({ publicPath: UMI_PublicPath, chainWebpack, define: { - 'process.env.UMI_ENV': 'prod' + 'process.env.UMI_ENV': process.env.UMI_ENV, } }); diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index d78ed0b65..beb10810f 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -45,5 +45,7 @@ export default defineConfig({ }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], - + define: { + 'process.env.UMI_ENV': process.env.UMI_ENV, + } }); diff --git a/chat2db-client/config/config.desktop.js b/chat2db-client/config/config.desktop.js deleted file mode 100644 index 7a6e78635..000000000 --- a/chat2db-client/config/config.desktop.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - 'process.env.UMI_ENV': 'desktop' -} \ No newline at end of file diff --git a/chat2db-client/config/config.js b/chat2db-client/config/config.js deleted file mode 100644 index 4106c379e..000000000 --- a/chat2db-client/config/config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - 'process.env.UMI_ENV': 'local' -} \ No newline at end of file diff --git a/chat2db-client/config/config.prod.js b/chat2db-client/config/config.prod.js deleted file mode 100644 index 45b14ad89..000000000 --- a/chat2db-client/config/config.prod.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - 'process.env.UMI_ENV': 'prod' -} \ No newline at end of file diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 0a5083caa..a61b9c550 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,7 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env umi dev" + "start:web": "umi dev" }, "dependencies": { "ahooks": "^3.7.7", @@ -69,7 +69,7 @@ "productName": "Chat2DB", "asar": false, "files": [ - "dist/", + "dist/**/*", "static/", "src/main", "package.json" diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 34999a6e2..0d539e8d1 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,3 +1,11 @@ +@font-face { + font-family: 'iconfont'; + /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), + url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); +} + .iconfont { font-family: 'iconfont' !important; font-size: 14px; @@ -6,4 +14,4 @@ -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale; -} +} \ No newline at end of file diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 61146a261..0447f1a7d 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -3,15 +3,16 @@ import classnames from 'classnames'; // import desktopStyle from './desktop.less'; import styles from './index.less'; -let container = '' -container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), - url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); - } -` +// let container = '' +// container = ` +// @font-face { +// font-family: 'iconfont'; /* Project id 3633546 */ +// src: url('../../assets/font/iconfont.woff2') format('woff2'), +// url('../../assets/font/iconfont.woff') format('woff'), +// url('../../assets/font/iconfont.ttf') format('truetype'); +// } +// ` + // if (process.env.UMI_ENV === 'desktop') { // } else { // container = ` @@ -23,10 +24,10 @@ container = ` // } // ` // } -let style = document.createElement("style"); -style.type = "text/css"; -document.head.appendChild(style); -style.appendChild(document.createTextNode(container)); +// let style = document.createElement("style"); +// style.type = "text/css"; +// document.head.appendChild(style); +// style.appendChild(document.createTextNode(container)); export default class Iconfont extends PureComponent<{ code: string; 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 edac080a5..c30ac9299 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -12,6 +12,7 @@ import { Button, Dropdown, Form, MenuProps, Select } from 'antd'; import { initChartItem } from '..'; import { deleteChart, getChartById } from '@/service/dashboard'; import { data } from '../../../../../mock/sqlResult.json'; +import Console from '@/components/Console'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; @@ -57,6 +58,7 @@ interface IChartItemProps { function ChartItem(props: IChartItemProps) { const [chartData, setChartData] = useState(); const [chartMetaData, setChartMetaData] = useState(); + const [consoleValue, setConsoleValue] = useState(); const [isEditing, setIsEditing] = useState(false); const [toggle, setToggle] = useState(false); const [form] = Form.useForm(); // 创建一个表单实例 @@ -176,7 +178,25 @@ function ChartItem(props: IChartItemProps) { return (
-
编辑区域
+
+ { + console.log('onExecuteSQL', result); + // setResultData(result); + }} + /> +
Date: Sat, 24 Jun 2023 19:41:59 +0800 Subject: [PATCH 0052/1069] fix: delete node_modules from package --- chat2db-client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index a61b9c550..d953df0b8 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -70,6 +70,7 @@ "asar": false, "files": [ "dist/**/*", + "!node_modules", "static/", "src/main", "package.json" From aff271127a473694de9594ee0394463a06c83531 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 24 Jun 2023 20:16:46 +0800 Subject: [PATCH 0053/1069] fix close ResultSet --- .../chat2db-server-start/src/main/resources/thymeleaf/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html index a988d072a..58f523bd5 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html +++ b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html @@ -26,7 +26,6 @@ name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> - - + From c76c082ffc3d11a0360db95ba0f4e2ec071b948c Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 24 Jun 2023 20:34:29 +0800 Subject: [PATCH 0056/1069] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 +- .../components/Console/MonacoEditor/index.tsx | 6 +- .../src/components/Console/index.tsx | 11 +- .../components/DraggableContainer/index.less | 1 + .../src/components/Iconfont/index.tsx | 22 +- .../src/components/StateIndicator/index.less | 4 +- chat2db-client/src/constants/common.ts | 19 +- chat2db-client/src/constants/index.ts | 2 +- chat2db-client/src/layouts/index.tsx | 2 + .../main/workspace/components/Tree/index.tsx | 29 +-- .../components/WorkspaceLeft/index.less | 4 + .../components/WorkspaceLeft/index.tsx | 19 +- .../components/WorkspaceRight/index.less | 16 -- .../components/WorkspaceRight/index.tsx | 188 +++++++++--------- .../components/WorkspaceRightItem/index.less | 21 ++ .../components/WorkspaceRightItem/index.tsx | 72 +++++++ chat2db-client/src/service/history.ts | 28 ++- chat2db-client/src/theme/common.ts | 2 - chat2db-client/src/typings/common.ts | 17 +- chat2db-client/src/typings/connection.ts | 4 +- chat2db-client/src/typings/index.ts | 7 + 21 files changed, 286 insertions(+), 191 deletions(-) create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index d96d669e2..404270783 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "echarts", "nsis", "pgsql", - "sortablejs" + "sortablejs", + "wireframe" ] } diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 98ac9415e..75cc94fd0 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -124,9 +124,9 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { useEffect(() => { const _ref = editorRef.current?.onDidChangeModelContent((e) => { - const curVal = editorRef.current.getValue(); - if (props.onChange) props.onChange(curVal, e); - setEditorVal(curVal); + const curVal = editorRef.current?.getValue(); + props.onChange?.(curVal || '', e); + setEditorVal(curVal || ''); }); return () => _ref && _ref.dispose(); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 6d9297c49..9f7d0f59e 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -36,6 +36,7 @@ interface IProps { /** 是否可以开启SQL转到自然语言的相关ai操作 */ hasAi2Lang: boolean; value?: string; + onChangeValue?: Function; executeParams: { databaseName: string; dataSourceId: number; @@ -48,7 +49,7 @@ interface IProps { } function Console(props: IProps) { - const { hasAiChat = true, value, executeParams } = props; + const { hasAiChat = true, value, executeParams, onChangeValue } = props; const uid = useMemo(() => uuid(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -61,6 +62,10 @@ function Console(props: IProps) { setContext(value); }, [value]); + useEffect(()=>{ + onChangeValue?.(value); + },[context]) + const onPressChatInput = (value: string) => { const params = formatParams({ message: value, @@ -126,7 +131,7 @@ function Console(props: IProps) { }); }; - const saveWindowTab = () => { + const saveConsole = () => { // let p = { // id: windowTab.consoleId, // name: windowTab?.name, @@ -183,7 +188,7 @@ function Console(props: IProps) { -
diff --git a/chat2db-client/src/components/DraggableContainer/index.less b/chat2db-client/src/components/DraggableContainer/index.less index 4fee3d99c..cfc90fbcf 100644 --- a/chat2db-client/src/components/DraggableContainer/index.less +++ b/chat2db-client/src/components/DraggableContainer/index.less @@ -24,6 +24,7 @@ } .box_column { + height: 100%; flex-direction: column; .divider { height: 1px; diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 61146a261..8722d9c0d 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -12,17 +12,17 @@ container = ` url('../../assets/font/iconfont.ttf') format('truetype'); } ` -// if (process.env.UMI_ENV === 'desktop') { -// } else { -// container = ` -// @font-face { -// font-family: 'iconfont'; /* Project id 3633546 */ -// src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), -// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), -// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); -// } -// ` -// } +if (process.env.UMI_ENV === 'desktop') { +} else { + container = ` + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); + } + ` +} let style = document.createElement("style"); style.type = "text/css"; document.head.appendChild(style); diff --git a/chat2db-client/src/components/StateIndicator/index.less b/chat2db-client/src/components/StateIndicator/index.less index fa5267884..41537fb3c 100644 --- a/chat2db-client/src/components/StateIndicator/index.less +++ b/chat2db-client/src/components/StateIndicator/index.less @@ -13,8 +13,8 @@ } .empty { - width: 200px; - height: 200px; + // width: 200px; + // height: 200px; // background-image: url('../../assets/no-data.png'); background-size: cover; background-repeat: no-repeat; diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 44518342c..4cdf94a42 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -1,3 +1,18 @@ +export enum DatabaseTypeCode { + MYSQL = 'MYSQL', + ORACLE = 'ORACLE', + DB2 = 'DB2', + MONGODB = 'MONGODB', + REDIS = 'REDIS', + H2 = 'H2', + POSTGRESQL = 'POSTGRESQL', + SQLSERVER = 'SQLSERVER', + SQLITE = 'SQLITE', + MARIADB = 'MARIADB', + CLICKHOUSE = 'CLICKHOUSE', + DM = "DM", +} + export enum ThemeType { Light = 'light', Dark = 'dark', @@ -13,9 +28,9 @@ export enum PrimaryColorType { export enum LangType { EN_US = 'en-us', ZH_CN = 'zh-cn' -} +} -export enum TabOpened { +export enum ConsoleOpenedStatus { IS_OPEN = 'y', NOT_OPEN = 'n', } diff --git a/chat2db-client/src/constants/index.ts b/chat2db-client/src/constants/index.ts index 582fc5c63..64263709d 100644 --- a/chat2db-client/src/constants/index.ts +++ b/chat2db-client/src/constants/index.ts @@ -1,7 +1,7 @@ -// 这样导出有什么用呢 export * as Environment from './environment'; export * as Database from './database'; export * as appConfig from './appConfig'; export * as theme from './theme'; export * as common from './common'; +export * as tree from './tree'; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 5a5800835..8af639ebb 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -32,6 +32,8 @@ declare global { const __BUILD_TIME__: string; } +console.log(process.env.UMI_ENV); + window._ENV = process.env.UMI_ENV! || 'local'; window._Lang = getLang(); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index 33bdac42a..33c05dfaf 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -45,25 +45,18 @@ function Tree(props: IProps) { } } - // useImperativeHandle(cRef, () => ({ - // getDataSource, - // filtrationDataTree - // })) - return
- - { - (searchedTreeData || treeData)?.map((item, index) => { - return - }) - } - + { + (searchedTreeData || treeData)?.map((item, index) => { + return + }) + }
}; 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 a64c45022..3b1563c6c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -66,3 +66,7 @@ .tree{ margin: 0px -10px; } + +.left_box_title{ + margin-bottom: 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 4078ed792..e2afee5e9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -13,6 +13,7 @@ import { ITreeNode } from '@/typings/tree'; import { useReducerContext } from '../../index'; import { workspaceActionType } from '../../context'; import i18n from '@/i18n'; +import { IConsole } from '@/typings/common' interface IProps { className?: string; } @@ -25,7 +26,7 @@ export default memo(function WorkspaceLeft(props) {
-
Save
+
@@ -167,10 +168,12 @@ function RenderSelectDatabase() { function RenderTableBox() { const { state, dispatch } = useReducerContext(); const { currentWorkspaceData } = state; - const [initialData, setInitialData] = useState(); + const [initialData, setInitialData] = useState([]); useEffect(() => { - getInitialData(); + if(currentWorkspaceData.databaseName){ + getInitialData(); + } }, [currentWorkspaceData]); function getInitialData() { @@ -186,13 +189,16 @@ function RenderTableBox() { return (
- +
Table
+ + +
); } function RenderSaveBox() { - const [savedList, setSaveList] = useState(); + const [savedList, setSaveList] = useState([]); const { state, dispatch } = useReducerContext(); const { currentWorkspaceData } = state; @@ -214,8 +220,7 @@ function RenderSaveBox() { } return
-
Saved
- +
Saved
{ 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 72f32bfa9..fe90a277b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -31,22 +31,6 @@ display: block; } -.boxRightCenter { - height: 100%; -} - -.boxRightConsole { - height: 50vh; - overflow: hidden; -} - -.boxRightResult { - border-top: 1px solid var(--color-border); - padding: 10px; - height: 0px; - flex: 1; -} - .ears { position: absolute; top: 0; 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 edea6bd5f..bc11687a2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -3,7 +3,7 @@ import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import Console from '@/components/Console'; -import { TabOpened, ConsoleStatus, consoleTopComment } from '@/constants/common'; +import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment } from '@/constants/common'; import { DatabaseTypeCode } from '@/constants/database'; import { IConsole } from '@/typings/common'; import historyService from '@/service/history'; @@ -12,7 +12,7 @@ import { useReducerContext } from '@/pages/main/workspace'; import { workspaceActionType } from '@/pages/main/workspace/context'; import SearchResult from '@/components/SearchResult'; import LoadingContent from '@/components/Loading/LoadingContent'; -import { IManageResultData } from '@/typings/database'; +import WorkspaceRightItem from '../WorkspaceRightItem'; interface IProps { className?: string; @@ -20,62 +20,54 @@ interface IProps { export default memo(function WorkspaceRight(props) { const { className } = props; - const draggableRef = useRef(); const [consoleList, setConsoleList] = useState(); const [activeConsoleId, setActiveConsoleId] = useState(); const { state, dispatch } = useReducerContext(); const { dblclickTreeNodeData, currentWorkspaceData } = state; - const [consoleValue, setConsoleValue] = useState(); - const [resultData, setResultData] = useState([]); useEffect(() => { getConsoleList(); }, [currentWorkspaceData]); useEffect(() => { - if (dblclickTreeNodeData) { - const { extraParams } = dblclickTreeNodeData; - const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; - let flag = false; - - consoleList?.forEach((i) => { - if (i.databaseName === databaseName && i.dataSourceId === dataSourceId) { - flag = true; - setActiveConsoleId(i.id); - setConsoleValue(`SELECT * FROM ${tableName}`); - } - }); + if(!dblclickTreeNodeData){ + return + } + const { extraParams } = dblclickTreeNodeData; + const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; + let flag = false; + const ddl = `SELECT * FROM ${tableName};`; + + consoleList?.forEach((i) => { + if (i.databaseName === databaseName && i.dataSourceId === dataSourceId) { + flag = true; + setActiveConsoleId(i.id); + } + }); - if (!flag) { - const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); - let p = { - name: name, - type: databaseType, - dataSourceId: dataSourceId, - databaseName: databaseName, - schemaName: schemaName, - status: ConsoleStatus.DRAFT, - ddl: `${consoleTopComment}`, - tabOpened: TabOpened.IS_OPEN, + if (!flag) { + const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); + let p = { + name: name, + type: databaseType!, + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName: schemaName!, + dataSourceName: dataSourceName!, + status: ConsoleStatus.DRAFT, + ddl, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + connectable: true, + }; + + historyService.saveConsole(p).then((res) => { + const newConsole: IConsole = { + id: res, + ...p, }; - - historyService.saveWindowTab(p).then((res) => { - const newConsole: IConsole = { - name: name, - databaseType: databaseType!, - databaseName: databaseName!, - dataSourceId: dataSourceId!, - dataSourceName: dataSourceName!, - schemaName: schemaName!, - id: res, - ddl: `${consoleTopComment}`, - status: ConsoleStatus.DRAFT, - }; - setActiveConsoleId(newConsole.id); - setConsoleList([...(consoleList || []), newConsole]); - console.log([...(consoleList || []), newConsole]); - }); - } + setActiveConsoleId(newConsole.id); + setConsoleList([...(consoleList || []), newConsole]); + }); } }, [dblclickTreeNodeData]); @@ -83,7 +75,7 @@ export default memo(function WorkspaceRight(props) { let p = { pageNo: 1, pageSize: 999, - tabOpened: TabOpened.IS_OPEN, + ConsoleOpenedStatus: ConsoleOpenedStatus.IS_OPEN, ...currentWorkspaceData, }; @@ -96,12 +88,13 @@ export default memo(function WorkspaceRight(props) { id: item.id, ddl: item.ddl, name: item.name, - databaseType: item.type, + type: item.type, status: item.status, databaseName: item.databaseName, dataSourceName: item.dataSourceName, dataSourceId: item.dataSourceId, schemaName: item.schemaName, + connectable: true }); } }); @@ -117,24 +110,42 @@ export default memo(function WorkspaceRight(props) { setConsoleList(newWindowList); - // if (!flag && activeConsoleId) { - // historyService.getWindowTab({ id: consoleId }).then((res: any) => { - // if (res.connectable) { - // newWindowList.push({ - // id: res.id, - // ddl: res.ddl, - // name: res.name, - // status: res.status, - // databaseType: res.type, - // databaseName: res.databaseName, - // dataSourceName: res.dataSourceName, - // dataSourceId: res.dataSourceId, - // schemaName: res.schemaName, - // }); - // setActiveConsoleId(res.id); - // setConsoleList(newWindowList); + // if (!flag) { + // if (activeConsoleId) { + // historyService.getWindowTab({ id: activeConsoleId }).then((res: any) => { + // if (res.connectable) { + // newWindowList.push({ + // id: res.id, + // ddl: res.ddl, + // name: res.name, + // status: res.status, + // type: res.type, + // databaseName: res.databaseName, + // dataSourceName: res.dataSourceName, + // dataSourceId: res.dataSourceId, + // schemaName: res.schemaName, + // connectable: true, + // }); + // setActiveConsoleId(res.id); + // setConsoleList(newWindowList); + // } + // }); + // } else { + // let p = { + // name: 'default name', + // ddl: 'string', + // dataSourceId: currentWorkspaceData.dataSourceId, + // databaseName: currentWorkspaceData.databaseName, + // type: currentWorkspaceData.databaseType, + // status: ConsoleStatus.DRAFT, + // connectable: true, + // tabOpened: ConsoleOpenedStatus.IS_OPEN // } - // }); + // historyService.saveConsole(p).then(res => { + // setActiveConsoleId(res); + // // getConsoleList(); + // }) + // } // } else { // setConsoleList(newWindowList); // } @@ -173,7 +184,7 @@ export default memo(function WorkspaceRight(props) { let p: any = { id: targetKey, - tabOpened: 'n', + ConsoleOpenedStatus: 'n', }; const window = consoleList?.find((t) => t.id === +targetKey); @@ -211,36 +222,21 @@ export default memo(function WorkspaceRight(props) { />
{consoleList?.map((t, index) => { - return ( -
- -
- { - console.log('onExecuteSQL', result); - setResultData(result); - }} - /> -
-
- - - -
-
-
- ); + return
+ +
})}
diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less new file mode 100644 index 000000000..068c2b198 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less @@ -0,0 +1,21 @@ +@import '../../../../../styles/var.less'; + +.box { + height: 100%; +} + +.boxRightCenter { + height: 100%; +} + +.boxRightConsole { + height: 50vh; + overflow: hidden; +} + +.boxRightResult { + border-top: 1px solid var(--color-border); + padding: 10px; + height: 0px; + flex: 1; +} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx new file mode 100644 index 000000000..be83d06cc --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -0,0 +1,72 @@ +import React, { memo, useRef,useState, useEffect } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import DraggableContainer from '@/components/DraggableContainer'; +import Console from '@/components/Console'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import SearchResult from '@/components/SearchResult'; +import {DatabaseTypeCode} from '@/constants/common'; +import { IManageResultData } from '@/typings/database'; +import { useReducerContext } from '@/pages/main/workspace'; + +interface IProps { + className?: string; + data: { + databaseName: string; + dataSourceId: number; + type: DatabaseTypeCode; + schemaName: string; + consoleId: number; + consoleName: string; + initDDL: string; + }; +} + +export default memo(function WorkspaceRightItem(props) { + const { className, data } = props; + const draggableRef = useRef(); + const [consoleValue, setConsoleValue] = useState(data.initDDL || ''); + const [resultData, setResultData] = useState([]); + const { state, dispatch } = useReducerContext(); + const { dblclickTreeNodeData, currentWorkspaceData } = state; + + + useEffect(() => { + if(!dblclickTreeNodeData){ + return + } + const { extraParams } = dblclickTreeNodeData; + const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; + const ddl = `SELECT * FROM ${tableName};`; + + if (data.databaseName === databaseName && data.dataSourceId === dataSourceId) { + setConsoleValue(`${consoleValue}\n${ddl}`) + } + }, [dblclickTreeNodeData]); + + return
+ +
+ { + setConsoleValue(value) + } + } + onExecuteSQL={(result) => { + setResultData(result); + }} + /> +
+
+ + + +
+
+
+}) diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index a8606429f..eb9359bbd 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -1,12 +1,12 @@ import createRequest from "./base"; // import { IPageResponse,IPageParams,IHistoryRecord, IWindowTab, ISavedConsole } from '@/types'; -// import { DatabaseTypeCode, ConsoleStatus, TabOpened } from '@/utils/constants' -import {ISaveConsole,IConsole,IPageResponse,IPageParams} from '@/typings/common'; +import { ConsoleOpenedStatus, DatabaseTypeCode } from '@/constants/common' +import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings/common'; -export interface IGetHistoryListParams extends IPageParams { +export interface IGetSavedListParams extends IPageParams { dataSourceId?: string; databaseName?: string; - tabOpened?: TabOpened; + ConsoleOpenedStatus?: ConsoleOpenedStatus; } export interface ISaveBasicInfo { name: string; @@ -15,10 +15,6 @@ export interface ISaveBasicInfo { dataSourceId: number; databaseName: string; } -export interface ISaveConsole extends ISaveBasicInfo { - status: ConsoleStatus; - tabOpened: TabOpened; -} export interface IUpdateWindowParams { id: number; @@ -28,25 +24,25 @@ export interface IUpdateWindowParams { databaseName: string; } -const saveWindowTab = createRequest('/api/operation/saved/create', { method: 'post' }); +const saveConsole = createRequest('/api/operation/saved/create', { method: 'post' }); -const getWindowTab = createRequest<{id:string}, number>('/api/operation/saved/:id',{method: 'get'}); +const getWindowTab = createRequest<{ id: number }, number>('/api/operation/saved/:id', { method: 'get' }); -const updateWindowTab = createRequest('/api/operation/saved/update',{method: 'post'}); +const updateWindowTab = createRequest('/api/operation/saved/update', { method: 'post' }); -const getSaveList = createRequest>('/api/operation/saved/list',{}); +const getSaveList = createRequest>('/api/operation/saved/list', {}); -const deleteWindowTab = createRequest<{id: number}, string>('/api/operation/saved/:id',{method: 'delete'}); +const deleteWindowTab = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); -const createHistory = createRequest('/api/operation/log/create',{method: 'post'}); +const createHistory = createRequest('/api/operation/log/create', { method: 'post' }); -const getHistoryList = createRequest>('/api/operation/log/list',{}); +const getHistoryList = createRequest>('/api/operation/log/list', {}); export default { getSaveList, updateWindowTab, getHistoryList, - saveWindowTab, + saveConsole, deleteWindowTab, createHistory, getWindowTab diff --git a/chat2db-client/src/theme/common.ts b/chat2db-client/src/theme/common.ts index 06449f6bc..26aceabba 100644 --- a/chat2db-client/src/theme/common.ts +++ b/chat2db-client/src/theme/common.ts @@ -1,6 +1,4 @@ export const commonToken = { - fontSize1: 14, wireframe: true, borderRadius: 4, - borderRadiusLG: 12, }; diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 7332ee67a..a373e6579 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -1,4 +1,5 @@ import { DatabaseTypeCode } from '@/constants/database'; +import { ConsoleOpenedStatus, ConsoleStatus } from '@/constants/common'; export interface IPageResponse { data: T[]; pageNo: number; @@ -14,7 +15,7 @@ export interface IPageParams { pageSize: number; } -export interface ISaveConsole { +export interface IConsole { id: number; name: string; ddl: string; @@ -25,16 +26,8 @@ export interface ISaveConsole { type: DatabaseTypeCode; status: string; connectable: boolean; + tabOpened?: ConsoleOpenedStatus; } -export interface IConsole { - id: number; - name: string; - ddl: string; - dataSourceId: number; - databaseName: string; - dataSourceName: string; - schemaName: string; - databaseType: DatabaseTypeCode; - status: string; -} \ No newline at end of file +export type ICreateConsole = Omit + diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index afee15eb9..abbe72c45 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -13,9 +13,11 @@ export interface IConnectionDetails { user: string; password: string; type: DatabaseTypeCode; - tabOpened: 'y' | 'n'; + ConsoleOpenedStatus: 'y' | 'n'; EnvType: ConnectionEnv; extendInfo: IConnectionExtendInfoItem[]; ssh: any; [key: string]: any; } + +export type ICreateConnectionDetails = Omit diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index e69de29bb..6714b8d94 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -0,0 +1,7 @@ +export * as tree from './tree'; +export * as common from './common'; +export * as database from './database'; +export * as dashboard from './dashboard'; +export * as connection from './connection'; +export * as theme from './theme'; +export * as main from './main'; \ No newline at end of file From 2a72aefbd59d020bc48a07b447211280c4ff4074 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sun, 25 Jun 2023 16:40:25 +0800 Subject: [PATCH 0057/1069] =?UTF-8?q?umi=20=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.desktop.ts | 5 +-- chat2db-client/.umirc.prod.ts | 3 -- chat2db-client/.umirc.ts | 7 +++- chat2db-client/config/config.desktop.js | 3 -- chat2db-client/config/config.js | 3 -- chat2db-client/config/config.prod.js | 3 -- chat2db-client/package.json | 5 +-- .../src/components/Iconfont/index.tsx | 41 ++++++++++--------- chat2db-client/src/layouts/index.tsx | 5 ++- chat2db-client/typings.d.ts | 2 - 10 files changed, 35 insertions(+), 42 deletions(-) delete mode 100644 chat2db-client/config/config.desktop.js delete mode 100644 chat2db-client/config/config.js delete mode 100644 chat2db-client/config/config.prod.js diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index d715a73c7..2df48bc12 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -3,6 +3,8 @@ import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const UMI_PublicPath = process.env.UMI_PublicPath || './'; +console.log('woshi','.umirc.desktop') + const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { @@ -22,7 +24,4 @@ export default defineConfig({ // publicPath: './', chainWebpack, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], - define: { - 'process.env.UMI_ENV': 'desktop' - } }); diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 10da9963e..a94d9998e 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -21,7 +21,4 @@ const chainWebpack = (config: any, { webpack }: any) => { export default defineConfig({ publicPath: UMI_PublicPath, chainWebpack, - define: { - 'process.env.UMI_ENV': 'prod' - } }); diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index d78ed0b65..adb94353d 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -1,6 +1,7 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); +console.log(process.env.UMI_ENV) const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ @@ -45,5 +46,9 @@ export default defineConfig({ }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], - + define: { + 'process.env': { + 'UMI_ENV': process.env.UMI_ENV + } + } }); diff --git a/chat2db-client/config/config.desktop.js b/chat2db-client/config/config.desktop.js deleted file mode 100644 index 7a6e78635..000000000 --- a/chat2db-client/config/config.desktop.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - 'process.env.UMI_ENV': 'desktop' -} \ No newline at end of file diff --git a/chat2db-client/config/config.js b/chat2db-client/config/config.js deleted file mode 100644 index 4106c379e..000000000 --- a/chat2db-client/config/config.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - 'process.env.UMI_ENV': 'local' -} \ No newline at end of file diff --git a/chat2db-client/config/config.prod.js b/chat2db-client/config/config.prod.js deleted file mode 100644 index 45b14ad89..000000000 --- a/chat2db-client/config/config.prod.js +++ /dev/null @@ -1,3 +0,0 @@ -export default { - 'process.env.UMI_ENV': 'prod' -} \ No newline at end of file diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 0a5083caa..1689aef13 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,7 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env umi dev" + "start:web": "cross-env UMI_ENV=desktop umi dev" }, "dependencies": { "ahooks": "^3.7.7", @@ -51,7 +51,6 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", - "electron": "^25.0.1", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", @@ -100,4 +99,4 @@ "icon": "src/assets/logo/logo.png" } } -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 61146a261..04d1a0146 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -3,26 +3,27 @@ import classnames from 'classnames'; // import desktopStyle from './desktop.less'; import styles from './index.less'; -let container = '' -container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), - url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); - } -` -// if (process.env.UMI_ENV === 'desktop') { -// } else { -// container = ` -// @font-face { -// font-family: 'iconfont'; /* Project id 3633546 */ -// src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), -// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), -// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); -// } -// ` -// } +let container = ''; + +if (process.env.UMI_ENV === 'desktop') { + container = ` + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), + url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); + } + ` +} else { + container = ` + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); + } + ` +} let style = document.createElement("style"); style.type = "text/css"; document.head.appendChild(style); diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 5a5800835..c487251be 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -32,7 +32,10 @@ declare global { const __BUILD_TIME__: string; } -window._ENV = process.env.UMI_ENV! || 'local'; +console.log(process.env.UMI_ENV) +console.log(process.env) + +// window._ENV = process.env.UMI_ENV! || 'local'; window._Lang = getLang(); const { getDesignToken, useToken } = theme; diff --git a/chat2db-client/typings.d.ts b/chat2db-client/typings.d.ts index 3b03b2904..9ae2fc424 100644 --- a/chat2db-client/typings.d.ts +++ b/chat2db-client/typings.d.ts @@ -1,3 +1 @@ import 'umi/typings'; - -// declare const process.env.UMI_ENV: string; \ No newline at end of file From e88301f2463a532e10111407488c27d0c740119e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sun, 25 Jun 2023 17:47:42 +0800 Subject: [PATCH 0058/1069] fix:iconfont --- chat2db-client/.umirc.desktop.ts | 11 ++------ chat2db-client/.umirc.prod.ts | 7 ------ chat2db-client/.umirc.ts | 18 +++++-------- chat2db-client/package.json | 2 +- .../src/components/Iconfont/index.less | 5 ++++ .../src/components/Iconfont/index.tsx | 25 ++++++------------- chat2db-client/src/layouts/index.tsx | 5 +--- chat2db-client/src/utils/common.ts | 22 ---------------- 8 files changed, 23 insertions(+), 72 deletions(-) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index 2df48bc12..855bdeed8 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -3,21 +3,14 @@ import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const UMI_PublicPath = process.env.UMI_PublicPath || './'; -console.log('woshi','.umirc.desktop') - const chainWebpack = (config: any, { webpack }: any) => { + new webpack.DefinePlugin({ + }); config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index a94d9998e..ff4485bc8 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -9,13 +9,6 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index adb94353d..780c6cc78 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -9,13 +9,6 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ @@ -44,11 +37,12 @@ export default defineConfig({ changeOrigin: true, }, }, + define: { + __ENV: process.env.UMI_ENV , + __BUILD_TIME__: formatDate(new Date(), 'yyyyMMddhhmmss'), + __APP_VERSION__: process.env.APP_VERSION || '0.0.0', + }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], - define: { - 'process.env': { - 'UMI_ENV': process.env.UMI_ENV - } - } + }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 1689aef13..dd24e3a01 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,7 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env UMI_ENV=desktop umi dev" + "start:web": "cross-env UMI_ENV=local umi dev" }, "dependencies": { "ahooks": "^3.7.7", diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 34999a6e2..eec834c04 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,3 +1,8 @@ +@font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); +} .iconfont { font-family: 'iconfont' !important; font-size: 14px; diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 04d1a0146..42fdaa3c9 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -3,19 +3,9 @@ import classnames from 'classnames'; // import desktopStyle from './desktop.less'; import styles from './index.less'; -let container = ''; - -if (process.env.UMI_ENV === 'desktop') { - container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), - url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); - } - ` -} else { - container = ` +// 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font +if (__ENV === 'local') { + let container = ` @font-face { font-family: 'iconfont'; /* Project id 3633546 */ src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), @@ -23,11 +13,12 @@ if (process.env.UMI_ENV === 'desktop') { url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); } ` + let style = document.createElement("style"); + style.type = "text/css"; + document.head.appendChild(style); + style.appendChild(document.createTextNode(container)); } -let style = document.createElement("style"); -style.type = "text/css"; -document.head.appendChild(style); -style.appendChild(document.createTextNode(container)); + export default class Iconfont extends PureComponent<{ code: string; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index c487251be..f6b2d783a 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -30,12 +30,9 @@ declare global { } const __APP_VERSION__: string; const __BUILD_TIME__: string; + const __ENV: string; } -console.log(process.env.UMI_ENV) -console.log(process.env) - -// window._ENV = process.env.UMI_ENV! || 'local'; window._Lang = getLang(); const { getDesignToken, useToken } = theme; diff --git a/chat2db-client/src/utils/common.ts b/chat2db-client/src/utils/common.ts index 8f94e944b..e69de29bb 100644 --- a/chat2db-client/src/utils/common.ts +++ b/chat2db-client/src/utils/common.ts @@ -1,22 +0,0 @@ -export function uuid() { - var s = []; - var hexDigits = '0123456789abcdef'; - for (var i = 0; i < 36; i++) { - s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); - } - s[14] = '4'; // bits 12-15 of the time_hi_and_version field to 0010 - s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 - s[8] = s[13] = s[18] = s[23] = '-'; - - var uuid = s.join(''); - return uuid; -} -export function formatParams(obj: { [key: string]: any }) { - let params = ''; - for (let key in obj) { - if (obj[key]) { - params += `${key}=${obj[key]}&`; - } - } - return params; -} From 5c7ab43226efc9913462c253e44cbbeb2a0273e7 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 25 Jun 2023 20:49:58 +0800 Subject: [PATCH 0059/1069] test release --- README.md | 9 --------- README_CN.md | 10 +--------- 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/README.md b/README.md index 5c2c567f9..4d7f88794 100644 --- a/README.md +++ b/README.md @@ -195,15 +195,6 @@ Please star and fork on GitHub before joining the group. Follow our WeChat public account
- - - - -Ding Talk:9135032392 - -QQ:863576619 - - ## ❤️ Acknowledgements Thanks to all the students who contributed to Chat2DB~ diff --git a/README_CN.md b/README_CN.md index 36a3cd414..d88f2920b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -211,18 +211,10 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 ## ☎️ 联系我们 -加群前请先Star和Fork,谢谢~关注微信公众号,回复"AI",关注Chat2DB最新动态和更新。 +加群前请先Star和Fork,谢谢~关注微信公众号可加入微信、钉钉、QQ群一起讨论,并可以获取Chat2DB最新动态和更新。 - - -钉钉:33285019116 - -QQ:863576619 - - - ## ❤️ 致谢 感谢所有为Chat2DB贡献力量的同学们~ From 07ccb6fbf4e9f31b239d04e4435bb9dbdc73df07 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 25 Jun 2023 22:47:05 +0800 Subject: [PATCH 0060/1069] =?UTF-8?q?style:=20Connections=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/index.less | 4 ++ chat2db-client/src/blocks/Setting/index.tsx | 2 +- .../src/components/Iconfont/index.tsx | 12 ++-- .../src/pages/main/connections/index.less | 37 +++++------ .../src/pages/main/connections/index.tsx | 62 +++++++++---------- chat2db-client/src/pages/main/index.less | 33 ++++++---- chat2db-client/src/pages/main/index.tsx | 13 ++-- chat2db-client/src/styles/global.less | 5 +- chat2db-client/src/theme/abandon/dark.less | 44 ------------- chat2db-client/src/theme/abandon/light.less | 44 ------------- chat2db-client/src/theme/common.ts | 2 +- chat2db-client/src/theme/custom/dark.less | 3 + chat2db-client/src/theme/custom/light.less | 3 + chat2db-client/src/theme/light.ts | 2 +- chat2db-client/src/typings/main.ts | 1 + 15 files changed, 99 insertions(+), 168 deletions(-) delete mode 100644 chat2db-client/src/theme/abandon/dark.less delete mode 100644 chat2db-client/src/theme/abandon/light.less create mode 100644 chat2db-client/src/theme/custom/dark.less create mode 100644 chat2db-client/src/theme/custom/light.less diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index 4627e3939..c8f0a6649 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -4,6 +4,10 @@ .f-icon-button(); } +.settingIcon{ + color: var(--custom-color-icon); +} + .backgroundList { display: flex; margin: 0px 0px 20px -7px; diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 8354c963a..35dd4a81f 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -125,7 +125,7 @@ export default memo(function Setting({ className, text }) { {text ? ( {text} ) : ( - + )}
{ setCurConnection(menu.meta); }} >
{icon} {label} @@ -135,15 +135,6 @@ function Connections(props: IProps) {
{i18n('connection.title.connections')}
{renderMenu()} - {/*
- -
*/} -
diff --git a/chat2db-client/src/components/DraggableContainer/index.less b/chat2db-client/src/components/DraggableContainer/index.less index 4fee3d99c..cfc90fbcf 100644 --- a/chat2db-client/src/components/DraggableContainer/index.less +++ b/chat2db-client/src/components/DraggableContainer/index.less @@ -24,6 +24,7 @@ } .box_column { + height: 100%; flex-direction: column; .divider { height: 1px; diff --git a/chat2db-client/src/components/StateIndicator/index.less b/chat2db-client/src/components/StateIndicator/index.less index fa5267884..41537fb3c 100644 --- a/chat2db-client/src/components/StateIndicator/index.less +++ b/chat2db-client/src/components/StateIndicator/index.less @@ -13,8 +13,8 @@ } .empty { - width: 200px; - height: 200px; + // width: 200px; + // height: 200px; // background-image: url('../../assets/no-data.png'); background-size: cover; background-repeat: no-repeat; diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 44518342c..4cdf94a42 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -1,3 +1,18 @@ +export enum DatabaseTypeCode { + MYSQL = 'MYSQL', + ORACLE = 'ORACLE', + DB2 = 'DB2', + MONGODB = 'MONGODB', + REDIS = 'REDIS', + H2 = 'H2', + POSTGRESQL = 'POSTGRESQL', + SQLSERVER = 'SQLSERVER', + SQLITE = 'SQLITE', + MARIADB = 'MARIADB', + CLICKHOUSE = 'CLICKHOUSE', + DM = "DM", +} + export enum ThemeType { Light = 'light', Dark = 'dark', @@ -13,9 +28,9 @@ export enum PrimaryColorType { export enum LangType { EN_US = 'en-us', ZH_CN = 'zh-cn' -} +} -export enum TabOpened { +export enum ConsoleOpenedStatus { IS_OPEN = 'y', NOT_OPEN = 'n', } diff --git a/chat2db-client/src/constants/index.ts b/chat2db-client/src/constants/index.ts index 582fc5c63..64263709d 100644 --- a/chat2db-client/src/constants/index.ts +++ b/chat2db-client/src/constants/index.ts @@ -1,7 +1,7 @@ -// 这样导出有什么用呢 export * as Environment from './environment'; export * as Database from './database'; export * as appConfig from './appConfig'; export * as theme from './theme'; export * as common from './common'; +export * as tree from './tree'; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index 33bdac42a..33c05dfaf 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -45,25 +45,18 @@ function Tree(props: IProps) { } } - // useImperativeHandle(cRef, () => ({ - // getDataSource, - // filtrationDataTree - // })) - return
- - { - (searchedTreeData || treeData)?.map((item, index) => { - return - }) - } - + { + (searchedTreeData || treeData)?.map((item, index) => { + return + }) + }
}; 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 a64c45022..3b1563c6c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -66,3 +66,7 @@ .tree{ margin: 0px -10px; } + +.left_box_title{ + margin-bottom: 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 4078ed792..e2afee5e9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -13,6 +13,7 @@ import { ITreeNode } from '@/typings/tree'; import { useReducerContext } from '../../index'; import { workspaceActionType } from '../../context'; import i18n from '@/i18n'; +import { IConsole } from '@/typings/common' interface IProps { className?: string; } @@ -25,7 +26,7 @@ export default memo(function WorkspaceLeft(props) {
-
Save
+
@@ -167,10 +168,12 @@ function RenderSelectDatabase() { function RenderTableBox() { const { state, dispatch } = useReducerContext(); const { currentWorkspaceData } = state; - const [initialData, setInitialData] = useState(); + const [initialData, setInitialData] = useState([]); useEffect(() => { - getInitialData(); + if(currentWorkspaceData.databaseName){ + getInitialData(); + } }, [currentWorkspaceData]); function getInitialData() { @@ -186,13 +189,16 @@ function RenderTableBox() { return (
- +
Table
+ + +
); } function RenderSaveBox() { - const [savedList, setSaveList] = useState(); + const [savedList, setSaveList] = useState([]); const { state, dispatch } = useReducerContext(); const { currentWorkspaceData } = state; @@ -214,8 +220,7 @@ function RenderSaveBox() { } return
-
Saved
- +
Saved
{ 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 72f32bfa9..fe90a277b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -31,22 +31,6 @@ display: block; } -.boxRightCenter { - height: 100%; -} - -.boxRightConsole { - height: 50vh; - overflow: hidden; -} - -.boxRightResult { - border-top: 1px solid var(--color-border); - padding: 10px; - height: 0px; - flex: 1; -} - .ears { position: absolute; top: 0; 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 edea6bd5f..bc11687a2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -3,7 +3,7 @@ import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import Console from '@/components/Console'; -import { TabOpened, ConsoleStatus, consoleTopComment } from '@/constants/common'; +import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment } from '@/constants/common'; import { DatabaseTypeCode } from '@/constants/database'; import { IConsole } from '@/typings/common'; import historyService from '@/service/history'; @@ -12,7 +12,7 @@ import { useReducerContext } from '@/pages/main/workspace'; import { workspaceActionType } from '@/pages/main/workspace/context'; import SearchResult from '@/components/SearchResult'; import LoadingContent from '@/components/Loading/LoadingContent'; -import { IManageResultData } from '@/typings/database'; +import WorkspaceRightItem from '../WorkspaceRightItem'; interface IProps { className?: string; @@ -20,62 +20,54 @@ interface IProps { export default memo(function WorkspaceRight(props) { const { className } = props; - const draggableRef = useRef(); const [consoleList, setConsoleList] = useState(); const [activeConsoleId, setActiveConsoleId] = useState(); const { state, dispatch } = useReducerContext(); const { dblclickTreeNodeData, currentWorkspaceData } = state; - const [consoleValue, setConsoleValue] = useState(); - const [resultData, setResultData] = useState([]); useEffect(() => { getConsoleList(); }, [currentWorkspaceData]); useEffect(() => { - if (dblclickTreeNodeData) { - const { extraParams } = dblclickTreeNodeData; - const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; - let flag = false; - - consoleList?.forEach((i) => { - if (i.databaseName === databaseName && i.dataSourceId === dataSourceId) { - flag = true; - setActiveConsoleId(i.id); - setConsoleValue(`SELECT * FROM ${tableName}`); - } - }); + if(!dblclickTreeNodeData){ + return + } + const { extraParams } = dblclickTreeNodeData; + const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; + let flag = false; + const ddl = `SELECT * FROM ${tableName};`; + + consoleList?.forEach((i) => { + if (i.databaseName === databaseName && i.dataSourceId === dataSourceId) { + flag = true; + setActiveConsoleId(i.id); + } + }); - if (!flag) { - const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); - let p = { - name: name, - type: databaseType, - dataSourceId: dataSourceId, - databaseName: databaseName, - schemaName: schemaName, - status: ConsoleStatus.DRAFT, - ddl: `${consoleTopComment}`, - tabOpened: TabOpened.IS_OPEN, + if (!flag) { + const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); + let p = { + name: name, + type: databaseType!, + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName: schemaName!, + dataSourceName: dataSourceName!, + status: ConsoleStatus.DRAFT, + ddl, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + connectable: true, + }; + + historyService.saveConsole(p).then((res) => { + const newConsole: IConsole = { + id: res, + ...p, }; - - historyService.saveWindowTab(p).then((res) => { - const newConsole: IConsole = { - name: name, - databaseType: databaseType!, - databaseName: databaseName!, - dataSourceId: dataSourceId!, - dataSourceName: dataSourceName!, - schemaName: schemaName!, - id: res, - ddl: `${consoleTopComment}`, - status: ConsoleStatus.DRAFT, - }; - setActiveConsoleId(newConsole.id); - setConsoleList([...(consoleList || []), newConsole]); - console.log([...(consoleList || []), newConsole]); - }); - } + setActiveConsoleId(newConsole.id); + setConsoleList([...(consoleList || []), newConsole]); + }); } }, [dblclickTreeNodeData]); @@ -83,7 +75,7 @@ export default memo(function WorkspaceRight(props) { let p = { pageNo: 1, pageSize: 999, - tabOpened: TabOpened.IS_OPEN, + ConsoleOpenedStatus: ConsoleOpenedStatus.IS_OPEN, ...currentWorkspaceData, }; @@ -96,12 +88,13 @@ export default memo(function WorkspaceRight(props) { id: item.id, ddl: item.ddl, name: item.name, - databaseType: item.type, + type: item.type, status: item.status, databaseName: item.databaseName, dataSourceName: item.dataSourceName, dataSourceId: item.dataSourceId, schemaName: item.schemaName, + connectable: true }); } }); @@ -117,24 +110,42 @@ export default memo(function WorkspaceRight(props) { setConsoleList(newWindowList); - // if (!flag && activeConsoleId) { - // historyService.getWindowTab({ id: consoleId }).then((res: any) => { - // if (res.connectable) { - // newWindowList.push({ - // id: res.id, - // ddl: res.ddl, - // name: res.name, - // status: res.status, - // databaseType: res.type, - // databaseName: res.databaseName, - // dataSourceName: res.dataSourceName, - // dataSourceId: res.dataSourceId, - // schemaName: res.schemaName, - // }); - // setActiveConsoleId(res.id); - // setConsoleList(newWindowList); + // if (!flag) { + // if (activeConsoleId) { + // historyService.getWindowTab({ id: activeConsoleId }).then((res: any) => { + // if (res.connectable) { + // newWindowList.push({ + // id: res.id, + // ddl: res.ddl, + // name: res.name, + // status: res.status, + // type: res.type, + // databaseName: res.databaseName, + // dataSourceName: res.dataSourceName, + // dataSourceId: res.dataSourceId, + // schemaName: res.schemaName, + // connectable: true, + // }); + // setActiveConsoleId(res.id); + // setConsoleList(newWindowList); + // } + // }); + // } else { + // let p = { + // name: 'default name', + // ddl: 'string', + // dataSourceId: currentWorkspaceData.dataSourceId, + // databaseName: currentWorkspaceData.databaseName, + // type: currentWorkspaceData.databaseType, + // status: ConsoleStatus.DRAFT, + // connectable: true, + // tabOpened: ConsoleOpenedStatus.IS_OPEN // } - // }); + // historyService.saveConsole(p).then(res => { + // setActiveConsoleId(res); + // // getConsoleList(); + // }) + // } // } else { // setConsoleList(newWindowList); // } @@ -173,7 +184,7 @@ export default memo(function WorkspaceRight(props) { let p: any = { id: targetKey, - tabOpened: 'n', + ConsoleOpenedStatus: 'n', }; const window = consoleList?.find((t) => t.id === +targetKey); @@ -211,36 +222,21 @@ export default memo(function WorkspaceRight(props) { />
{consoleList?.map((t, index) => { - return ( -
- -
- { - console.log('onExecuteSQL', result); - setResultData(result); - }} - /> -
-
- - - -
-
-
- ); + return
+ +
})}
diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less new file mode 100644 index 000000000..068c2b198 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less @@ -0,0 +1,21 @@ +@import '../../../../../styles/var.less'; + +.box { + height: 100%; +} + +.boxRightCenter { + height: 100%; +} + +.boxRightConsole { + height: 50vh; + overflow: hidden; +} + +.boxRightResult { + border-top: 1px solid var(--color-border); + padding: 10px; + height: 0px; + flex: 1; +} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx new file mode 100644 index 000000000..be83d06cc --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -0,0 +1,72 @@ +import React, { memo, useRef,useState, useEffect } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import DraggableContainer from '@/components/DraggableContainer'; +import Console from '@/components/Console'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import SearchResult from '@/components/SearchResult'; +import {DatabaseTypeCode} from '@/constants/common'; +import { IManageResultData } from '@/typings/database'; +import { useReducerContext } from '@/pages/main/workspace'; + +interface IProps { + className?: string; + data: { + databaseName: string; + dataSourceId: number; + type: DatabaseTypeCode; + schemaName: string; + consoleId: number; + consoleName: string; + initDDL: string; + }; +} + +export default memo(function WorkspaceRightItem(props) { + const { className, data } = props; + const draggableRef = useRef(); + const [consoleValue, setConsoleValue] = useState(data.initDDL || ''); + const [resultData, setResultData] = useState([]); + const { state, dispatch } = useReducerContext(); + const { dblclickTreeNodeData, currentWorkspaceData } = state; + + + useEffect(() => { + if(!dblclickTreeNodeData){ + return + } + const { extraParams } = dblclickTreeNodeData; + const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; + const ddl = `SELECT * FROM ${tableName};`; + + if (data.databaseName === databaseName && data.dataSourceId === dataSourceId) { + setConsoleValue(`${consoleValue}\n${ddl}`) + } + }, [dblclickTreeNodeData]); + + return
+ +
+ { + setConsoleValue(value) + } + } + onExecuteSQL={(result) => { + setResultData(result); + }} + /> +
+
+ + + +
+
+
+}) diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index a8606429f..eb9359bbd 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -1,12 +1,12 @@ import createRequest from "./base"; // import { IPageResponse,IPageParams,IHistoryRecord, IWindowTab, ISavedConsole } from '@/types'; -// import { DatabaseTypeCode, ConsoleStatus, TabOpened } from '@/utils/constants' -import {ISaveConsole,IConsole,IPageResponse,IPageParams} from '@/typings/common'; +import { ConsoleOpenedStatus, DatabaseTypeCode } from '@/constants/common' +import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings/common'; -export interface IGetHistoryListParams extends IPageParams { +export interface IGetSavedListParams extends IPageParams { dataSourceId?: string; databaseName?: string; - tabOpened?: TabOpened; + ConsoleOpenedStatus?: ConsoleOpenedStatus; } export interface ISaveBasicInfo { name: string; @@ -15,10 +15,6 @@ export interface ISaveBasicInfo { dataSourceId: number; databaseName: string; } -export interface ISaveConsole extends ISaveBasicInfo { - status: ConsoleStatus; - tabOpened: TabOpened; -} export interface IUpdateWindowParams { id: number; @@ -28,25 +24,25 @@ export interface IUpdateWindowParams { databaseName: string; } -const saveWindowTab = createRequest('/api/operation/saved/create', { method: 'post' }); +const saveConsole = createRequest('/api/operation/saved/create', { method: 'post' }); -const getWindowTab = createRequest<{id:string}, number>('/api/operation/saved/:id',{method: 'get'}); +const getWindowTab = createRequest<{ id: number }, number>('/api/operation/saved/:id', { method: 'get' }); -const updateWindowTab = createRequest('/api/operation/saved/update',{method: 'post'}); +const updateWindowTab = createRequest('/api/operation/saved/update', { method: 'post' }); -const getSaveList = createRequest>('/api/operation/saved/list',{}); +const getSaveList = createRequest>('/api/operation/saved/list', {}); -const deleteWindowTab = createRequest<{id: number}, string>('/api/operation/saved/:id',{method: 'delete'}); +const deleteWindowTab = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); -const createHistory = createRequest('/api/operation/log/create',{method: 'post'}); +const createHistory = createRequest('/api/operation/log/create', { method: 'post' }); -const getHistoryList = createRequest>('/api/operation/log/list',{}); +const getHistoryList = createRequest>('/api/operation/log/list', {}); export default { getSaveList, updateWindowTab, getHistoryList, - saveWindowTab, + saveConsole, deleteWindowTab, createHistory, getWindowTab diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 7332ee67a..a373e6579 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -1,4 +1,5 @@ import { DatabaseTypeCode } from '@/constants/database'; +import { ConsoleOpenedStatus, ConsoleStatus } from '@/constants/common'; export interface IPageResponse { data: T[]; pageNo: number; @@ -14,7 +15,7 @@ export interface IPageParams { pageSize: number; } -export interface ISaveConsole { +export interface IConsole { id: number; name: string; ddl: string; @@ -25,16 +26,8 @@ export interface ISaveConsole { type: DatabaseTypeCode; status: string; connectable: boolean; + tabOpened?: ConsoleOpenedStatus; } -export interface IConsole { - id: number; - name: string; - ddl: string; - dataSourceId: number; - databaseName: string; - dataSourceName: string; - schemaName: string; - databaseType: DatabaseTypeCode; - status: string; -} \ No newline at end of file +export type ICreateConsole = Omit + diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index afee15eb9..abbe72c45 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -13,9 +13,11 @@ export interface IConnectionDetails { user: string; password: string; type: DatabaseTypeCode; - tabOpened: 'y' | 'n'; + ConsoleOpenedStatus: 'y' | 'n'; EnvType: ConnectionEnv; extendInfo: IConnectionExtendInfoItem[]; ssh: any; [key: string]: any; } + +export type ICreateConnectionDetails = Omit diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index e69de29bb..6714b8d94 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -0,0 +1,7 @@ +export * as tree from './tree'; +export * as common from './common'; +export * as database from './database'; +export * as dashboard from './dashboard'; +export * as connection from './connection'; +export * as theme from './theme'; +export * as main from './main'; \ No newline at end of file From 76ed736ef7c7ae959727bc8ab6c5a96ad9ffd360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sun, 25 Jun 2023 16:40:25 +0800 Subject: [PATCH 0074/1069] =?UTF-8?q?umi=20=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.ts | 4 +- chat2db-client/package.json | 3 +- .../src/components/Iconfont/index.tsx | 48 +++++++++---------- chat2db-client/src/layouts/index.tsx | 1 + 4 files changed, 29 insertions(+), 27 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index d56eca385..dd0b3016e 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -45,6 +45,8 @@ export default defineConfig({ headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], define: { - 'process.env.UMI_ENV': process.env.UMI_ENV, + 'process.env': { + 'UMI_ENV': process.env.UMI_ENV + } } }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index d953df0b8..e518152de 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -69,8 +69,7 @@ "productName": "Chat2DB", "asar": false, "files": [ - "dist/**/*", - "!node_modules", + "dist/", "static/", "src/main", "package.json" diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 0447f1a7d..04d1a0146 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -3,31 +3,31 @@ import classnames from 'classnames'; // import desktopStyle from './desktop.less'; import styles from './index.less'; -// let container = '' -// container = ` -// @font-face { -// font-family: 'iconfont'; /* Project id 3633546 */ -// src: url('../../assets/font/iconfont.woff2') format('woff2'), -// url('../../assets/font/iconfont.woff') format('woff'), -// url('../../assets/font/iconfont.ttf') format('truetype'); -// } -// ` +let container = ''; -// if (process.env.UMI_ENV === 'desktop') { -// } else { -// container = ` -// @font-face { -// font-family: 'iconfont'; /* Project id 3633546 */ -// src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), -// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), -// url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); -// } -// ` -// } -// let style = document.createElement("style"); -// style.type = "text/css"; -// document.head.appendChild(style); -// style.appendChild(document.createTextNode(container)); +if (process.env.UMI_ENV === 'desktop') { + container = ` + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), + url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); + } + ` +} else { + container = ` + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); + } + ` +} +let style = document.createElement("style"); +style.type = "text/css"; +document.head.appendChild(style); +style.appendChild(document.createTextNode(container)); export default class Iconfont extends PureComponent<{ code: string; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index f6b2d783a..0755bbf04 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -33,6 +33,7 @@ declare global { const __ENV: string; } +window._ENV = process.env.UMI_ENV! || 'local'; window._Lang = getLang(); const { getDesignToken, useToken } = theme; From 9feed43cc017d897442f2003b050af02fad21930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sun, 25 Jun 2023 17:47:42 +0800 Subject: [PATCH 0075/1069] fix:iconfont --- chat2db-client/.umirc.desktop.ts | 9 ++----- chat2db-client/.umirc.prod.ts | 7 ------ chat2db-client/.umirc.ts | 6 +---- chat2db-client/package.json | 2 +- .../src/components/Iconfont/index.less | 2 +- .../src/components/Iconfont/index.tsx | 25 ++++++------------- 6 files changed, 13 insertions(+), 38 deletions(-) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index 53c37d1f2..cf6402c48 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -4,18 +4,13 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const UMI_PublicPath = process.env.UMI_PublicPath || './'; const chainWebpack = (config: any, { webpack }: any) => { + new webpack.DefinePlugin({ + }); config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 77b1dfead..6185a8548 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -9,13 +9,6 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index dd0b3016e..780c6cc78 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -44,9 +44,5 @@ export default defineConfig({ }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], - define: { - 'process.env': { - 'UMI_ENV': process.env.UMI_ENV - } - } + }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index e518152de..ccc8e5c6a 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -100,4 +100,4 @@ "icon": "src/assets/logo/logo.png" } } -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 0d539e8d1..5572157f7 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -14,4 +14,4 @@ -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale; -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 04d1a0146..42fdaa3c9 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -3,19 +3,9 @@ import classnames from 'classnames'; // import desktopStyle from './desktop.less'; import styles from './index.less'; -let container = ''; - -if (process.env.UMI_ENV === 'desktop') { - container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), - url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); - } - ` -} else { - container = ` +// 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font +if (__ENV === 'local') { + let container = ` @font-face { font-family: 'iconfont'; /* Project id 3633546 */ src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), @@ -23,11 +13,12 @@ if (process.env.UMI_ENV === 'desktop') { url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); } ` + let style = document.createElement("style"); + style.type = "text/css"; + document.head.appendChild(style); + style.appendChild(document.createTextNode(container)); } -let style = document.createElement("style"); -style.type = "text/css"; -document.head.appendChild(style); -style.appendChild(document.createTextNode(container)); + export default class Iconfont extends PureComponent<{ code: string; From 81c8a7f45e3309b1baa830f7cdb8e3901b8a25f4 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 25 Jun 2023 22:47:05 +0800 Subject: [PATCH 0076/1069] =?UTF-8?q?style:=20Connections=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/components/Iconfont/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 42fdaa3c9..1277ea3fc 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -6,12 +6,12 @@ import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font if (__ENV === 'local') { let container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff2?t=1687102726036') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.woff?t=1687102726036') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_bobs6jadiya.ttf?t=1687102726036') format('truetype'); - } + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_0ip5jtwa6g6.woff2?t=1687700061581') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_0ip5jtwa6g6.woff?t=1687700061581') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_0ip5jtwa6g6.ttf?t=1687700061581') format('truetype'); + } ` let style = document.createElement("style"); style.type = "text/css"; From 745daa8e6e4b4b290d5109e11c8bb097cb1ce4c4 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 24 Jun 2023 19:38:54 +0800 Subject: [PATCH 0077/1069] fix: icon bug --- chat2db-client/.umirc.desktop.ts | 9 +++++++-- chat2db-client/.umirc.prod.ts | 7 +++++++ chat2db-client/.umirc.ts | 4 +++- chat2db-client/package.json | 2 +- 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index cf6402c48..53c37d1f2 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -4,13 +4,18 @@ const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const UMI_PublicPath = process.env.UMI_PublicPath || './'; const chainWebpack = (config: any, { webpack }: any) => { - new webpack.DefinePlugin({ - }); config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { languages: ['mysql', 'pgsql', 'sql'], }, ]); + + config.plugin('define').use(require('webpack').DefinePlugin, [ + { + __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), + __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), + }, + ]); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 6185a8548..77b1dfead 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -9,6 +9,13 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); + + config.plugin('define').use(require('webpack').DefinePlugin, [ + { + __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), + __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), + }, + ]); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 780c6cc78..d56eca385 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -44,5 +44,7 @@ export default defineConfig({ }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], - + define: { + 'process.env.UMI_ENV': process.env.UMI_ENV, + } }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index ccc8e5c6a..e518152de 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -100,4 +100,4 @@ "icon": "src/assets/logo/logo.png" } } -} +} \ No newline at end of file From 36925c2d4d6b3db2b55fef8554f7f77120bcfa1e Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 24 Jun 2023 19:41:59 +0800 Subject: [PATCH 0078/1069] fix: delete node_modules from package --- chat2db-client/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index e518152de..d953df0b8 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -69,7 +69,8 @@ "productName": "Chat2DB", "asar": false, "files": [ - "dist/", + "dist/**/*", + "!node_modules", "static/", "src/main", "package.json" From fe9fc2ddbd1b4154a4f78aab9e5f8fecde8fd4e6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 24 Jun 2023 20:34:29 +0800 Subject: [PATCH 0079/1069] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/layouts/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 0755bbf04..27aca96a3 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -33,6 +33,8 @@ declare global { const __ENV: string; } +console.log(process.env.UMI_ENV); + window._ENV = process.env.UMI_ENV! || 'local'; window._Lang = getLang(); From c157aaeaceacbdc7e842e66d826881abfbe409b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Mon, 26 Jun 2023 09:50:01 +0800 Subject: [PATCH 0080/1069] =?UTF-8?q?fix:=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.ts | 3 --- chat2db-client/package.json | 2 +- chat2db-client/src/components/Console/index.tsx | 5 +++-- chat2db-client/src/hooks/useEventSource.ts | 4 ++-- chat2db-client/src/utils/common.ts | 9 +++++++++ 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index d56eca385..8a941676e 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -44,7 +44,4 @@ export default defineConfig({ }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], favicons: ['logo.ico'], - define: { - 'process.env.UMI_ENV': process.env.UMI_ENV, - } }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index d953df0b8..40a3553aa 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,7 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "umi dev" + "start:web": "cross-env UMI_ENV=local umi dev" }, "dependencies": { "ahooks": "^3.7.7", diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 9f7d0f59e..577c524db 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -1,4 +1,4 @@ -import { formatParams, uuid } from '@/utils/common'; +import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; import { Button, Spin } from 'antd'; import React, { ForwardedRef, useEffect, useMemo, useRef, useState } from 'react'; @@ -9,6 +9,7 @@ import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import MonacoEditor from 'react-monaco-editor'; import { useReducerContext } from '@/pages/main/workspace/index'; +import { v4 as uuidv4 } from 'uuid'; import styles from './index.less'; import Loading from '../Loading/Loading'; @@ -50,7 +51,7 @@ interface IProps { function Console(props: IProps) { const { hasAiChat = true, value, executeParams, onChangeValue } = props; - const uid = useMemo(() => uuid(), []); + const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); const [context, setContext] = useState(); diff --git a/chat2db-client/src/hooks/useEventSource.ts b/chat2db-client/src/hooks/useEventSource.ts index 31f7af676..2bba210d2 100644 --- a/chat2db-client/src/hooks/useEventSource.ts +++ b/chat2db-client/src/hooks/useEventSource.ts @@ -1,9 +1,9 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { uuid } from '@/utils/common'; import { EventSourcePolyfill } from 'event-source-polyfill'; +import { v4 as uuidv4 } from 'uuid'; function useEventSource({ url }) { - const uid = useMemo(() => uuid(), []); + const uid = useMemo(() => uuidv4(), []); const [messages, setMessage] = useState(''); useEffect(() => { diff --git a/chat2db-client/src/utils/common.ts b/chat2db-client/src/utils/common.ts index e69de29bb..a172c524f 100644 --- a/chat2db-client/src/utils/common.ts +++ b/chat2db-client/src/utils/common.ts @@ -0,0 +1,9 @@ +export function formatParams(obj: { [key: string]: any }) { + let params = ''; + for (let key in obj) { + if (obj[key]) { + params += `${key}=${obj[key]}&`; + } + } + return params; +} \ No newline at end of file From 36dfe056c6ad63cac221c43385b50a1e106efacb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Mon, 26 Jun 2023 12:47:49 +0800 Subject: [PATCH 0081/1069] =?UTF-8?q?fix:=E7=8E=AF=E5=A2=83=E5=8F=98?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/index.tsx | 2 +- chat2db-client/src/components/Iconfont/index.tsx | 13 +++++++------ chat2db-client/src/layouts/index.tsx | 4 ---- .../src/pages/main/connections/index.less | 1 - chat2db-client/src/pages/main/index.tsx | 4 ++-- chat2db-client/src/service/base.ts | 2 +- 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 35dd4a81f..938bfe81f 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -459,7 +459,7 @@ function AboutUs() {
{APP_NAME}
- {i18n('setting.text.currentEnv')}:{window._ENV} + {i18n('setting.text.currentEnv')}:{window.__ENV}
{i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 1277ea3fc..6ad67dbfc 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -6,12 +6,13 @@ import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font if (__ENV === 'local') { let container = ` - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_0ip5jtwa6g6.woff2?t=1687700061581') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_0ip5jtwa6g6.woff?t=1687700061581') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_0ip5jtwa6g6.ttf?t=1687700061581') format('truetype'); - } + /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.woff2?t=1687748230475') format('woff2'), + url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.woff?t=1687748230475') format('woff'), + url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.ttf?t=1687748230475') format('truetype'); + } ` let style = document.createElement("style"); style.type = "text/css"; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 27aca96a3..041fa6a37 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -22,7 +22,6 @@ import { declare global { interface Window { _Lang: string; - _ENV: string; _APP_PORT: string; _BUILD_TIME: string; _BaseURL: string; @@ -33,9 +32,6 @@ declare global { const __ENV: string; } -console.log(process.env.UMI_ENV); - -window._ENV = process.env.UMI_ENV! || 'local'; window._Lang = getLang(); const { getDesignToken, useToken } = theme; diff --git a/chat2db-client/src/pages/main/connections/index.less b/chat2db-client/src/pages/main/connections/index.less index e3fefb076..a15f264be 100644 --- a/chat2db-client/src/pages/main/connections/index.less +++ b/chat2db-client/src/pages/main/connections/index.less @@ -119,7 +119,6 @@ .databaseItemRight { display: none; - i { font-size: 16px; } diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 42c7630c6..4762fef59 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -24,8 +24,8 @@ const navConfig: INavItem[] = [ }, { key: 'dashboard', - icon: '\ue616', - iconFontSize: 16, + icon: '\ue629', + iconFontSize: 24, component: , }, { diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 14f384ccb..40aa52cb0 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -89,7 +89,7 @@ request.interceptors.request.use((url, options) => { request.interceptors.response.use(async (response, options) => { const res = await response.clone().json(); - if (window._ENV === 'desktop') { + if (__ENV === 'desktop') { const DBHUB = response.headers.get('DBHUB') || ''; if (DBHUB) { localStorage.setItem('DBHUB', DBHUB); From b54c5544b4ea0986ef6121c84e5c035e16ebb3b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Mon, 26 Jun 2023 18:26:49 +0800 Subject: [PATCH 0082/1069] style:connections --- .../src/pages/main/connections/index.less | 21 ++++++++-- .../src/pages/main/connections/index.tsx | 10 ++--- chat2db-client/src/styles/global.less | 38 +++++++++---------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/chat2db-client/src/pages/main/connections/index.less b/chat2db-client/src/pages/main/connections/index.less index a15f264be..c5a88ff67 100644 --- a/chat2db-client/src/pages/main/connections/index.less +++ b/chat2db-client/src/pages/main/connections/index.less @@ -44,17 +44,32 @@ justify-content: space-between; align-items: center; cursor: pointer; - padding: 10px; + padding: 8px; + margin-bottom: 4px; height: 20px; border-radius: 8px; + .menuItemsTitle { + flex: 1; + width: 0; + .f-single-line(); + } + + .moreButton{ + flex-shrink: 0; + display: none; + transform: rotate(90deg); + } + &:hover { background-color: var(--color-hover-bg); border: var(--border-radius); + .moreButton{ + display: block; + } } } - .menuItemsTitle {} .menuItemActive { color: var(--color-primary); @@ -156,7 +171,7 @@ .databaseItemSpacer { flex-grow: 1; width: 210px; - margin: 10px 20px; + margin: 0px 20px; padding: 0px 16px; box-sizing: border-box; } diff --git a/chat2db-client/src/pages/main/connections/index.tsx b/chat2db-client/src/pages/main/connections/index.tsx index 8da543de1..23ef53d20 100644 --- a/chat2db-client/src/pages/main/connections/index.tsx +++ b/chat2db-client/src/pages/main/connections/index.tsx @@ -34,8 +34,6 @@ function Connections(props: IProps) { // const [connectionList, setConnectionList] = useState(); const [curConnection, setCurConnection] = useState>({}); - - useEffect(() => { getConnectionList(); }, []); @@ -46,7 +44,6 @@ function Connections(props: IProps) { pageSize: 999, }; let res = await connectionService.getList(p) - // setConnectionList(res.data); props.dispatch({ type: 'connection/setConnectionList', @@ -94,6 +91,7 @@ function Connections(props: IProps) { {label}
- +
+ +
); @@ -180,7 +180,7 @@ function Connections(props: IProps) { ); })} { - Array.from({ length: 20 }).map(t => { + Array.from({ length: 5 }).map(t => { return
}) } diff --git a/chat2db-client/src/styles/global.less b/chat2db-client/src/styles/global.less index 890db61f4..085fff708 100644 --- a/chat2db-client/src/styles/global.less +++ b/chat2db-client/src/styles/global.less @@ -21,29 +21,29 @@ input:-webkit-autofill { caret-color: var(--color-text); // 光标的颜色 } -// ::-webkit-scrollbar { -// width: 6px; -// height: 6px; -// } +::-webkit-scrollbar { + width: 6px; + height: 6px; +} -// ::-webkit-scrollbar-thumb { -// background-color: var(--color-bg); -// border-radius: 3px; -// } +::-webkit-scrollbar-thumb { + background-color: var(--color-fill); + border-radius: 3px; +} -// ::-webkit-scrollbar-button { -// width: 0; -// height: 0; -// background: transparent; -// } +::-webkit-scrollbar-button { + width: 0; + height: 0; + background: transparent; +} -// ::-webkit-scrollbar-track { -// background: transparent; -// } +::-webkit-scrollbar-track { + background: transparent; +} -// ::-webkit-scrollbar-corner { -// background: transparent; -// } +::-webkit-scrollbar-corner { + background: transparent; +} html, body, From c305da3303306905e2ae1d0cb05724064e5896ef Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 26 Jun 2023 20:48:56 +0800 Subject: [PATCH 0083/1069] =?UTF-8?q?=E5=90=88=E5=B9=B6typesing=20constant?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/index.tsx | 4 +- .../components/Console/MonacoEditor/index.tsx | 3 +- .../src/components/Console/index.tsx | 6 +- .../CreateConnection/config/dataSource.ts | 96 +++++++++---------- .../CreateConnection/config/types.ts | 7 +- .../src/components/CreateConnection/index.tsx | 4 +- .../src/components/SearchResult/index.tsx | 8 +- chat2db-client/src/constants/common.ts | 17 ---- chat2db-client/src/constants/database.ts | 18 +--- chat2db-client/src/constants/index.ts | 12 +-- chat2db-client/src/hooks/useTheme.ts | 4 +- chat2db-client/src/i18n/index.tsx | 2 +- chat2db-client/src/i18n/zh-cn/index.ts | 2 +- chat2db-client/src/layouts/index.tsx | 2 +- chat2db-client/src/models/connection.ts | 4 +- .../src/pages/main/connections/index.tsx | 5 +- .../pages/main/dashboard/chart-item/index.tsx | 2 +- .../src/pages/main/dashboard/index.tsx | 2 +- .../main/workspace/components/Tree/index.tsx | 5 +- .../workspace/components/Tree/treeConfig.tsx | 4 +- .../components/WorkspaceLeft/index.tsx | 8 +- .../components/WorkspaceRight/index.tsx | 9 +- .../components/WorkspaceRightItem/index.tsx | 12 +-- .../src/pages/main/workspace/context.ts | 5 +- chat2db-client/src/service/connection.ts | 3 +- chat2db-client/src/service/dashboard.ts | 3 +- chat2db-client/src/service/history.ts | 4 +- chat2db-client/src/service/sql.ts | 38 ++++---- chat2db-client/src/service/user.ts | 2 +- chat2db-client/src/theme/dark.ts | 2 +- chat2db-client/src/theme/index.ts | 4 +- chat2db-client/src/theme/light.ts | 2 +- chat2db-client/src/typings/common.ts | 3 +- chat2db-client/src/typings/connection.ts | 3 +- chat2db-client/src/typings/database.ts | 3 +- chat2db-client/src/typings/index.ts | 14 +-- chat2db-client/src/typings/theme.ts | 2 +- chat2db-client/src/typings/tree.ts | 7 +- chat2db-client/src/utils/index.ts | 4 +- chat2db-client/src/utils/localStorage.ts | 11 +-- 40 files changed, 150 insertions(+), 196 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 938bfe81f..6941a095f 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -10,10 +10,8 @@ import themeLightImg from '@/assets/img/theme-light.png'; import themeAutoImg from '@/assets/img/theme-auto.png'; import { getOsTheme } from '@/utils'; import i18n, { currentLang } from '@/i18n'; -import { ThemeType } from '@/constants/common'; -import { LangType } from '@/constants/common'; +import { ThemeType, LangType, APP_NAME, GITHUB_URL } from '@/constants'; import { useTheme } from '@/hooks'; -import { APP_NAME, GITHUB_URL } from '@/constants/appConfig'; import { setLang as setLangLocalStorage } from '@/utils/localStorage' const { Option } = Select; diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 75cc94fd0..71fbc478a 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -4,9 +4,8 @@ import { useTheme } from '@/hooks'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; const { keywords: SQLKeys } = language; -import { editorDefaultOptions } from '@/constants/monacoEditor'; +import { editorDefaultOptions, ThemeType } from '@/constants'; import styles from './index.less'; -import { ThemeType } from '@/constants/common'; import { monacoSqlAutocomplete } from './syntax-parser/plugin/monaco-plugin'; export type IEditorIns = monaco.editor.IStandaloneCodeEditor; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 577c524db..710810225 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -13,7 +13,7 @@ import { v4 as uuidv4 } from 'uuid'; import styles from './index.less'; import Loading from '../Loading/Loading'; -import { DatabaseTypeCode } from '@/constants/database'; +import { DatabaseTypeCode } from '@/constants'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -63,9 +63,9 @@ function Console(props: IProps) { setContext(value); }, [value]); - useEffect(()=>{ + useEffect(() => { onChangeValue?.(value); - },[context]) + }, [context]) const onPressChatInput = (value: string) => { const params = formatParams({ diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index 826b03ed5..4765073ba 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -1,4 +1,4 @@ -import { DatabaseTypeCode } from '@/constants/database'; +import { DatabaseTypeCode } from '@/constants'; import { IConnectionConfig } from './types'; import { InputType, AuthenticationType, SSHAuthenticationType, OperationColumn } from './enum'; @@ -170,7 +170,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -227,7 +227,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -262,7 +262,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -271,7 +271,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], label: 'User&Password', @@ -285,7 +285,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -363,7 +363,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -413,7 +413,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -440,7 +440,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -466,7 +466,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '30%', - + } }, { @@ -486,7 +486,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -495,7 +495,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], label: 'User&Password', @@ -509,7 +509,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -553,7 +553,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -579,7 +579,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -629,7 +629,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -664,7 +664,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -673,7 +673,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], @@ -688,7 +688,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -741,7 +741,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -767,7 +767,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -835,7 +835,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -879,7 +879,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -888,7 +888,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], @@ -903,7 +903,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -956,7 +956,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -982,7 +982,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1073,7 +1073,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1099,7 +1099,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1149,7 +1149,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -1184,7 +1184,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -1193,7 +1193,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], @@ -1208,7 +1208,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -1261,7 +1261,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1287,7 +1287,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1337,7 +1337,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -1372,7 +1372,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -1381,7 +1381,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], @@ -1396,7 +1396,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -1450,7 +1450,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1462,7 +1462,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1488,7 +1488,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1537,7 +1537,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, styles: { width: '70%', - + } }, { @@ -1572,7 +1572,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'User', name: 'user', required: true, - + }, { defaultValue: '', @@ -1581,7 +1581,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Password', name: 'password', required: true, - + }, ], label: 'User&Password', @@ -1594,7 +1594,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], styles: { width: '50%', - + } }, { @@ -1647,7 +1647,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { @@ -1673,7 +1673,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - + } }, { diff --git a/chat2db-client/src/components/CreateConnection/config/types.ts b/chat2db-client/src/components/CreateConnection/config/types.ts index 6c90efd3f..e64dfb275 100644 --- a/chat2db-client/src/components/CreateConnection/config/types.ts +++ b/chat2db-client/src/components/CreateConnection/config/types.ts @@ -1,6 +1,5 @@ import { InputType, AuthenticationType, SSHAuthenticationType } from './enum'; -import { DatabaseTypeCode } from '@/constants/database'; -import { OperationColumn } from '@/components/Tree/treeConfig'; +import { DatabaseTypeCode, OperationColumn } from '@/constants'; export type ISelect = { value?: AuthenticationType | SSHAuthenticationType | string; @@ -18,7 +17,7 @@ export interface IFormItem { selected?: any; selects?: ISelect[]; labelTextAlign?: 'right'; - styles?:{ + styles?: { width?: string; // 表单占用的长度 推荐百分比 默认值为 100% labelWidthEN?: string; // 英文环境下表单label的长度 推荐px 默认值为 70px labelWidthCN?: string; // 中文环境下表单label的长度 推荐px 默认值为 100px @@ -43,7 +42,7 @@ export type IConnectionConfig = { value: any; }[], // TODO: 先取form里的配置,在取form.item的配置, 最后取默认值,目前没有取全局的 - styles?:{ + styles?: { width?: string; // 表单占用的长度 推荐百分比 默认值为 100% labelWidthEN?: string; // 英文环境下表单label的长度 推荐px 默认值为 70px labelWidthCN?: string; // 中文环境下表单label的长度 推荐px 默认值为 100px diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 06b554a24..3c30dd778 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -5,10 +5,10 @@ import classnames from 'classnames'; import connectionService from '@/service/connection'; -import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants/database'; +import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants'; import { dataSourceFormConfigs } from './config/dataSource'; import { IConnectionConfig, IFormItem, ISelect } from './config/types'; -import { IConnectionDetails } from '@/typings/connection'; +import { IConnectionDetails } from '@/typings'; import { InputType } from './config/enum'; import { deepClone } from '@/utils'; import { Select, Form, Input, message, Table, Button, Collapse } from 'antd'; diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 49affc9fa..a40225dd1 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -6,9 +6,9 @@ import StateIndicator from '@/components/StateIndicator'; import LoadingContent from '@/components/Loading/LoadingContent'; import MonacoEditor from '@/components/Console/MonacoEditor'; import { Button, DatePicker, Input, Table, Modal, message } from 'antd'; -import { StatusType, TableDataType } from '@/constants/table'; +import { StatusType, TableDataType } from '@/constants'; import { formatDate } from '@/utils/date'; -import { IManageResultData, ITableHeaderItem } from '@/typings/database'; +import { IManageResultData, ITableHeaderItem } from '@/typings'; import styles from './index.less'; interface IProps { @@ -172,8 +172,8 @@ export function TableBox(props: ITableProps) { {dataList !== null ? (
) : ( - - )} + + )} ([]); useEffect(() => { - if(currentWorkspaceData.databaseName){ + if (currentWorkspaceData.databaseName) { getInitialData(); } }, [currentWorkspaceData]); 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 bc11687a2..979b5dbd4 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -3,9 +3,8 @@ import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import Console from '@/components/Console'; -import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment } from '@/constants/common'; -import { DatabaseTypeCode } from '@/constants/database'; -import { IConsole } from '@/typings/common'; +import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment, DatabaseTypeCode } from '@/constants'; +import { IConsole } from '@/typings'; import historyService from '@/service/history'; import { Button, Tabs } from 'antd'; import { useReducerContext } from '@/pages/main/workspace'; @@ -30,7 +29,7 @@ export default memo(function WorkspaceRight(props) { }, [currentWorkspaceData]); useEffect(() => { - if(!dblclickTreeNodeData){ + if (!dblclickTreeNodeData) { return } const { extraParams } = dblclickTreeNodeData; @@ -223,7 +222,7 @@ export default memo(function WorkspaceRight(props) { {consoleList?.map((t, index) => { return
- (function WorkspaceRightItem(props) { useEffect(() => { - if(!dblclickTreeNodeData){ + if (!dblclickTreeNodeData) { return } const { extraParams } = dblclickTreeNodeData; @@ -48,12 +48,12 @@ export default memo(function WorkspaceRightItem(props) {
{ + (value: string) => { setConsoleValue(value) } } diff --git a/chat2db-client/src/pages/main/workspace/context.ts b/chat2db-client/src/pages/main/workspace/context.ts index 3c6fd77b5..b572fab57 100644 --- a/chat2db-client/src/pages/main/workspace/context.ts +++ b/chat2db-client/src/pages/main/workspace/context.ts @@ -1,7 +1,6 @@ import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/utils/localStorage'; -import { ITreeNode } from '@/typings/tree'; -import { TreeNodeType } from '@/constants/tree'; -import { DatabaseTypeCode } from '@/constants/database'; +import { ITreeNode } from '@/typings'; +import { TreeNodeType, DatabaseTypeCode } from '@/constants'; export type ICurrentWorkspaceData = { dataSourceId: number; diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index daf9c18c2..8d69742f1 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,5 +1,4 @@ -import { IPageResponse } from '@/typings/common'; -import { IConnectionDetails } from '@/typings/connection'; +import { IPageResponse, IConnectionDetails } from '@/typings'; import createRequest from './base'; // import { IPageResponse, IConnectionDetails, IDB } from '@/types'; diff --git a/chat2db-client/src/service/dashboard.ts b/chat2db-client/src/service/dashboard.ts index 30ffa37f9..ace525556 100644 --- a/chat2db-client/src/service/dashboard.ts +++ b/chat2db-client/src/service/dashboard.ts @@ -1,5 +1,4 @@ -import { IPageResponse } from '@/typings/common'; -import { IChartItem, IDashboardItem } from '@/typings/dashboard'; +import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; import createRequest from './base'; /** 获取报表列表 */ diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index eb9359bbd..0f2cceb70 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -1,7 +1,7 @@ import createRequest from "./base"; // import { IPageResponse,IPageParams,IHistoryRecord, IWindowTab, ISavedConsole } from '@/types'; -import { ConsoleOpenedStatus, DatabaseTypeCode } from '@/constants/common' -import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings/common'; +import { ConsoleOpenedStatus, DatabaseTypeCode } from '@/constants' +import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings'; export interface IGetSavedListParams extends IPageParams { dataSourceId?: string; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index feaece77a..590f170d6 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,8 +1,8 @@ import createRequest from "./base"; import { IPageResponse, ITable, IPageParams } from '@/types'; -import { DatabaseTypeCode } from '@/constants/database'; +import { DatabaseTypeCode } from '@/constants'; -export interface IGetListParams extends IPageParams { +export interface IGetListParams extends IPageParams { dataSourceId: number; databaseName: string; schemaName?: string; @@ -21,32 +21,32 @@ export interface IExecuteSqlResponse { description: string; message: string; success: boolean; - headerList:any[]; + headerList: any[]; dataList: any[]; } export interface IConnectConsoleParams { - consoleId: number, + consoleId: number, dataSourceId: number, databaseName: string, } -const getList = createRequest>('/api/rdb/ddl/list',{}); +const getList = createRequest>('/api/rdb/ddl/list', {}); -const executeSql = createRequest('/api/rdb/dml/execute',{method: 'post'}); +const executeSql = createRequest('/api/rdb/dml/execute', { method: 'post' }); -const connectConsole = createRequest('/api/connection/console/connect',{method: 'get'}); +const connectConsole = createRequest('/api/connection/console/connect', { method: 'get' }); //表操作 export interface ITableParams { - tableName:string; - dataSourceId:number; + tableName: string; + dataSourceId: number; databaseName: string; schemaName?: string; } export interface IExecuteTableParams { sql: string; - consoleId: number; + consoleId: number; dataSourceId: number; databaseName: string; } @@ -72,16 +72,16 @@ export interface ISchemaResponse { name: string; } -const deleteTable = createRequest('/api/rdb/ddl/delete',{method: 'post'}); -const createTableExample = createRequest<{dbType:DatabaseTypeCode}, string>('/api/rdb/ddl/create/example',{method: 'get'}); -const updateTableExample = createRequest<{dbType:DatabaseTypeCode}, string>('/api/rdb/ddl/update/example',{method: 'get'}); -const exportCreateTableSql = createRequest('/api/rdb/ddl/export',{method: 'get'}); -const executeTable = createRequest('/api/rdb/ddl/execute',{method: 'post'}); +const deleteTable = createRequest('/api/rdb/ddl/delete', { method: 'post' }); +const createTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/create/example', { method: 'get' }); +const updateTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/update/example', { method: 'get' }); +const exportCreateTableSql = createRequest('/api/rdb/ddl/export', { method: 'get' }); +const executeTable = createRequest('/api/rdb/ddl/execute', { method: 'post' }); -const getColumnList = createRequest('/api/rdb/ddl/column_list',{method: 'get'}); -const getIndexList = createRequest('/api/rdb/ddl/index_list',{method: 'get'}); -const getKeyList = createRequest('/api/rdb/ddl/key_list',{method: 'get'}); -const getSchemaList = createRequest('/api/rdb/ddl/schema_list',{method: 'get'}); +const getColumnList = createRequest('/api/rdb/ddl/column_list', { method: 'get' }); +const getIndexList = createRequest('/api/rdb/ddl/index_list', { method: 'get' }); +const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get' }); +const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { method: 'get' }); export default { diff --git a/chat2db-client/src/service/user.ts b/chat2db-client/src/service/user.ts index a0695fd5d..46f7ae8f2 100644 --- a/chat2db-client/src/service/user.ts +++ b/chat2db-client/src/service/user.ts @@ -1,6 +1,6 @@ import createRequest from './base'; import { IPageResponse, IPageParams } from '@/types'; -import { IUser } from '@/typings/user'; +import { IUser } from '@/typings'; /** 用户登录接口 */ const userLogin = createRequest< diff --git a/chat2db-client/src/theme/dark.ts b/chat2db-client/src/theme/dark.ts index cd4d9f750..e3530aba7 100644 --- a/chat2db-client/src/theme/dark.ts +++ b/chat2db-client/src/theme/dark.ts @@ -1,5 +1,5 @@ import { theme } from 'antd'; -import { PrimaryColorType } from '@/constants/common'; +import { PrimaryColorType } from '@/constants'; import { commonToken } from './common'; type IAntdPrimaryColor = { diff --git a/chat2db-client/src/theme/index.ts b/chat2db-client/src/theme/index.ts index 7ed1f5c57..ef7eff1ea 100644 --- a/chat2db-client/src/theme/index.ts +++ b/chat2db-client/src/theme/index.ts @@ -1,7 +1,7 @@ import antdDarkTheme from './dark'; import antdLightTheme from './light'; -import { ThemeType, PrimaryColorType } from "@/constants/common"; -import { ITheme } from '@/typings/theme'; +import { ThemeType, PrimaryColorType } from "@/constants"; +import { ITheme } from '@/typings'; import lodash from 'lodash'; const antdThemeConfigs = { diff --git a/chat2db-client/src/theme/light.ts b/chat2db-client/src/theme/light.ts index eed24a32e..74e875588 100644 --- a/chat2db-client/src/theme/light.ts +++ b/chat2db-client/src/theme/light.ts @@ -1,5 +1,5 @@ import { theme } from 'antd'; -import { PrimaryColorType } from '@/constants/common'; +import { PrimaryColorType } from '@/constants'; import { commonToken } from './common'; type IAntdPrimaryColor = { diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index a373e6579..62a6c41a9 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -1,5 +1,4 @@ -import { DatabaseTypeCode } from '@/constants/database'; -import { ConsoleOpenedStatus, ConsoleStatus } from '@/constants/common'; +import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode } from '@/constants'; export interface IPageResponse { data: T[]; pageNo: number; diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index abbe72c45..97cf1202a 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -1,5 +1,4 @@ -import { DatabaseTypeCode } from '@/constants/database'; -import { ConnectionEnv } from '@/constants/environment'; +import { DatabaseTypeCode, ConnectionEnv } from '@/constants'; export interface IConnectionExtendInfoItem { key: string; diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 326bf2820..2e2543705 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -1,5 +1,4 @@ -import { DatabaseTypeCode } from '@/constants/database'; -import { TableDataType } from '@/constants/table'; +import { DatabaseTypeCode, TableDataType } from '@/constants'; export interface IDatabase { name: string; diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index 6714b8d94..0ad015b15 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -1,7 +1,7 @@ -export * as tree from './tree'; -export * as common from './common'; -export * as database from './database'; -export * as dashboard from './dashboard'; -export * as connection from './connection'; -export * as theme from './theme'; -export * as main from './main'; \ No newline at end of file +export * from './tree'; +export * from './common'; +export * from './database'; +export * from './dashboard'; +export * from './connection'; +export * from './theme'; +export * from './main'; \ No newline at end of file diff --git a/chat2db-client/src/typings/theme.ts b/chat2db-client/src/typings/theme.ts index cc931bc5a..a4cb58080 100644 --- a/chat2db-client/src/typings/theme.ts +++ b/chat2db-client/src/typings/theme.ts @@ -1,4 +1,4 @@ -import { ThemeType, PrimaryColorType } from '@/constants/common'; +import { ThemeType, PrimaryColorType } from '@/constants'; export interface ITheme { backgroundColor: Exclude; diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index 6c32d78a0..e594642b6 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -1,10 +1,9 @@ -import { TreeNodeType } from '@/constants/tree'; -import { DatabaseTypeCode } from '@/constants/database'; +import { TreeNodeType, DatabaseTypeCode } from '@/constants'; export interface IExtraParams { databaseType?: DatabaseTypeCode; dataSourceName?: string; - dataSourceId?: number; + dataSourceId?: number; databaseName?: string; schemaName?: string; tableName?: string; @@ -17,5 +16,5 @@ export interface ITreeNode { isLeaf?: boolean; children?: ITreeNode[]; columnType?: string; - extraParams?: IExtraParams; + extraParams?: IExtraParams; } \ No newline at end of file diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 76111236f..6a63f0ffb 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -1,5 +1,5 @@ -import { ThemeType } from '@/constants/common'; -import { ITreeNode } from '@/typings/tree'; +import { ThemeType } from '@/constants'; +import { ITreeNode } from '@/typings'; export function getOsTheme() { return window.matchMedia && diff --git a/chat2db-client/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts index 4d7525bf8..0856bec05 100644 --- a/chat2db-client/src/utils/localStorage.ts +++ b/chat2db-client/src/utils/localStorage.ts @@ -1,5 +1,4 @@ -import { ThemeType, PrimaryColorType } from '@/constants/common'; -import { LangType } from '@/constants/common'; +import { ThemeType, PrimaryColorType, LangType } from '@/constants'; import { ICurrentDatabase } from '@/pages/main/workspace/context'; export function getLang(): LangType { @@ -29,14 +28,14 @@ export function setPrimaryColor(primaryColor: PrimaryColorType) { return localStorage.setItem('primary-color', primaryColor); } -export function setCurrentWorkspaceDatabase(value:ICurrentDatabase) { +export function setCurrentWorkspaceDatabase(value: ICurrentDatabase) { return localStorage.setItem('current-workspace-database', JSON.stringify(value)); -} +} -export function getCurrentWorkspaceDatabase():ICurrentDatabase { +export function getCurrentWorkspaceDatabase(): ICurrentDatabase { const currentWorkspaceDatabase = localStorage.getItem('current-workspace-database'); if (currentWorkspaceDatabase) { - return JSON.parse(currentWorkspaceDatabase) + return JSON.parse(currentWorkspaceDatabase) } return {}; } From 2bcf7bbf91ea908f18d1f670126b0323f866b677 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 26 Jun 2023 20:52:17 +0800 Subject: [PATCH 0084/1069] =?UTF-8?q?=E5=90=88=E5=B9=B6typesing=20constant?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/constants/index.ts | 8 +++++--- chat2db-client/src/typings/index.ts | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/chat2db-client/src/constants/index.ts b/chat2db-client/src/constants/index.ts index eee4e5db5..4e87d59d5 100644 --- a/chat2db-client/src/constants/index.ts +++ b/chat2db-client/src/constants/index.ts @@ -1,7 +1,9 @@ -export * from './environment'; -export * from './database'; export * from './appConfig'; -export * from './theme'; export * from './common'; +export * from './database'; +export * from './environment'; +export * from './monacoEditor'; +export * from './table'; +export * from './theme'; export * from './tree'; diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index 0ad015b15..124717aea 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -1,7 +1,7 @@ -export * from './tree'; export * from './common'; -export * from './database'; -export * from './dashboard'; export * from './connection'; +export * from './dashboard'; +export * from './database'; +export * from './main'; export * from './theme'; -export * from './main'; \ No newline at end of file +export * from './tree'; \ No newline at end of file From 2559526d20075e892fb8771777daa46818dac71c Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Mon, 26 Jun 2023 21:21:35 +0800 Subject: [PATCH 0085/1069] Resolve conflicts. --- .vscode/settings.json | 8 +- chat2db-client/.umirc.desktop.ts | 9 +- chat2db-client/.umirc.prod.ts | 9 +- chat2db-client/.umirc.ts | 8 +- .../src/components/Iconfont/index.less | 2 +- .../src/components/Iconfont/index.tsx | 16 +-- chat2db-client/src/i18n/en-us/connection.ts | 1 - chat2db-client/src/i18n/zh-cn/connection.ts | 1 - chat2db-client/src/layouts/index.tsx | 36 +++---- chat2db-client/src/models/connection.ts | 81 +++++++++----- chat2db-client/src/models/database.ts | 43 ++++++++ .../{connections => connection}/index.less | 0 .../{connections => connection}/index.tsx | 102 +++++++++--------- chat2db-client/src/pages/main/index.tsx | 2 +- chat2db-client/src/service/sql.ts | 25 +++-- chat2db-client/src/theme/index.ts | 24 ++--- chat2db-client/typings.d.ts | 1 + 17 files changed, 214 insertions(+), 154 deletions(-) create mode 100644 chat2db-client/src/models/database.ts rename chat2db-client/src/pages/main/{connections => connection}/index.less (100%) rename chat2db-client/src/pages/main/{connections => connection}/index.tsx (69%) diff --git a/.vscode/settings.json b/.vscode/settings.json index 404270783..47ce6195c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,17 @@ { "cSpell.words": [ - "Dserver", - "Dspring", - "Sercurity", "ahooks", "antd", "asar", "cascader", + "Dserver", + "Dspring", "echarts", + "favicons", + "iconfont", "nsis", "pgsql", + "Sercurity", "sortablejs", "wireframe" ] diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index 53c37d1f2..aafa2beba 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -9,13 +9,6 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ @@ -24,5 +17,5 @@ export default defineConfig({ headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], define: { 'process.env.UMI_ENV': process.env.UMI_ENV, - } + }, }); diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 77b1dfead..a0d3b6d75 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -9,13 +9,6 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); - - config.plugin('define').use(require('webpack').DefinePlugin, [ - { - __BUILD_TIME__: JSON.stringify(formatDate(new Date(), 'yyyyMMddhhmmss')), - __APP_VERSION__: JSON.stringify(process.env.APP_VERSION || '0.0.0'), - }, - ]); }; export default defineConfig({ @@ -23,5 +16,5 @@ export default defineConfig({ chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, - } + }, }); diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 8a941676e..1219b1ad7 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -1,7 +1,7 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -console.log(process.env.UMI_ENV) +console.log(process.env.UMI_ENV); const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ @@ -37,11 +37,11 @@ export default defineConfig({ changeOrigin: true, }, }, + headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], + favicons: ['logo.ico'], define: { - __ENV: process.env.UMI_ENV , + __ENV: process.env.UMI_ENV, __BUILD_TIME__: formatDate(new Date(), 'yyyyMMddhhmmss'), __APP_VERSION__: process.env.APP_VERSION || '0.0.0', }, - headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], - favicons: ['logo.ico'], }); diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 5572157f7..0d539e8d1 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -14,4 +14,4 @@ -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale; -} +} \ No newline at end of file diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 6ad67dbfc..390f5417d 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -6,7 +6,6 @@ import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font if (__ENV === 'local') { let container = ` - /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ src: url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.woff2?t=1687748230475') format('woff2'), @@ -20,11 +19,16 @@ if (__ENV === 'local') { style.appendChild(document.createTextNode(container)); } - -export default class Iconfont extends PureComponent<{ - code: string; -} & React.DetailedHTMLProps, HTMLElement>> { +export default class Iconfont extends PureComponent< + { + code: string; + } & React.DetailedHTMLProps, HTMLElement> +> { render() { - return {this.props.code} + return ( + + {this.props.code} + + ); } } diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 3b42f8ed4..3027aa320 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -17,5 +17,4 @@ export default { 'connection.message.testSshConnection': 'Test the ssh connection', 'connection.tableHeader.name': 'Name', 'connection.tableHeader.value': 'Value', - 'connection.menu.enterToWorkSpace': 'Go to DataSource', }; \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 809ffbe34..8ccd75a2c 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -17,5 +17,4 @@ export default { 'connection.message.testSshConnection': '测试ssh连接', 'connection.tableHeader.name': '名称', 'connection.tableHeader.value': '值', - 'connection.menu.enterToWorkSpace': '前往数据源', } diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 5376488b9..26c392057 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -9,15 +9,10 @@ import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import { useTheme } from '@/hooks'; import { isEn } from '@/utils/check'; -import { ThemeType, PrimaryColorType, LangType } from '@/constants'; -import { InjectThemeVar } from '@/theme' +import { ThemeType, PrimaryColorType, LangType } from '@/constants/'; +import { InjectThemeVar } from '@/theme'; import styles from './index.less'; -import { - getLang, - getPrimaryColor, - getTheme, - setLang, -} from '@/utils/localStorage'; +import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorage'; declare global { interface Window { @@ -59,7 +54,6 @@ export default function Layout() { ); } - function AppContainer() { const { token } = useToken(); const [initEnd, setInitEnd] = useState(false); @@ -67,7 +61,7 @@ function AppContainer() { useEffect(() => { InjectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); - }, [token]) + }, [token]); useLayoutEffect(() => { collectInitApp(); @@ -102,8 +96,7 @@ function AppContainer() { let theme = getTheme(); if (theme === ThemeType.FollowOs) { theme = - (window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches + (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeType.Dark : ThemeType.Light) || ThemeType.Dark; } @@ -119,14 +112,13 @@ function AppContainer() { } } - return
- { - initEnd && -
- -
- } -
+ return ( +
+ {initEnd && ( +
+ +
+ )} +
+ ); } - - diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index f047f3d06..f0d91c939 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -1,36 +1,63 @@ -import { IConnectionDetails } from "@/typings" - -export interface ConnectionState { - curConnection: IConnectionDetails - connectionList: IConnectionDetails[] +import { IConnectionDetails } from '@/typings/connection'; +import { Effect, Reducer } from 'umi'; +import connectionService from '@/service/connection'; +import { IPageResponse } from '@/typings/common'; + +/** + * 数据源相关 - 链接池、数据库、schema、表 + */ +export interface ConnectionModelState { + curConnection?: IConnectionDetails; + connectionList: IConnectionDetails[]; } +export interface ConnectionModelType { + namespace: 'connection'; + state: ConnectionModelState; + reducers: { + // 设置连接池列表 + setConnectionList: Reducer; + setCurConnection: Reducer; + }; + effects: { + // setConnectionList: Effect; + }; +} -export default { - namespace: 'connection', - state: { - curConnection: null, - connectionList: [], +// const ConnectionModel:ConnectionModelType = { +const ConnectionModel = { + namespace: 'connection', + state: { + curConnection: undefined, + connectionList: [], + }, + reducers: { + // 设置连接池列表 + setConnectionList(state: ConnectionModelState, { payload }: { payload: ConnectionModelState['connectionList'] }) { + return { + ...state, + connectionList: payload, + }; }, - reducers: { - // 获取连接池列表 - setConnectionList(state: ConnectionState, { payload }: { payload: ConnectionState['connectionList'] }) { - return { - ...state, - connectionList: payload - } - }, + // 设置当前选着的Connection + setCurConnection(state: ConnectionModelState, { payload }: { payload: ConnectionModelState['curConnection'] }) { + return { ...state, curConnection: payload }; + }, - // 设置当前选着的Connection - setCurConnection( - state: ConnectionState, - { payload }: { payload: ConnectionState['curConnection'] }, - ) { - return { ...state, curConnection: payload } - }, + }, + effects: { + *fetchConnectionList(_, { call, put }) { + const res = (yield connectionService.getList({ pageNo: 1, pageSize: 999 })) as IPageResponse; + console.log('fetchConnectionList==>', res.data); + yield put({ + type: 'setConnectionList', + payload: res.data, + }); + }, + }, +}; - } -} \ No newline at end of file +export default ConnectionModel; diff --git a/chat2db-client/src/models/database.ts b/chat2db-client/src/models/database.ts new file mode 100644 index 000000000..a5f8e5d94 --- /dev/null +++ b/chat2db-client/src/models/database.ts @@ -0,0 +1,43 @@ +import sqlService from '@/service/sql'; + +interface ISchema { + databaseName: string; + name: string; +} +interface IDatabase { + name: string; + schema: ISchema[]; +} + +const DatabaseModel = { + namespace: 'database', + state: { + databaseAndSchemaList: [], + }, + + reducers: { + // 设置 database schema 数据 + setDatabaseAndSchemaList(state, { payload }) { + return { + ...state, + databaseAndSchemaList: payload, + }; + }, + }, + + effects: { + *fetchDatabaseAndSchemaList(p, { call, put }) { + console.log('fetchDatabaseAndSchemaList start', p); + const res = (yield sqlService.getDatabaseSchemaList({ dataSourceId: 2 })) as { + data: { database: IDatabase[]; schema: ISchema[] }; + }; + console.log('fetchDatabaseAndSchemaList end', res); + yield put({ + type: 'setDatabaseAndSchemaList', + payload: res, + }); + }, + }, +}; + +export default DatabaseModel; diff --git a/chat2db-client/src/pages/main/connections/index.less b/chat2db-client/src/pages/main/connection/index.less similarity index 100% rename from chat2db-client/src/pages/main/connections/index.less rename to chat2db-client/src/pages/main/connection/index.less diff --git a/chat2db-client/src/pages/main/connections/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx similarity index 69% rename from chat2db-client/src/pages/main/connections/index.tsx rename to chat2db-client/src/pages/main/connection/index.tsx index ebefd5dd0..bbbd1f15d 100644 --- a/chat2db-client/src/pages/main/connections/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -12,7 +12,7 @@ import { Button, Dropdown, Modal } from 'antd'; import { MoreOutlined } from '@ant-design/icons'; import styles from './index.less'; import { connect, history } from 'umi'; -import { ConnectionState } from '@/models/connection'; +import { ConnectionModelType } from '@/models/connection'; interface IMenu { key: number; @@ -20,15 +20,22 @@ interface IMenu { icon: React.ReactNode; meta: IConnectionDetails; } -interface IProps { - connectionList: IConnectionDetails[]; - curConnection: IConnectionDetails; - dispatch: (p: { type: string, payload: any }) => void +interface IProps { + connectionModel: { + connectionList: IConnectionDetails[]; + curConnection: IConnectionDetails; + }; + databaseModel: { + databaseAndSchemaList: any; + }; + dispatch: (p: { type: `connection/${string}` | `database/${string}`; payload?: any }) => void; } function Connections(props: IProps) { - const { connectionList } = props; + console.log('props', props); + const { connectionModel, databaseModel, dispatch } = props; + const { connectionList } = connectionModel; const volatileRef = useRef(); // const [connectionList, setConnectionList] = useState(); const [curConnection, setCurConnection] = useState>({}); @@ -38,18 +45,10 @@ function Connections(props: IProps) { }, []); const getConnectionList = async () => { - let p = { - pageNo: 1, - pageSize: 999, - }; - let res = await connectionService.getList(p) - - props.dispatch({ - type: 'connection/setConnectionList', - payload: res.data, - }) - - } + dispatch({ + type: 'connection/fetchConnectionList', + }); + }; function handleCreateConnections(database: IDatabase) { setCurConnection({ @@ -83,9 +82,7 @@ function Connections(props: IProps) { setCurConnection(menu.meta); }} > -
+
{icon} {label}
@@ -95,13 +92,18 @@ function Connections(props: IProps) { items: [ { key: 'EnterWorkSpace', - label: i18n('connection.menu.enterToWorkSpace'), + label: i18n('connection.button.connect'), onClick: () => { - props.dispatch({ + dispatch({ type: 'connection/setCurConnection', payload: menu.meta, - }) - history.push('/workspace') + }); + + dispatch({ + type: 'database/fetchDatabaseAndSchemaList', + }); + + history.push('/workspace'); // window.location.replace('/workspace'); }, }, @@ -160,35 +162,35 @@ function Connections(props: IProps) { />
) : ( -
- {databaseTypeList.map((t) => { - return ( -
-
-
-
- -
- {t.name} -
-
- +
+ {databaseTypeList.map((t) => { + return ( +
+
+
+
+
+ {t.name} +
+
+
- ); - })} - { - Array.from({ length: 5 }).map(t => { - return
- }) - } -
- )} +
+ ); + })} + {Array.from({ length: 20 }).map((t) => { + return
; + })} +
+ )}
); -}; - +} -export default connect(({ connection }: { connection: ConnectionState }) => (connection))(Connections); +export default connect(({ connection, database }: { connection: ConnectionModelType; database: any }) => ({ + connectionModel: connection, + databaseModel: database, +}))(Connections); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 4762fef59..dfb502be5 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -7,7 +7,7 @@ import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; -import DataSource from './connections'; +import DataSource from './connection'; import Workspace from './workspace'; import Dashboard from './dashboard'; import Chat from './chat'; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 590f170d6..39d9dfbfb 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,4 +1,4 @@ -import createRequest from "./base"; +import createRequest from './base'; import { IPageResponse, ITable, IPageParams } from '@/types'; import { DatabaseTypeCode } from '@/constants'; @@ -10,10 +10,10 @@ export interface IGetListParams extends IPageParams { } export interface IExecuteSqlParams { - sql: string, - dataSourceId: number, - databaseName: string, - consoleId: number, + sql: string; + dataSourceId: number; + databaseName: string; + consoleId: number; } export interface IExecuteSqlResponse { @@ -25,9 +25,9 @@ export interface IExecuteSqlResponse { dataList: any[]; } export interface IConnectConsoleParams { - consoleId: number, - dataSourceId: number, - databaseName: string, + consoleId: number; + dataSourceId: number; + databaseName: string; } const getList = createRequest>('/api/rdb/ddl/list', {}); @@ -83,6 +83,10 @@ const getIndexList = createRequest('/api/rdb/ddl/index_ const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get' }); const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { method: 'get' }); +const getDatabaseSchemaList = createRequest<{ dataSourceId: number; databaseName?: string; schemaName?: string }>( + '/api/rdb/ddl/database_schema_list', + { method: 'get' } +); export default { getList, @@ -96,5 +100,6 @@ export default { getColumnList, getIndexList, getKeyList, - getSchemaList -} \ No newline at end of file + getSchemaList, + getDatabaseSchemaList +}; diff --git a/chat2db-client/src/theme/index.ts b/chat2db-client/src/theme/index.ts index ef7eff1ea..affaf40ce 100644 --- a/chat2db-client/src/theme/index.ts +++ b/chat2db-client/src/theme/index.ts @@ -1,13 +1,13 @@ import antdDarkTheme from './dark'; import antdLightTheme from './light'; -import { ThemeType, PrimaryColorType } from "@/constants"; -import { ITheme } from '@/typings'; +import { ThemeType, PrimaryColorType } from '@/constants'; +import { ITheme } from '@/typings/theme'; import lodash from 'lodash'; const antdThemeConfigs = { [ThemeType.Dark]: antdDarkTheme, [ThemeType.Light]: antdLightTheme, -} +}; export function getAntdThemeConfig(theme: ITheme) { const antdThemeConfig = lodash.cloneDeep(antdThemeConfigs[theme.backgroundColor]); @@ -15,29 +15,29 @@ export function getAntdThemeConfig(theme: ITheme) { ...antdThemeConfig.token, ...(antdThemeConfig.antdPrimaryColor[theme.primaryColor as PrimaryColorType] || {}), }; - return antdThemeConfig + return antdThemeConfig; } // TODO: 只插入一次 export function InjectThemeVar(token: { [key in string]: string }, theme: ThemeType, primaryColor: PrimaryColorType) { let css = ''; - Object.keys(token).map(t => { + Object.keys(token).map((t) => { const attributeName = camelToDash(t); let value = token[t]; // 将需要px的数字带上px - const joinPxArr = ['fontSize', 'borderRadius'] + const joinPxArr = ['fontSize', 'borderRadius', 'borderRadiusLG']; if (joinPxArr.includes(t)) { - value = value + 'px' + value = value + 'px'; } - css = css + `--${attributeName}: ${value};\n` - }) + css = css + `--${attributeName}: ${value};\n`; + }); const container = `html[theme='${theme}'],html[primary-color='${primaryColor}']{ ${css} - }` + }`; - let style = document.createElement("style"); // 创建style标签 - style.type = "text/css"; + let style = document.createElement('style'); // 创建style标签 + style.type = 'text/css'; style.appendChild(document.createTextNode(container)); document.head.appendChild(style); // 将style标签插入到head标签中 diff --git a/chat2db-client/typings.d.ts b/chat2db-client/typings.d.ts index dc75bbd41..e076dda6e 100644 --- a/chat2db-client/typings.d.ts +++ b/chat2db-client/typings.d.ts @@ -4,5 +4,6 @@ declare namespace NodeJS { interface ProcessEnv { readonly NODE_ENV: 'development' | 'production' readonly UMI_ENV: string + readonly __ENV: string; } } \ No newline at end of file From e9407e24d5460d6aa1f1e3cc8a6553ac5a0eb0c7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 27 Jun 2023 00:07:37 +0800 Subject: [PATCH 0086/1069] feat: mainpage model --- chat2db-client/src/blocks/Setting/index.tsx | 4 +- .../src/components/CreateConnection/index.tsx | 18 ++++-- .../src/components/Iconfont/index.tsx | 9 +-- chat2db-client/src/models/main.ts | 20 +++++++ .../src/pages/main/connection/index.tsx | 58 +++++++++---------- chat2db-client/src/pages/main/index.tsx | 39 +++++++++++-- chat2db-client/src/utils/index.ts | 13 +++++ 7 files changed, 117 insertions(+), 44 deletions(-) create mode 100644 chat2db-client/src/models/main.ts diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 6941a095f..8193c1718 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -123,7 +123,7 @@ export default memo(function Setting({ className, text }) { {text ? ( {text} ) : ( - + )}
{APP_NAME}
- {i18n('setting.text.currentEnv')}:{window.__ENV} + {i18n('setting.text.currentEnv')}:{__ENV}
{i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 3c30dd778..a6c115675 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -41,6 +41,7 @@ export default function CreateConnection(props: IProps) { const [loadings, setLoading] = useState({ confirmButton: false, testButton: false, + backfillDataLoading: false }); // const [connectionData, setConnectionData] = useState(props.connectionData); // const [currentType, setCurrentType] = useState(createType || DatabaseTypeCode.MYSQL); @@ -56,6 +57,10 @@ export default function CreateConnection(props: IProps) { }, [backfillData.id]); function getConnectionDetails(id: number) { + setLoading({ + ...loadings, + backfillDataLoading: true + }) connectionService.getDetails({ id }).then((res) => { if (!res) { return; @@ -65,9 +70,14 @@ export default function CreateConnection(props: IProps) { } else { res.authentication = 2; } + setBackfillData(res); + }).finally(() => { setTimeout(() => { - setBackfillData(res); - }, 300); + setLoading({ + ...loadings, + backfillDataLoading: false + }) + }, 100) }); } @@ -178,7 +188,7 @@ export default function CreateConnection(props: IProps) { return (
- +
@@ -210,7 +220,7 @@ export default function CreateConnection(props: IProps) { loading={loadings.confirmButton} onClick={saveConnection.bind(null, backfillData.id ? submitType.UPDATE : submitType.SAVE)} > - {i18n('common.button.save')} + {backfillData.id ? i18n('common.button.edit') : i18n('common.button.save')}
diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 390f5417d..ef5c523fd 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -6,11 +6,12 @@ import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font if (__ENV === 'local') { let container = ` + /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.woff2?t=1687748230475') format('woff2'), - url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.woff?t=1687748230475') format('woff'), - url('//at.alicdn.com/t/a/font_3633546_p80guyu8w2s.ttf?t=1687748230475') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_h8vaafdnpbp.woff2?t=1687789960780') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_h8vaafdnpbp.woff?t=1687789960780') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_h8vaafdnpbp.ttf?t=1687789960780') format('truetype'); } ` let style = document.createElement("style"); @@ -23,7 +24,7 @@ export default class Iconfont extends PureComponent< { code: string; } & React.DetailedHTMLProps, HTMLElement> -> { + > { render() { return ( diff --git a/chat2db-client/src/models/main.ts b/chat2db-client/src/models/main.ts new file mode 100644 index 000000000..bdc0dbb17 --- /dev/null +++ b/chat2db-client/src/models/main.ts @@ -0,0 +1,20 @@ +export interface MainState { + curPage: string; +} + +//全局配置信息 +const mainModel = { + namespace: 'mainPage', + + state: { + curPage: 'connections', + }, + + reducers: { + updateCurPage(state: MainState, { payload }: { payload: MainState['curPage'] }) { + return { ...state, curPage: payload }; + }, + }, +}; + +export default mainModel diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index bbbd1f15d..04dad3e4c 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -29,11 +29,10 @@ interface IProps { databaseModel: { databaseAndSchemaList: any; }; - dispatch: (p: { type: `connection/${string}` | `database/${string}`; payload?: any }) => void; + dispatch: (p: { type: `connection/${string}` | `database/${string}`; payload ?: any }) => void; } function Connections(props: IProps) { - console.log('props', props); const { connectionModel, databaseModel, dispatch } = props; const { connectionList } = connectionModel; const volatileRef = useRef(); @@ -87,24 +86,25 @@ function Connections(props: IProps) { {label}
{ + // dispatch({ + // type: 'connection/setCurConnection', + // payload: menu.meta, + // }); + dispatch({ - type: 'connection/setCurConnection', - payload: menu.meta, - }); + type: 'mainPage/updateCurPage', + payload: 'workspace' + }) dispatch({ type: 'database/fetchDatabaseAndSchemaList', }); - - history.push('/workspace'); - // window.location.replace('/workspace'); }, }, { @@ -162,29 +162,29 @@ function Connections(props: IProps) { />
) : ( -
- {databaseTypeList.map((t) => { - return ( -
-
-
-
- +
+ {databaseTypeList.map((t) => { + return ( +
+
+
+
+ +
+ {t.name} +
+
+
- {t.name} -
-
-
-
- ); - })} - {Array.from({ length: 20 }).map((t) => { - return
; - })} -
- )} + ); + })} + {Array.from({ length: 20 }).map((t) => { + return
; + })} +
+ )}
); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index dfb502be5..3bb3d3af7 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -1,11 +1,13 @@ import React, { useEffect, useState, PropsWithChildren } from 'react'; import i18n from '@/i18n'; import { Button } from 'antd'; -import { history } from 'umi'; +import { history, connect } from 'umi'; import classnames from 'classnames'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; +import { MainState } from '@/models/main'; +import { findObjListValue } from '@/utils' import DataSource from './connection'; import Workspace from './workspace'; @@ -14,7 +16,6 @@ import Chat from './chat'; import styles from './index.less'; import { INavItem } from '@/typings/main'; -console.log('UMI_ENV', process.env.UMI_ENV) const navConfig: INavItem[] = [ { key: 'workspace', @@ -42,12 +43,34 @@ const navConfig: INavItem[] = [ ]; const initPageIndex = navConfig.findIndex(t => `/${t.key}` === window.location.pathname); -function MainPage() { + +interface IProps { + mainModel: MainState; + dispatch: any; +} + +function MainPage(props: IProps) { + const { mainModel, dispatch } = props; + const { curPage } = mainModel; const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > 0 ? initPageIndex : 0]); + + useEffect(() => { + dispatch({ + type: 'mainPage/updateCurPage', + payload: activeNav.key + }) + }, [activeNav]) + + useEffect(() => { + if (curPage !== activeNav.key) { + const activeNav = navConfig[findObjListValue(navConfig, 'key', curPage)] + setActiveNav(activeNav) + } + }, [curPage]) + function switchingNav(item: INavItem) { // change url,but no page refresh window.history.pushState({}, "", item.key); - if (item.openBrowser) { window.open(item.openBrowser); } else { @@ -55,6 +78,10 @@ function MainPage() { } } + useEffect(() => { + + }, []) + return (
@@ -96,4 +123,6 @@ function MainPage() { ); } -export default MainPage; +export default connect(({ mainPage }: { mainPage: MainState }) => ({ + mainModel: mainPage, +}))(MainPage); diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 6a63f0ffb..9c1bf95e1 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -110,3 +110,16 @@ export const callVar = (css: string) => { .getPropertyValue(css) .trim(); }; + +// 给我一个 obj[], 和 obj的 key 和 value,给你返index +export function findObjListValue(list: T[], key: K, value: any) { + let flag = -1; + list.forEach((t: T, index) => { + Object.keys(t).forEach((j: K) => { + if (j === key && t[j] === value) { + flag = index + } + }) + }) + return flag +} From 51996c9dab537c7cb5967cedbb9b9c948acff28e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Tue, 27 Jun 2023 15:58:49 +0800 Subject: [PATCH 0087/1069] feat: workspace model --- chat2db-client/readme.md | 5 + chat2db-client/src/models/connection.ts | 11 +- chat2db-client/src/models/database.ts | 41 ++-- .../src/models/{main.ts => mainPage.ts} | 1 - chat2db-client/src/models/workspace.ts | 70 ++++++ .../src/pages/main/connection/index.less | 10 +- .../src/pages/main/connection/index.tsx | 106 +++++---- chat2db-client/src/pages/main/index.less | 8 +- chat2db-client/src/pages/main/index.tsx | 2 +- .../components/WorkspaceLeft/index.tsx | 204 +++++++++--------- .../components/WorkspaceRight/index.tsx | 48 +++-- chat2db-client/src/service/sql.ts | 16 +- 12 files changed, 304 insertions(+), 218 deletions(-) rename chat2db-client/src/models/{main.ts => mainPage.ts} (93%) create mode 100644 chat2db-client/src/models/workspace.ts diff --git a/chat2db-client/readme.md b/chat2db-client/readme.md index 88808e297..1d43aa257 100644 --- a/chat2db-client/readme.md +++ b/chat2db-client/readme.md @@ -8,6 +8,11 @@ 目录结构 tree ./ -L 2 -I node_modules +## TS书写规范 + 1. 所有的interfase 与 type 必须已I开头 + `interfase IState { name: string }` // good + `interfase State { name: string }` // bad + ## 如何使用国际化 diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index f0d91c939..cacc54be0 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -11,7 +11,7 @@ export interface ConnectionModelState { connectionList: IConnectionDetails[]; } -export interface ConnectionModelType { +export interface IConnectionModelType { namespace: 'connection'; state: ConnectionModelState; reducers: { @@ -20,12 +20,12 @@ export interface ConnectionModelType { setCurConnection: Reducer; }; effects: { - // setConnectionList: Effect; + fetchConnectionList: Effect; }; } // const ConnectionModel:ConnectionModelType = { -const ConnectionModel = { +const ConnectionModel: IConnectionModelType = { namespace: 'connection', state: { curConnection: undefined, @@ -33,7 +33,7 @@ const ConnectionModel = { }, reducers: { // 设置连接池列表 - setConnectionList(state: ConnectionModelState, { payload }: { payload: ConnectionModelState['connectionList'] }) { + setConnectionList(state, { payload }) { return { ...state, connectionList: payload, @@ -41,7 +41,7 @@ const ConnectionModel = { }, // 设置当前选着的Connection - setCurConnection(state: ConnectionModelState, { payload }: { payload: ConnectionModelState['curConnection'] }) { + setCurConnection(state, { payload }) { return { ...state, curConnection: payload }; }, @@ -50,7 +50,6 @@ const ConnectionModel = { effects: { *fetchConnectionList(_, { call, put }) { const res = (yield connectionService.getList({ pageNo: 1, pageSize: 999 })) as IPageResponse; - console.log('fetchConnectionList==>', res.data); yield put({ type: 'setConnectionList', payload: res.data, diff --git a/chat2db-client/src/models/database.ts b/chat2db-client/src/models/database.ts index a5f8e5d94..386eec107 100644 --- a/chat2db-client/src/models/database.ts +++ b/chat2db-client/src/models/database.ts @@ -1,39 +1,44 @@ -import sqlService from '@/service/sql'; +import sqlService,{MetaSchemaVO} from '@/service/sql'; +import { Effect, Reducer } from 'umi'; -interface ISchema { - databaseName: string; - name: string; +interface IState { + databaseAndSchema: MetaSchemaVO } -interface IDatabase { - name: string; - schema: ISchema[]; + +export interface DatabaseModelType { + namespace: 'database'; + state: IState; + reducers: { + setDatabaseAndSchema: Reducer; + }; + effects: { + fetchdatabaseAndSchema: Effect; + }; } -const DatabaseModel = { +const DatabaseModel:DatabaseModelType = { namespace: 'database', state: { - databaseAndSchemaList: [], + databaseAndSchema: {} , }, reducers: { // 设置 database schema 数据 - setDatabaseAndSchemaList(state, { payload }) { + setDatabaseAndSchema(state, { payload }) { return { ...state, - databaseAndSchemaList: payload, + databaseAndSchema: payload, }; }, }, effects: { - *fetchDatabaseAndSchemaList(p, { call, put }) { - console.log('fetchDatabaseAndSchemaList start', p); - const res = (yield sqlService.getDatabaseSchemaList({ dataSourceId: 2 })) as { - data: { database: IDatabase[]; schema: ISchema[] }; - }; - console.log('fetchDatabaseAndSchemaList end', res); + *fetchdatabaseAndSchema(p, action) { + const { call, put } = action + console.log(p,action) + const res = (yield sqlService.getDatabaseSchemaList({ dataSourceId: 2 })) yield put({ - type: 'setDatabaseAndSchemaList', + type: 'setDatabaseAndSchema', payload: res, }); }, diff --git a/chat2db-client/src/models/main.ts b/chat2db-client/src/models/mainPage.ts similarity index 93% rename from chat2db-client/src/models/main.ts rename to chat2db-client/src/models/mainPage.ts index bdc0dbb17..8d4a1b8ed 100644 --- a/chat2db-client/src/models/main.ts +++ b/chat2db-client/src/models/mainPage.ts @@ -2,7 +2,6 @@ export interface MainState { curPage: string; } -//全局配置信息 const mainModel = { namespace: 'mainPage', diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts new file mode 100644 index 000000000..c286cb185 --- /dev/null +++ b/chat2db-client/src/models/workspace.ts @@ -0,0 +1,70 @@ +import sqlService, { MetaSchemaVO } from '@/service/sql'; +import { DatabaseTypeCode } from '@/constants'; +import { Effect, Reducer } from 'umi'; + +export type ICurWorkspaceData = { + dataSourceId: number; + databaseSourceName: string; + databaseType: DatabaseTypeCode; + databaseName?: string; + schemaName?: string; +} + +export interface IState { + // 当前连接下的及联databaseAndSchema数据 + databaseAndSchema: MetaSchemaVO; + // 当前工作区所需的参数 + curWorkspaceParams: ICurWorkspaceData; +} + +export interface IWorkspaceModelType { + namespace: 'workspace', + state: IState, + reducers: { + setDatabaseAndSchema: Reducer; + setCurWorkspaceParams: Reducer; + }; + effects: { + fetchdatabaseAndSchema: Effect; + }; +} + +const WorkspaceModel:IWorkspaceModelType = { + namespace: 'workspace', + + state: { + databaseAndSchema: {}, + curWorkspaceParams: {} as ICurWorkspaceData + }, + + reducers: { + // 设置 database schema 数据 + setDatabaseAndSchema(state, { payload }) { + return { + ...state, + databaseAndSchema: payload, + }; + }, + + setCurWorkspaceParams(state, { payload }) { + return { + ...state, + curWorkspaceParams: payload, + }; + }, + }, + + effects: { + *fetchdatabaseAndSchema(p, action) { + const { call, put } = action + console.log(p,action) + const res = (yield sqlService.getDatabaseSchemaList({ dataSourceId: 2 })) + yield put({ + type: 'setDatabaseAndSchema', + payload: res, + }); + }, + }, +}; + +export default WorkspaceModel diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index c5a88ff67..592bcbc06 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -48,14 +48,15 @@ margin-bottom: 4px; height: 20px; border-radius: 8px; + user-select: none; .menuItemsTitle { flex: 1; width: 0; .f-single-line(); } - - .moreButton{ + + .moreButton { flex-shrink: 0; display: none; transform: rotate(90deg); @@ -64,13 +65,12 @@ &:hover { background-color: var(--color-hover-bg); border: var(--border-radius); - .moreButton{ + .moreButton { display: block; } } } - .menuItemActive { color: var(--color-primary); // background-color: var(--color-primary-bg); @@ -193,4 +193,4 @@ z-index: 1; transform: scale(1); transition: transform 0.3s ease-in-out; -} \ No newline at end of file +} diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 04dad3e4c..fdf38d5c0 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -11,8 +11,9 @@ import { IDatabase, IConnectionDetails } from '@/typings'; import { Button, Dropdown, Modal } from 'antd'; import { MoreOutlined } from '@ant-design/icons'; import styles from './index.less'; -import { connect, history } from 'umi'; -import { ConnectionModelType } from '@/models/connection'; +import { connect, history, Dispatch } from 'umi'; +import { IConnectionModelType } from '@/models/connection'; +import { IWorkspaceModelType } from '@/models/workspace'; interface IMenu { key: number; @@ -22,18 +23,13 @@ interface IMenu { } interface IProps { - connectionModel: { - connectionList: IConnectionDetails[]; - curConnection: IConnectionDetails; - }; - databaseModel: { - databaseAndSchemaList: any; - }; - dispatch: (p: { type: `connection/${string}` | `database/${string}`; payload ?: any }) => void; + connectionModel: IConnectionModelType['state']; + workspaceModel: IWorkspaceModelType['state']; + dispatch: any; } function Connections(props: IProps) { - const { connectionModel, databaseModel, dispatch } = props; + const { connectionModel, workspaceModel, dispatch } = props; const { connectionList } = connectionModel; const volatileRef = useRef(); // const [connectionList, setConnectionList] = useState(); @@ -66,19 +62,36 @@ function Connections(props: IProps) { [connectionList], ); + const menuItemDoubleClick = (t: any) => { + dispatch({ + type: 'connection/setCurConnection', + payload: t.meta, + }); + + dispatch({ + type: 'mainPage/updateCurPage', + payload: 'workspace' + }) + + dispatch({ + type: 'workspace/fetchdatabaseAndSchema', + }); + } + const renderMenu = () => { return (
- {(menuItems || []).map((menu) => { - const { key, label, icon } = menu; + {(menuItems || []).map((t) => { + const { key, label, icon } = t; return (
{ - setCurConnection(menu.meta); + setCurConnection(t.meta); }} >
@@ -88,25 +101,6 @@ function Connections(props: IProps) { { - // dispatch({ - // type: 'connection/setCurConnection', - // payload: menu.meta, - // }); - - dispatch({ - type: 'mainPage/updateCurPage', - payload: 'workspace' - }) - - dispatch({ - type: 'database/fetchDatabaseAndSchemaList', - }); - }, - }, { key: 'Delete', label: i18n('common.button.delete'), @@ -154,7 +148,7 @@ function Connections(props: IProps) { })} > { setCurConnection({}); }} @@ -162,35 +156,35 @@ function Connections(props: IProps) { />
) : ( -
- {databaseTypeList.map((t) => { - return ( -
-
-
-
- -
- {t.name} -
-
- +
+ {databaseTypeList.map((t) => { + return ( +
+
+
+
+
+ {t.name} +
+
+
- ); - })} - {Array.from({ length: 20 }).map((t) => { - return
; - })} -
- )} +
+ ); + })} + {Array.from({ length: 20 }).map((t) => { + return
; + })} +
+ )}
); } -export default connect(({ connection, database }: { connection: ConnectionModelType; database: any }) => ({ +export default connect(({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ connectionModel: connection, - databaseModel: database, + workspaceModel: workspace, }))(Connections); diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 980c8212f..a81f57901 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -35,7 +35,7 @@ flex-shrink: 0; border-radius: 50%; overflow: hidden; - margin: 40px auto 20px; + margin: 20px auto; cursor: pointer; } @@ -45,7 +45,7 @@ display: flex; flex-direction: column; align-items: center; - + li { display: flex; justify-content: center; @@ -69,7 +69,7 @@ } &:hover { - .icon{ + .icon { color: var(--color-primary); } background-color: var(--color-primary-bg-hover); @@ -77,7 +77,7 @@ } .activeNav { - .icon{ + .icon { color: var(--color-primary); } background-color: var(--color-primary-bg-hover); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 3bb3d3af7..1be5da83f 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -6,7 +6,7 @@ import classnames from 'classnames'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; -import { MainState } from '@/models/main'; +import { MainState } from '@/models/mainPage'; import { findObjListValue } from '@/utils' import DataSource from './connection'; 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 0b764dcb3..754f6d8aa 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -1,26 +1,31 @@ -import React, { memo, useState, useEffect, useRef, useContext } from 'react'; -import styles from './index.less'; +import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react'; +import { connect } from 'umi' +import i18n from '@/i18n'; import classnames from 'classnames'; +import Iconfont from '@/components/Iconfont'; +import LoadingContent from '@/components/Loading/LoadingContent'; import { Cascader, Divider } from 'antd'; -import connectionService from '@/service/connection'; import historyService from '@/service/history'; -import { treeConfig } from '../Tree/treeConfig'; +import { IConnectionModelType } from '@/models/connection'; +import { IWorkspaceModelType } from '@/models/workspace'; import Tree from '../Tree'; -import Iconfont from '@/components/Iconfont'; -import LoadingContent from '@/components/Loading/LoadingContent'; +import { treeConfig } from '../Tree/treeConfig'; import { TreeNodeType } from '@/constants'; import { ITreeNode } from '@/typings'; -import { useReducerContext } from '../../index'; -import { workspaceActionType } from '../../context'; -import i18n from '@/i18n'; -import { IConsole } from '@/typings' +import { IConsole } from '@/typings'; +import styles from './index.less'; + interface IProps { className?: string; } -export default memo(function WorkspaceLeft(props) { - const { className } = props; +const dvaModel = connect(({ connection, workspace }: { connection: IConnectionModelType, workspace: IWorkspaceModelType }) => ({ + connectionModel: connection, + workspaceModel: workspace +})) +const WorkspaceLeft = memo(function (props) { + const { className } = props; return (
@@ -34,38 +39,67 @@ export default memo(function WorkspaceLeft(props) { }); interface Option { - value: number; + value: string; label: string; children?: Option[]; } -function RenderSelectDatabase() { - const [options, setOptions] = useState(); - const { state, dispatch } = useReducerContext(); - const { currentWorkspaceData } = state; +interface IProps { + connectionModel: IConnectionModelType['state']; + workspaceModel: IWorkspaceModelType['state']; + dispatch: any; +} - useEffect(() => { - getDataSource(); - }, []); +function handelDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { + let newCascaderOptions: Option[] = []; + if (databaseAndSchema.databases) { + newCascaderOptions = databaseAndSchema.databases.map(t => { + let schemasList: Option[] = [] + if (t.schemas) { + schemasList = t.schemas.map(t => { + return { + value: t.name, + label: t.name + } + }) + } + return { + value: t.name, + label: t.name, + children: schemasList + } + }) + } else if (databaseAndSchema?.schemas) { + newCascaderOptions = databaseAndSchema.schemas.map(t => { + return { + value: t.name, + label: t.name + } + }) + } + return newCascaderOptions +} - function getDataSource() { - let p = { - pageNo: 1, - pageSize: 999, +const RenderSelectDatabase = dvaModel(function (props: IProps) { + const { connectionModel, workspaceModel, dispatch } = props; + const { databaseAndSchema, curWorkspaceParams } = workspaceModel; + const { curConnection } = connectionModel; + + const cascaderOptions = useMemo(() => { + const res = handelDatabaseAndSchema(databaseAndSchema); + const curWorkspaceParams = { + dataSourceId: curConnection?.id, + databaseSourceName: curConnection?.alias, + databaseName: res?.[0]?.value, + schemaName: res?.[0]?.children?.[0]?.value, + databaseType: curConnection?.type, }; - treeConfig[TreeNodeType.DATA_SOURCES].getChildren!(p).then((res) => { - let newOptions: any = res.map((t) => { - return { - label: t.name, - value: t.key, - type: TreeNodeType.DATA_SOURCE, - isLeaf: false, - databaseType: t.extraParams?.databaseType, - }; - }); - setOptions(newOptions); + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: curWorkspaceParams, }); - } + return res + }, [databaseAndSchema]) const onChange: any = (valueArr: any, selectedOptions: any) => { let labelArr: string[] = []; @@ -73,70 +107,28 @@ function RenderSelectDatabase() { return t.label; }); - const currentWorkspaceData = { - dataSourceId: valueArr[0], - databaseSourceName: labelArr[0], - databaseName: labelArr[1], - schemaName: labelArr[2], - databaseType: selectedOptions[0].databaseType, + const curWorkspaceParams = { + dataSourceId: curConnection?.id, + databaseSourceName: curConnection?.alias, + databaseName: labelArr[0], + schemaName: labelArr[1], + databaseType: curConnection?.type, }; dispatch({ - type: workspaceActionType.CURRENT_WORKSPACE_DATA, - payload: currentWorkspaceData, - }); - }; - - // 及联loadData - const loadData = (selectedOptions: any) => { - if (selectedOptions.length > 1) { - return; - } - - const targetOption = selectedOptions[0]; - treeConfig[TreeNodeType.DATA_SOURCE].getChildren!({ - id: targetOption.value, - }).then((res) => { - let newOptions = res.map((t) => { - return { - label: t.name, - value: t.key, - type: TreeNodeType.DATABASE, - databaseType: t.extraParams?.databaseType, - }; - }); - targetOption.children = newOptions; - setOptions([...(options || [])]); + type: 'workspace/setCurWorkspaceParams', + payload: curWorkspaceParams, }); - - // TODO:根据后端字段 如果有SCHEMAS再去查询SCHEMAS - // if (targetOption.type === TreeNodeType.SCHEMAS) { - // treeConfig[TreeNodeType.DATA_SOURCE]?.getChildren({ - // id: targetOption.value - // }).then(res => { - // let newOptions = res.map((t) => { - // return { - // label: t.name, - // value: t.name, - // type: TreeNodeType.SCHEMAS - // }; - // }); - // targetOption.children = newOptions; - // setOptions([...(options || [])]); - // }) - // } else { - // } }; const dropdownRender = (menus: React.ReactNode) => (
{menus} - {/*
The footer is not very short.
*/}
); function renderCurrentSelected() { - const { databaseName, schemaName, databaseSourceName } = currentWorkspaceData; + const { databaseName, schemaName, databaseSourceName } = curWorkspaceParams; const currentSelectedArr = [databaseSourceName, databaseName, schemaName].filter((t) => t); return currentSelectedArr.join('/'); } @@ -145,9 +137,8 @@ function RenderSelectDatabase() {
@@ -163,25 +154,25 @@ function RenderSelectDatabase() {
); -} +}) -function RenderTableBox() { - const { state, dispatch } = useReducerContext(); - const { currentWorkspaceData } = state; +const RenderTableBox = dvaModel(function RenderTableBox(props: any) { + const { workspaceModel } = props; + const { curWorkspaceParams } = workspaceModel; const [initialData, setInitialData] = useState([]); useEffect(() => { - if (currentWorkspaceData.databaseName) { + if (curWorkspaceParams.databaseName) { getInitialData(); } - }, [currentWorkspaceData]); + }, [curWorkspaceParams]); function getInitialData() { treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, - ...currentWorkspaceData, - extraParams: currentWorkspaceData, + ...curWorkspaceParams, + extraParams: curWorkspaceParams, }).then((res) => { setInitialData(res); }); @@ -195,23 +186,22 @@ function RenderTableBox() {
); -} +}) -function RenderSaveBox() { +const RenderSaveBox = dvaModel(function RenderSaveBox(props: any) { const [savedList, setSaveList] = useState([]); - const { state, dispatch } = useReducerContext(); - const { currentWorkspaceData } = state; + const { workspaceModel } = props; + const { curWorkspaceParams } = workspaceModel; useEffect(() => { getSaveList(); - }, [currentWorkspaceData]) - + }, [curWorkspaceParams]) function getSaveList() { - let p = { + let p: any = { pageNo: 1, pageSize: 999, - ...currentWorkspaceData + ...curWorkspaceParams } historyService.getSaveList(p).then(res => { @@ -233,4 +223,6 @@ function RenderSaveBox() {
-} +}) + +export default dvaModel(WorkspaceLeft) 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 979b5dbd4..f35b10f6f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -1,32 +1,34 @@ import React, { memo, useRef, useEffect, useState } from 'react'; +import { connect } from 'umi' import styles from './index.less'; import classnames from 'classnames'; -import DraggableContainer from '@/components/DraggableContainer'; -import Console from '@/components/Console'; import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment, DatabaseTypeCode } from '@/constants'; import { IConsole } from '@/typings'; import historyService from '@/service/history'; -import { Button, Tabs } from 'antd'; -import { useReducerContext } from '@/pages/main/workspace'; -import { workspaceActionType } from '@/pages/main/workspace/context'; -import SearchResult from '@/components/SearchResult'; +import { Tabs } from 'antd'; import LoadingContent from '@/components/Loading/LoadingContent'; import WorkspaceRightItem from '../WorkspaceRightItem'; +import { IWorkspaceModelType } from '@/models/workspace'; + interface IProps { className?: string; + workspaceModel: IWorkspaceModelType['state']; + dispatch: any; } -export default memo(function WorkspaceRight(props) { +const WorkspaceRight = memo(function (props) { const { className } = props; const [consoleList, setConsoleList] = useState(); const [activeConsoleId, setActiveConsoleId] = useState(); - const { state, dispatch } = useReducerContext(); - const { dblclickTreeNodeData, currentWorkspaceData } = state; + const dblclickTreeNodeData = false; + const { workspaceModel, dispatch } = props; + const { databaseAndSchema, curWorkspaceParams } = workspaceModel; + useEffect(() => { getConsoleList(); - }, [currentWorkspaceData]); + }, [curWorkspaceParams]); useEffect(() => { if (!dblclickTreeNodeData) { @@ -68,14 +70,14 @@ export default memo(function WorkspaceRight(props) { setConsoleList([...(consoleList || []), newConsole]); }); } - }, [dblclickTreeNodeData]); + }, [curWorkspaceParams]); function getConsoleList() { - let p = { + let p: any = { pageNo: 1, pageSize: 999, - ConsoleOpenedStatus: ConsoleOpenedStatus.IS_OPEN, - ...currentWorkspaceData, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + ...curWorkspaceParams, }; historyService.getSaveList(p).then((res) => { @@ -183,7 +185,7 @@ export default memo(function WorkspaceRight(props) { let p: any = { id: targetKey, - ConsoleOpenedStatus: 'n', + tabOpened: 'n', }; const window = consoleList?.find((t) => t.id === +targetKey); @@ -226,10 +228,10 @@ export default memo(function WorkspaceRight(props) { data={ { initDDL: t.ddl, - databaseName: currentWorkspaceData.databaseName!, - dataSourceId: currentWorkspaceData.dataSourceId!, - type: currentWorkspaceData.databaseType!, - schemaName: currentWorkspaceData?.schemaName!, + databaseName: curWorkspaceParams.databaseName!, + dataSourceId: curWorkspaceParams.dataSourceId!, + type: curWorkspaceParams.databaseType!, + schemaName: curWorkspaceParams?.schemaName!, consoleId: t.id, consoleName: t.name, } @@ -240,4 +242,10 @@ export default memo(function WorkspaceRight(props) {
); -}); +}) + +const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ + workspaceModel: workspace +})) + +export default dvaModel(WorkspaceRight) diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 39d9dfbfb..910490e59 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -72,6 +72,20 @@ export interface ISchemaResponse { name: string; } +export interface MetaSchemaVO { + databases?: Database[]; + schemas?: Schema[]; +} + +export interface Database { + name: string; + schemas?: Schema[]; +} + +export interface Schema { + name: string; +} + const deleteTable = createRequest('/api/rdb/ddl/delete', { method: 'post' }); const createTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/create/example', { method: 'get' }); const updateTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>('/api/rdb/ddl/update/example', { method: 'get' }); @@ -83,7 +97,7 @@ const getIndexList = createRequest('/api/rdb/ddl/index_ const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get' }); const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { method: 'get' }); -const getDatabaseSchemaList = createRequest<{ dataSourceId: number; databaseName?: string; schemaName?: string }>( +const getDatabaseSchemaList = createRequest<{ dataSourceId: number; }, MetaSchemaVO>( '/api/rdb/ddl/database_schema_list', { method: 'get' } ); From efe1c830bb7fb0062f294da6cdc5eaf5463dc2af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Tue, 27 Jun 2023 17:37:53 +0800 Subject: [PATCH 0088/1069] =?UTF-8?q?feat:=E5=8F=8C=E5=87=BB=E6=A0=91?= =?UTF-8?q?=E5=88=9B=E5=BB=BAconsole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Iconfont/index.tsx | 8 ++-- chat2db-client/src/models/connection.ts | 4 +- chat2db-client/src/models/mainPage.ts | 19 ++++++-- chat2db-client/src/models/workspace.ts | 20 ++++++-- .../src/pages/main/connection/index.tsx | 4 -- chat2db-client/src/pages/main/index.tsx | 43 ++++++++++++----- .../main/workspace/components/Tree/index.less | 29 ++++++++---- .../main/workspace/components/Tree/index.tsx | 47 ++++++++++++------- .../components/WorkspaceLeft/index.less | 6 ++- .../components/WorkspaceLeft/index.tsx | 38 ++++++++------- .../components/WorkspaceRight/index.tsx | 9 ++-- chat2db-client/src/utils/localStorage.ts | 22 ++++++--- 12 files changed, 162 insertions(+), 87 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index ef5c523fd..fb19ea55c 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_h8vaafdnpbp.woff2?t=1687789960780') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_h8vaafdnpbp.woff?t=1687789960780') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_h8vaafdnpbp.ttf?t=1687789960780') format('truetype'); + src: url('//at.alicdn.com/t/a/font_3633546_7qftabf6y8.woff2?t=1687853414741') format('woff2'), + url('//at.alicdn.com/t/a/font_3633546_7qftabf6y8.woff?t=1687853414741') format('woff'), + url('//at.alicdn.com/t/a/font_3633546_7qftabf6y8.ttf?t=1687853414741') format('truetype'); } ` let style = document.createElement("style"); @@ -24,7 +24,7 @@ export default class Iconfont extends PureComponent< { code: string; } & React.DetailedHTMLProps, HTMLElement> - > { +> { render() { return ( diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index cacc54be0..b8abebfa7 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -2,6 +2,7 @@ import { IConnectionDetails } from '@/typings/connection'; import { Effect, Reducer } from 'umi'; import connectionService from '@/service/connection'; import { IPageResponse } from '@/typings/common'; +import { getCurConnection } from '@/utils/localStorage'; /** * 数据源相关 - 链接池、数据库、schema、表 @@ -28,7 +29,7 @@ export interface IConnectionModelType { const ConnectionModel: IConnectionModelType = { namespace: 'connection', state: { - curConnection: undefined, + curConnection: getCurConnection(), connectionList: [], }, reducers: { @@ -42,6 +43,7 @@ const ConnectionModel: IConnectionModelType = { // 设置当前选着的Connection setCurConnection(state, { payload }) { + localStorage.setItem('cur-connection',JSON.stringify(payload)) return { ...state, curConnection: payload }; }, diff --git a/chat2db-client/src/models/mainPage.ts b/chat2db-client/src/models/mainPage.ts index 8d4a1b8ed..b4503b11e 100644 --- a/chat2db-client/src/models/mainPage.ts +++ b/chat2db-client/src/models/mainPage.ts @@ -1,19 +1,28 @@ -export interface MainState { +import { Effect, Reducer } from 'umi'; +export interface IState { curPage: string; } -const mainModel = { +export interface IMainPageType { + namespace: 'mainPage', + state: IState, + reducers: { + updateCurPage: Reducer; + }; +} + +const MainPageModel: IMainPageType = { namespace: 'mainPage', state: { - curPage: 'connections', + curPage: '', }, reducers: { - updateCurPage(state: MainState, { payload }: { payload: MainState['curPage'] }) { + updateCurPage(state, { payload }) { return { ...state, curPage: payload }; }, }, }; -export default mainModel +export default MainPageModel diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index c286cb185..2b89d9706 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -1,8 +1,10 @@ +import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/utils/localStorage'; import sqlService, { MetaSchemaVO } from '@/service/sql'; import { DatabaseTypeCode } from '@/constants'; import { Effect, Reducer } from 'umi'; +import { ITreeNode } from '@/typings' -export type ICurWorkspaceData = { +export type ICurWorkspaceParams = { dataSourceId: number; databaseSourceName: string; databaseType: DatabaseTypeCode; @@ -14,7 +16,9 @@ export interface IState { // 当前连接下的及联databaseAndSchema数据 databaseAndSchema: MetaSchemaVO; // 当前工作区所需的参数 - curWorkspaceParams: ICurWorkspaceData; + curWorkspaceParams: ICurWorkspaceParams; + // 双击树node节点 + doubleClickTreeNodeData: ITreeNode | undefined; } export interface IWorkspaceModelType { @@ -23,6 +27,7 @@ export interface IWorkspaceModelType { reducers: { setDatabaseAndSchema: Reducer; setCurWorkspaceParams: Reducer; + setDoubleClickTreeNodeData: Reducer; //TS TODO: }; effects: { fetchdatabaseAndSchema: Effect; @@ -34,7 +39,8 @@ const WorkspaceModel:IWorkspaceModelType = { state: { databaseAndSchema: {}, - curWorkspaceParams: {} as ICurWorkspaceData + curWorkspaceParams: getCurrentWorkspaceDatabase(), + doubleClickTreeNodeData: undefined, }, reducers: { @@ -47,11 +53,19 @@ const WorkspaceModel:IWorkspaceModelType = { }, setCurWorkspaceParams(state, { payload }) { + setCurrentWorkspaceDatabase(payload); return { ...state, curWorkspaceParams: payload, }; }, + + setDoubleClickTreeNodeData(state, { payload }) { + return { + ...state, + doubleClickTreeNodeData: payload, + }; + }, }, effects: { diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index fdf38d5c0..0923dc24d 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -72,10 +72,6 @@ function Connections(props: IProps) { type: 'mainPage/updateCurPage', payload: 'workspace' }) - - dispatch({ - type: 'workspace/fetchdatabaseAndSchema', - }); } const renderMenu = () => { diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 1be5da83f..e1f9b75f7 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -6,7 +6,9 @@ import classnames from 'classnames'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; -import { MainState } from '@/models/mainPage'; +import { IMainPageType } from '@/models/mainPage'; +import { IWorkspaceModelType } from '@/models/workspace'; +import { IConnectionModelType } from '@/models/connection'; import { findObjListValue } from '@/utils' import DataSource from './connection'; @@ -45,32 +47,50 @@ const navConfig: INavItem[] = [ const initPageIndex = navConfig.findIndex(t => `/${t.key}` === window.location.pathname); interface IProps { - mainModel: MainState; + mainModel: IMainPageType['state']; + workspaceModel: IWorkspaceModelType['state']; + connectionModel: IConnectionModelType['state']; dispatch: any; } function MainPage(props: IProps) { - const { mainModel, dispatch } = props; + const { mainModel, workspaceModel, connectionModel, dispatch } = props; const { curPage } = mainModel; + const { curConnection } = connectionModel; const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > 0 ? initPageIndex : 0]); useEffect(() => { + // activeNav 发生变化,同步到全局状态管理 dispatch({ type: 'mainPage/updateCurPage', payload: activeNav.key }) + // activeNav 发生变化 如果没有选中连接并且不在connections 那么需要跳转到 连接页面 + // if (!curConnection?.id && activeNav.key !== 'connections') { + // setActiveNav(navConfig[2]); + // } + // activeNav 变化 同步地址栏变化 + // change url,but no page refresh + window.history.pushState({}, "", activeNav.key); }, [activeNav]) useEffect(() => { - if (curPage !== activeNav.key) { - const activeNav = navConfig[findObjListValue(navConfig, 'key', curPage)] - setActiveNav(activeNav) + // 全局状态curPage发生变化,activeNav 需要同步变化 + if (curPage && curPage !== activeNav.key) { + const newActiveNav = navConfig[findObjListValue(navConfig, 'key', curPage)] + setActiveNav(newActiveNav) } }, [curPage]) + useEffect(() => { + if (curConnection?.id) { + dispatch({ + type: 'workspace/fetchdatabaseAndSchema', + }) + } + }, [curConnection]) + function switchingNav(item: INavItem) { - // change url,but no page refresh - window.history.pushState({}, "", item.key); if (item.openBrowser) { window.open(item.openBrowser); } else { @@ -78,9 +98,6 @@ function MainPage(props: IProps) { } } - useEffect(() => { - - }, []) return (
@@ -123,6 +140,8 @@ function MainPage(props: IProps) { ); } -export default connect(({ mainPage }: { mainPage: MainState }) => ({ +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/workspace/components/Tree/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/index.less index 6913f3ad3..4aa3f2546 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.less @@ -8,16 +8,22 @@ display: flex; align-items: center; overflow: hidden; - height: 24px; - border-radius: 2px; + height: 26px; + border-radius: 4px; opacity: 1; cursor: pointer; transition: opacity 0.05s ease-in, height 0.1s ease-in; user-select: none; - &:hover .right { - background-color: var(--custom-primary); - color: var(--color-primary); + &:hover { + background-color: var(--color-hover-bg); + .right { + background-color: var(--custom-primary); + color: var(--color-primary); + } + .arrows { + display: flex; + } } } @@ -35,35 +41,38 @@ height: 100%; padding: 0px 10px; border-radius: 2px; - color: var(--color-text-secondary) + color: var(--color-text-secondary); } .arrows { flex-shrink: 0; width: 20px; - height: 24px; + height: 26px; display: flex; align-items: center; transform: rotate(0deg); transition: transform 0.2s ease-in; + display: none; } .arrowsIcon { display: inline-block; + transform: rotate(-90deg); + font-size: 12px; } .rotateArrowsIcon { - transform: rotate(90deg); + transform: rotate(0deg); } .dblclickArea { display: flex; flex: 1; - height: 24px; + height: 26px; } .typeIcon { - height: 24px; + height: 26px; display: flex; align-items: center; width: 20px; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index ddefdd085..668dfccaf 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -1,4 +1,5 @@ import React, { memo, useEffect, useRef, useContext, useState, useImperativeHandle } from 'react'; +import { connect } from 'umi' import styles from './index.less'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; @@ -9,9 +10,8 @@ import { TreeNodeType, databaseMap } from '@/constants'; import LoadingContent from '@/components/Loading/LoadingContent'; // import TreeNodeRightClick from './TreeNodeRightClick'; import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; -import { useReducerContext } from '@/pages/main/workspace'; import { workspaceActionType } from '@/pages/main/workspace/context'; -// import { DatabaseContext } from '@/context/database'; +import { IWorkspaceModelType } from '@/models/workspace' interface IProps { className?: string; @@ -24,8 +24,14 @@ interface TreeNodeIProps { show: boolean; setTreeData: Function; showAllChildrenPenetrate?: boolean; + workspaceModel: IWorkspaceModelType['state']; + dispatch: any; } +const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ + workspaceModel: workspace +})) + function Tree(props: IProps) { const { className, initialData } = props; const [treeData, setTreeData] = useState(); @@ -57,14 +63,21 @@ function Tree(props: IProps) { }) }
-}; +} -function TreeNode(props: TreeNodeIProps) { - const { setTreeData, data, level, show = false, showAllChildrenPenetrate = false } = props; +const TreeNode = dvaModel((props: TreeNodeIProps) => { + const { + setTreeData, + data, + level, + show = false, + showAllChildrenPenetrate = false, + dispatch, + workspaceModel + } = props; const [showChildren, setShowChildren] = useState(false); const [isLoading, setIsLoading] = useState(false); const indentArr = new Array(level).fill('indent'); - const { state, dispatch } = useReducerContext(); function loadData(data: ITreeNode) { const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; @@ -123,13 +136,13 @@ function TreeNode(props: TreeNodeIProps) { } }; - const renderMenu = () => { - return - } + // const renderMenu = () => { + // return + // } const recognizeIcon = (treeNodeType: TreeNodeType) => { if (treeNodeType === TreeNodeType.DATA_SOURCE) { @@ -152,7 +165,7 @@ function TreeNode(props: TreeNodeIProps) { function nodeDoubleClick() { if (data.treeNodeType === TreeNodeType.TABLE || data.treeNodeType === TreeNodeType.COLUMN) { dispatch({ - type: workspaceActionType.DBLCLICK_TREE_NODE, + type: 'workspace/setDoubleClickTreeNodeData', payload: data }); } else { @@ -161,8 +174,6 @@ function TreeNode(props: TreeNodeIProps) { } return show ? <> - {/* - */}
@@ -184,7 +195,7 @@ function TreeNode(props: TreeNodeIProps) {
: - + } {/* */}
@@ -214,6 +225,6 @@ function TreeNode(props: TreeNodeIProps) { }) } : <> -} +}) export default Tree; 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 3b1563c6c..412b55943 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -26,6 +26,7 @@ display: flex; align-items: center; width: 100%; + user-select: none; .currentDatabase { flex: 1; width: 0px; @@ -37,6 +38,7 @@ margin-left: 10px; } .name { + font-size: 18px; max-width: 90%; .f-single-line(); } @@ -63,10 +65,10 @@ } } -.tree{ +.tree { margin: 0px -10px; } -.left_box_title{ +.left_box_title { margin-bottom: 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 754f6d8aa..912ffeb64 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -87,17 +87,20 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { const cascaderOptions = useMemo(() => { const res = handelDatabaseAndSchema(databaseAndSchema); - const curWorkspaceParams = { - dataSourceId: curConnection?.id, - databaseSourceName: curConnection?.alias, - databaseName: res?.[0]?.value, - schemaName: res?.[0]?.children?.[0]?.value, - databaseType: curConnection?.type, - }; - dispatch({ - type: 'workspace/setCurWorkspaceParams', - payload: curWorkspaceParams, - }); + // 如果databaseAndSchema 发生切变 并且没选中确切的database时,需要默认选中第一个 + if (!curWorkspaceParams.dataSourceId) { + const curWorkspaceParams = { + dataSourceId: curConnection?.id, + databaseSourceName: curConnection?.alias, + databaseName: res?.[0]?.value, + schemaName: res?.[0]?.children?.[0]?.value, + databaseType: curConnection?.type, + }; + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: curWorkspaceParams, + }); + } return res }, [databaseAndSchema]) @@ -147,33 +150,36 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) {
-
+ {/*
-
+
*/}
); }) -const RenderTableBox = dvaModel(function RenderTableBox(props: any) { +const RenderTableBox = dvaModel(function (props: any) { const { workspaceModel } = props; const { curWorkspaceParams } = workspaceModel; const [initialData, setInitialData] = useState([]); useEffect(() => { - if (curWorkspaceParams.databaseName) { + if (curWorkspaceParams.dataSourceId) { getInitialData(); } }, [curWorkspaceParams]); function getInitialData() { + console.log(curWorkspaceParams); treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, ...curWorkspaceParams, extraParams: curWorkspaceParams, }).then((res) => { + console.log(res) + setInitialData(res); }); } @@ -188,7 +194,7 @@ const RenderTableBox = dvaModel(function RenderTableBox(props: any) { ); }) -const RenderSaveBox = dvaModel(function RenderSaveBox(props: any) { +const RenderSaveBox = dvaModel(function (props: any) { const [savedList, setSaveList] = useState([]); const { workspaceModel } = props; const { curWorkspaceParams } = workspaceModel; 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 f35b10f6f..1c0a5b2a5 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -21,9 +21,8 @@ const WorkspaceRight = memo(function (props) { const { className } = props; const [consoleList, setConsoleList] = useState(); const [activeConsoleId, setActiveConsoleId] = useState(); - const dblclickTreeNodeData = false; const { workspaceModel, dispatch } = props; - const { databaseAndSchema, curWorkspaceParams } = workspaceModel; + const { databaseAndSchema, curWorkspaceParams, doubleClickTreeNodeData } = workspaceModel; useEffect(() => { @@ -31,10 +30,10 @@ const WorkspaceRight = memo(function (props) { }, [curWorkspaceParams]); useEffect(() => { - if (!dblclickTreeNodeData) { + if (!doubleClickTreeNodeData) { return } - const { extraParams } = dblclickTreeNodeData; + const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; let flag = false; const ddl = `SELECT * FROM ${tableName};`; @@ -70,7 +69,7 @@ const WorkspaceRight = memo(function (props) { setConsoleList([...(consoleList || []), newConsole]); }); } - }, [curWorkspaceParams]); + }, [doubleClickTreeNodeData]); function getConsoleList() { let p: any = { diff --git a/chat2db-client/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts index 0856bec05..dd22abed8 100644 --- a/chat2db-client/src/utils/localStorage.ts +++ b/chat2db-client/src/utils/localStorage.ts @@ -1,5 +1,5 @@ import { ThemeType, PrimaryColorType, LangType } from '@/constants'; -import { ICurrentDatabase } from '@/pages/main/workspace/context'; +import { ICurWorkspaceParams } from '@/models/workspace'; export function getLang(): LangType { return localStorage.getItem('lang') as LangType; @@ -28,14 +28,22 @@ export function setPrimaryColor(primaryColor: PrimaryColorType) { return localStorage.setItem('primary-color', primaryColor); } -export function setCurrentWorkspaceDatabase(value: ICurrentDatabase) { +export function setCurrentWorkspaceDatabase(value: ICurWorkspaceParams) { return localStorage.setItem('current-workspace-database', JSON.stringify(value)); } -export function getCurrentWorkspaceDatabase(): ICurrentDatabase { - const currentWorkspaceDatabase = localStorage.getItem('current-workspace-database'); - if (currentWorkspaceDatabase) { - return JSON.parse(currentWorkspaceDatabase) +export function getCurrentWorkspaceDatabase(): ICurWorkspaceParams { + const curWorkspaceParams = localStorage.getItem('current-workspace-database'); + if (curWorkspaceParams) { + return JSON.parse(curWorkspaceParams) } - return {}; + return {} as ICurWorkspaceParams; } + +export function getCurConnection() { + const curConnection = localStorage.getItem('cur-connection') + if (curConnection) { + return JSON.parse(curConnection) + } + return undefined +} From 02fc8e32b647635157baeb093382d61c146a9746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Tue, 27 Jun 2023 19:19:54 +0800 Subject: [PATCH 0089/1069] feat: tab --- chat2db-client/.umirc.ts | 6 +- .../src/components/Iconfont/index.tsx | 14 ++-- chat2db-client/src/components/Tab/index.less | 63 ++++++++++++++ chat2db-client/src/components/Tab/index.tsx | 83 +++++++++++++++++++ chat2db-client/src/pages/demo/index.tsx | 66 ++++++--------- .../src/pages/main/connection/index.less | 2 +- .../components/WorkspaceLeft/index.less | 3 +- .../src/pages/main/workspace/index.less | 2 +- 8 files changed, 186 insertions(+), 53 deletions(-) create mode 100644 chat2db-client/src/components/Tab/index.less create mode 100644 chat2db-client/src/components/Tab/index.tsx diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 1219b1ad7..3fefdf91d 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -13,9 +13,9 @@ const chainWebpack = (config: any, { webpack }: any) => { export default defineConfig({ title: 'Chat2DB', - history: { - type: 'hash', - }, + // history: { + // type: 'hash', + // }, base: '/', publicPath: '/', hash: false, diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index fb19ea55c..9268788c8 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -6,13 +6,13 @@ import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font if (__ENV === 'local') { let container = ` - /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ - @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/a/font_3633546_7qftabf6y8.woff2?t=1687853414741') format('woff2'), - url('//at.alicdn.com/t/a/font_3633546_7qftabf6y8.woff?t=1687853414741') format('woff'), - url('//at.alicdn.com/t/a/font_3633546_7qftabf6y8.ttf?t=1687853414741') format('truetype'); - } + /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ + @font-face { + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/a/font_3633546_idhjlwx11v9.woff2?t=1687859839396') format('woff2'), + url('//at.alicdn.com/t/a/font_3633546_idhjlwx11v9.woff?t=1687859839396') format('woff'), + url('//at.alicdn.com/t/a/font_3633546_idhjlwx11v9.ttf?t=1687859839396') format('truetype'); + } ` let style = document.createElement("style"); style.type = "text/css"; diff --git a/chat2db-client/src/components/Tab/index.less b/chat2db-client/src/components/Tab/index.less new file mode 100644 index 000000000..ff544aab8 --- /dev/null +++ b/chat2db-client/src/components/Tab/index.less @@ -0,0 +1,63 @@ +@import '../../styles/var.less'; + +.tab { + display: flex; + padding: 4px 0px 0px 4px; +} + +.tabList { + display: flex; +} + +.tabItem { + display: flex; + align-items: center; + padding: 0px 10px; + line-height: 26px; + width: 100px; + border: 1px solid var(--color-border); + border-right: 0; + cursor: pointer; + &:nth-last-of-type(1) { + border-right: 1px solid var(--color-border); + } + .text { + flex: 1; + width: 0; + .f-single-line(); + } + .icon { + display: flex; + align-items: center; + flex-shrink: 0; + height: 20px; + margin-left: 2px; + cursor: pointer; + i { + font-size: 12px; + } + &:hover { + color: var(--color-primary); + } + } +} + +.activeTab { + border-bottom: 1px solid var(--color-primary); +} + +.rightBox { + flex: 1; +} + +.addIcon { + width: 30px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + &:hover { + color: var(--color-primary); + } +} diff --git a/chat2db-client/src/components/Tab/index.tsx b/chat2db-client/src/components/Tab/index.tsx new file mode 100644 index 000000000..72a439ced --- /dev/null +++ b/chat2db-client/src/components/Tab/index.tsx @@ -0,0 +1,83 @@ +import React, { memo, useEffect, useState } from 'react'; +import classnames from 'classnames'; +import Iconfont from '@/components/Iconfont'; +import lodash from 'lodash' +import styles from './index.less'; + +export interface IOption { + label: string; + value: string | number; +} + +export interface IOnchangeProps { + type: 'add' | 'delete' | 'switch'; + data?: IOption; +} + +interface IProps { + className?: string; + tabs: IOption[]; + onchange: (props: IOnchangeProps) => {} +} + +export default memo(function Tab(props) { + const { className, tabs, onchange } = props; + const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs)); + const [activeTab, setActiveTab] = useState(internalTabs[0]?.value); + + useEffect(() => { + setInternalTabs(lodash.cloneDeep(tabs)); + }, [tabs]) + + function deleteTab(data: IOption) { + const newTabs = internalTabs?.filter(t => t.value !== data.value); + setInternalTabs(newTabs); + onchange({ + type: 'delete', + data: data + }) + } + + function changeTab(data: IOption) { + setActiveTab(data.value); + onchange({ + type: 'switch', + data: data + }) + } + + function handelAdd() { + onchange({ + type: 'add' + }) + } + + return
+ { + !!internalTabs?.length && +
+ { + internalTabs.map(t => { + return
+
+ {t.label} +
+
+ +
+
+ }) + } +
+ } +
+
+ +
+
+
+}) diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index 3cd25df64..f13c2d48b 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -4,49 +4,35 @@ import { Button, Steps } from 'antd' import { LoadingOutlined, SmileOutlined, SolutionOutlined, UserOutlined } from '@ant-design/icons'; import styles from './index.less'; import Setting from '@/blocks/Setting'; -import LazyLoading from '@/components/Loading/LazyLoading'; +import Tab from '@/components/Tab'; export default function Demo() { + const tabs = [ + { + label: 'components/121212', + value: 1 + }, + { + label: 'components/121212components/121212components/121212', + value: 2 + }, + { + label: 'components/121212components/121212components/121212components/121212components/121212components/121212', + value: 3 + }, + { + label: 'components/121212components/121212components/121212components/121212', + value: 4 + }, + { + label: 'components/121212', + value: 5 + }, + ] return
- -
- , - }, - { - title: 'Verification', - status: 'finish', - icon: , - }, - { - title: 'Pay', - status: 'process', - icon: , - }, - { - title: 'Done', - status: 'wait', - icon: , - }, - ]} - /> -
-
- -
-
- -
-
- -
-
- -
+
} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 592bcbc06..45d92c6bd 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -11,7 +11,7 @@ flex-shrink: 0; display: flex; flex-direction: column; - width: 260px; + width: 220px; overflow: hidden; background-color: var(--color-bg-elevated); border-left: 1px solid var(--color-border); 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 412b55943..d9c47aaba 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -38,7 +38,8 @@ margin-left: 10px; } .name { - font-size: 18px; + font-size: 16px; + font-weight: bold; max-width: 90%; .f-single-line(); } diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index 9caeadbdc..70de87354 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -6,7 +6,7 @@ } .boxLeft { - width: 260px; + width: 220px; height: 100%; overflow: hidden; } From 6ddf7ab5e3474edb96c9db6536c41a4a1ff8c32f Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 27 Jun 2023 21:25:10 +0800 Subject: [PATCH 0090/1069] add azure openai support --- .../chat2db-server-web-api/pom.xml | 5 +- .../ai/azure/client/AzureOpenAIClient.java | 77 ++++++++++++ .../azure/client/AzureOpenAiStreamClient.java | 102 +++++++++++++++ .../AzureOpenAIEventSourceListener.java | 118 ++++++++++++++++++ chat2db-server/pom.xml | 5 + 5 files changed, 306 insertions(+), 1 deletion(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index 4ba575907..6373a7a11 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -34,10 +34,13 @@ com.unfbx chatgpt-java + + com.azure + azure-ai-openai + com.theokanning.openai-gpt3-java service - 0.12.0 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java new file mode 100644 index 000000000..82e18745a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java @@ -0,0 +1,77 @@ +/** + * alibaba.com Inc. + * Copyright (c) 2004-2023 All Rights Reserved. + */ +package ai.chat2db.server.web.api.controller.ai.azure.client; + +import ai.chat2db.server.domain.api.model.Config; +import ai.chat2db.server.domain.api.service.ConfigService; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import lombok.extern.slf4j.Slf4j; + +/** + * @author jipengfei + * @version : OpenAIClient.java + */ +@Slf4j +public class AzureOpenAIClient { + + public static final String AZURE_CHATGPT_API_KEY = "azure.chatgpt.apiKey"; + + /** + * OPENAI接口域名 + */ + public static final String AZURE_CHATGPT_ENDPOINT = "azure.chatgpt.endpoint"; + + private static AzureOpenAiStreamClient OPEN_AI_CLIENT; + private static String apiKey; + + public static AzureOpenAiStreamClient getInstance() { + if (OPEN_AI_CLIENT != null) { + return OPEN_AI_CLIENT; + } else { + return singleton(); + } + } + + private static AzureOpenAiStreamClient singleton() { + if (OPEN_AI_CLIENT == null) { + synchronized (AzureOpenAIClient.class) { + if (OPEN_AI_CLIENT == null) { + refresh(); + } + } + } + return OPEN_AI_CLIENT; + } + + public static void refresh() { + String apikey = ""; + String apiEndpoint = ""; + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config apiHostConfig = configService.find(AZURE_CHATGPT_ENDPOINT).getData(); + if (apiHostConfig != null) { + apiEndpoint = apiHostConfig.getContent(); + } + Config config = configService.find(AZURE_CHATGPT_API_KEY).getData(); + if (config != null) { + apikey = config.getContent(); + } + log.info("refresh azure openai apikey:{}", maskApiKey(apikey)); + OPEN_AI_CLIENT = new AzureOpenAiStreamClient(apiKey, apiEndpoint); + apiKey = apikey; + } + + private static String maskApiKey(String input) { + if (input == null) { + return input; + } + + StringBuilder maskedString = new StringBuilder(input); + for (int i = input.length() / 2; i < input.length() / 2; i++) { + maskedString.setCharAt(i, '*'); + } + return maskedString.toString(); + } + +} 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 new file mode 100644 index 000000000..2327ce71d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAiStreamClient.java @@ -0,0 +1,102 @@ +package ai.chat2db.server.web.api.controller.ai.azure.client; + +import java.util.List; +import java.util.Objects; + +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import com.azure.ai.openai.OpenAIClient; +import com.azure.ai.openai.OpenAIClientBuilder; +import com.azure.ai.openai.models.ChatChoice; +import com.azure.ai.openai.models.ChatCompletions; +import com.azure.ai.openai.models.ChatCompletionsOptions; +import com.azure.ai.openai.models.ChatMessage; +import com.azure.ai.openai.models.CompletionsUsage; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.util.IterableStream; +import com.unfbx.chatgpt.entity.chat.Message; +import lombok.extern.slf4j.Slf4j; +import okhttp3.sse.EventSourceListener; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * 自定义AI接口client + * + * @author moji + */ +@Slf4j +public class AzureOpenAiStreamClient { + + /** + * client + */ + private OpenAIClient client; + + /** + * 构造实例对象 + * + * @param apiKey + * @param endpoint + */ + public AzureOpenAiStreamClient(String apiKey, String endpoint) { + this.client = new OpenAIClientBuilder() + .credential(new AzureKeyCredential(apiKey)) + .endpoint(endpoint) + .buildClient(); + } + + /** + * 问答接口 stream 形式 + * + * @param deployId + * @param chatMessages + * @param eventSourceListener + */ + public void streamCompletions(String deployId, List chatMessages, EventSourceListener eventSourceListener) { + if (CollectionUtils.isEmpty(chatMessages)) { + log.error("参数异常:Azure Prompt不能为空"); + throw new ParamBusinessException("prompt"); + } + if (Objects.isNull(eventSourceListener)) { + log.error("参数异常:AzureEventSourceListener不能为空"); + throw new ParamBusinessException(); + } + log.info("开始调用Azure Open AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); + try { + IterableStream chatCompletionsStream = client.getChatCompletionsStream(deployId, + new ChatCompletionsOptions(chatMessages)); + + chatCompletionsStream.forEach(chatCompletions -> { + String text = ""; + System.out.printf("Model ID=%s is created at %d.%n", chatCompletions.getId(), + chatCompletions.getCreated()); + for (ChatChoice choice : chatCompletions.getChoices()) { + ChatMessage message = choice.getDelta(); + if (message != null) { + log.info("Index: {}, Chat Role: {}.%n", choice.getIndex(), message.getRole()); + text = message.getContent(); + } + } + Message message = new Message(); + if (StringUtils.isNotBlank(text)) { + message.setContent(text); + eventSourceListener.onEvent(null, "[DATA]", null, text); + } + CompletionsUsage usage = chatCompletions.getUsage(); + if (usage != null) { + log.info( + "Usage: number of prompt token is {}, number of completion token is {}, and number of total " + + "tokens in request and response is {}.%n", usage.getPromptTokens(), + usage.getCompletionTokens(), usage.getTotalTokens()); + } + }); + eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]"); + log.info("结束调用非流式输出自定义AI"); + } catch (Exception e) { + log.error("请求参数解析异常", e); + eventSourceListener.onFailure(null, e, null); + throw new ParamBusinessException(); + } + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java new file mode 100644 index 000000000..1d6dedfe1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java @@ -0,0 +1,118 @@ +package ai.chat2db.server.web.api.controller.ai.listener; + +import java.util.Objects; + +import com.azure.ai.openai.models.Completions; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.unfbx.chatgpt.entity.chat.Message; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +/** + * 描述:OpenAIEventSourceListener + * + * @author https:www.unfbx.com + * @date 2023-02-22 + */ +@Slf4j +public class AzureOpenAIEventSourceListener extends EventSourceListener { + + private SseEmitter sseEmitter; + + public AzureOpenAIEventSourceListener(SseEmitter sseEmitter) { + this.sseEmitter = sseEmitter; + } + + /** + * {@inheritDoc} + */ + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("AzureOpenAI建立sse连接..."); + } + + /** + * {@inheritDoc} + */ + @SneakyThrows + @Override + public void onEvent(EventSource eventSource, String id, String type, String data) { + log.info("AzureOpenAI返回数据:{}", data); + if (data.equals("[DONE]")) { + log.info("AzureOpenAI返回数据结束了"); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]") + .reconnectTime(3000)); + sseEmitter.complete(); + return; + } + ObjectMapper mapper = new ObjectMapper(); + // 读取Json + Completions completionResponse = mapper.readValue(data, Completions.class); + String text = completionResponse.getChoices().get(0).getText() ; + Message message = new Message(); + if (text != null) { + message.setContent(text); + sseEmitter.send(SseEmitter.event() + .id(completionResponse.getId()) + .data(message) + .reconnectTime(3000)); + } + } + + @Override + public void onClosed(EventSource eventSource) { + sseEmitter.complete(); + log.info("AzureOpenAI关闭sse连接..."); + } + + @Override + public void onFailure(EventSource eventSource, Throwable t, Response response) { + try { + if (Objects.isNull(response)) { + String message = t.getMessage(); + if ("No route to host".equals(message)) { + message = "网络连接超时,请检查网络连通性,参考文章"; + } else { + message = "Azure AI无法正常访问,请参考文章进行配置"; + } + Message sseMessage = new Message(); + sseMessage.setContent(message); + sseEmitter.send(SseEmitter.event() + .id("[ERROR]") + .data(sseMessage)); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + sseEmitter.complete(); + return; + } + ResponseBody body = response.body(); + String bodyString = null; + if (Objects.nonNull(body)) { + bodyString = body.string(); + log.error("Azure OpenAI sse连接异常data:{},异常:{}", bodyString, t); + } else { + log.error("Azure OpenAI sse连接异常data:{},异常:{}", response, t); + } + eventSource.cancel(); + Message message = new Message(); + message.setContent("Azure OpenAI出现异常,请在帮助中查看详细日志:" + bodyString); + sseEmitter.send(SseEmitter.event() + .id("[ERROR]") + .data(message)); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + sseEmitter.complete(); + } catch (Exception exception) { + log.error("Azure OpenAI发送数据异常:", exception); + } + } +} diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 6b27a8b28..094b8ad9d 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -252,6 +252,11 @@ commons-beanutils 1.9.4 + + com.azure + azure-ai-openai + 1.0.0-beta.2 + From 6d9543639cd77525929b817edb3134d7a873d4c5 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 27 Jun 2023 21:44:51 +0800 Subject: [PATCH 0091/1069] add azure openai support --- .../ai/azure/client/AzureOpenAIClient.java | 17 +++++++++++++++-- .../azure/client/AzureOpenAiStreamClient.java | 11 ++++++++--- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java index 82e18745a..96c3103d1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java @@ -16,13 +16,21 @@ @Slf4j public class AzureOpenAIClient { + /** + * AZURE OPENAI KEY + */ public static final String AZURE_CHATGPT_API_KEY = "azure.chatgpt.apiKey"; /** - * OPENAI接口域名 + * AZURE OPENAI ENDPOINT */ public static final String AZURE_CHATGPT_ENDPOINT = "azure.chatgpt.endpoint"; + /** + * AZURE OPENAI DEPLOYMENT ID + */ + public static final String AZURE_CHATGPT_DEPLOYMENT_ID = "azure.chatgpt.deployment.id"; + private static AzureOpenAiStreamClient OPEN_AI_CLIENT; private static String apiKey; @@ -48,6 +56,7 @@ private static AzureOpenAiStreamClient singleton() { public static void refresh() { String apikey = ""; String apiEndpoint = ""; + String deployId = ""; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(AZURE_CHATGPT_ENDPOINT).getData(); if (apiHostConfig != null) { @@ -57,8 +66,12 @@ public static void refresh() { if (config != null) { apikey = config.getContent(); } + Config deployConfig = configService.find(AZURE_CHATGPT_DEPLOYMENT_ID).getData(); + if (config != null) { + deployId = deployConfig.getContent(); + } log.info("refresh azure openai apikey:{}", maskApiKey(apikey)); - OPEN_AI_CLIENT = new AzureOpenAiStreamClient(apiKey, apiEndpoint); + OPEN_AI_CLIENT = new AzureOpenAiStreamClient(apiKey, apiEndpoint, deployId); apiKey = apikey; } 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 2327ce71d..ce4ff7ddf 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 @@ -27,6 +27,11 @@ @Slf4j public class AzureOpenAiStreamClient { + /** + * deployId + */ + private String deployId; + /** * client */ @@ -38,7 +43,8 @@ public class AzureOpenAiStreamClient { * @param apiKey * @param endpoint */ - public AzureOpenAiStreamClient(String apiKey, String endpoint) { + public AzureOpenAiStreamClient(String apiKey, String endpoint, String deployId) { + this.deployId = deployId; this.client = new OpenAIClientBuilder() .credential(new AzureKeyCredential(apiKey)) .endpoint(endpoint) @@ -48,11 +54,10 @@ public AzureOpenAiStreamClient(String apiKey, String endpoint) { /** * 问答接口 stream 形式 * - * @param deployId * @param chatMessages * @param eventSourceListener */ - public void streamCompletions(String deployId, List chatMessages, EventSourceListener eventSourceListener) { + public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { if (CollectionUtils.isEmpty(chatMessages)) { log.error("参数异常:Azure Prompt不能为空"); throw new ParamBusinessException("prompt"); From f08444899344d71dc3d2591469519134211ff0bb Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 27 Jun 2023 21:59:14 +0800 Subject: [PATCH 0092/1069] add azure openai support --- .../domain/api/enums/AiSqlSourceEnum.java | 13 +++++++++---- .../api/controller/config/ConfigController.java | 8 ++++---- ...gRequest.java => AISystemConfigRequest.java} | 17 ++++++++++++++++- 3 files changed, 29 insertions(+), 9 deletions(-) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/{ChatGptSystemConfigRequest.java => AISystemConfigRequest.java} (77%) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java index c577839f0..589527a15 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java @@ -13,14 +13,19 @@ @Getter public enum AiSqlSourceEnum implements BaseEnum { /** - * 使用OPENAI接口 + * OPENAI */ - OPENAI( "使用OPENAI接口"), + OPENAI( "OPENAI"), /** - * 自定义RESTAI接口 + * RESTAI */ - RESTAI("自定义RESTAI接口"), + RESTAI("RESTAI"), + + /** + * AZURE OPENAI + */ + AZUREAI("AZURE OPENAI"), ; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index 7e1c552e5..c0420d6ee 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -14,7 +14,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.config.request.ChatGptSystemConfigRequest; +import ai.chat2db.server.web.api.controller.config.request.AISystemConfigRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; import ai.chat2db.server.web.api.util.OpenAIClient; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; @@ -58,7 +58,7 @@ public ActionResult systemConfig(@RequestBody SystemConfigRequest request) { * @return */ @PostMapping("/system_config/chatgpt") - public ActionResult addChatGptSystemConfig(@RequestBody ChatGptSystemConfigRequest request) { + public ActionResult addChatGptSystemConfig(@RequestBody AISystemConfigRequest request) { String sqlSource = StringUtils.isNotBlank(request.getAiSqlSource()) ? request.getAiSqlSource() : AiSqlSourceEnum.OPENAI.getCode(); SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource) @@ -77,7 +77,7 @@ public ActionResult addChatGptSystemConfig(@RequestBody ChatGptSystemConfigReque * * @param request */ - private void saveOpenAIConfig(ChatGptSystemConfigRequest request) { + private void saveOpenAIConfig(AISystemConfigRequest request) { SystemConfigParam param = SystemConfigParam.builder().code(OpenAIClient.OPENAI_KEY).content( request.getApiKey()) .build(); @@ -100,7 +100,7 @@ private void saveOpenAIConfig(ChatGptSystemConfigRequest request) { * * @param request */ - private void saveRestAIConfig(ChatGptSystemConfigRequest request) { + private void saveRestAIConfig(AISystemConfigRequest request) { SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content( request.getRestAiUrl()) .build(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ChatGptSystemConfigRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java similarity index 77% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ChatGptSystemConfigRequest.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java index 583fc25e3..a5e22d04f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/ChatGptSystemConfigRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java @@ -13,7 +13,7 @@ * @version : SystemConfigRequest.java */ @Data -public class ChatGptSystemConfigRequest { +public class AISystemConfigRequest { /** * OpenAi APIKEY @@ -56,4 +56,19 @@ public class ChatGptSystemConfigRequest { * 非必填,默认值为TRUE */ private Boolean restAiStream = Boolean.TRUE; + + /** + * Get Azure OpenAI key credential from the Azure Portal + */ + private String azureApiKey; + + /** + * Get Azure OpenAI endpoint from the Azure Portal + */ + private String azureEndpoint; + + /** + * deployment id of the deployed model + */ + private String azureDeploymentId; } From 8a8689ece42ad46c5629288cfe7a76f74fd7ec74 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 27 Jun 2023 22:14:59 +0800 Subject: [PATCH 0093/1069] style: tabs --- chat2db-client/.vscode/settings.json | 11 ++- .../components/Console/ChatInput/index.less | 3 +- chat2db-client/src/components/Tab/index.less | 63 ------------ chat2db-client/src/components/Tab/index.tsx | 83 ---------------- chat2db-client/src/components/Tabs/index.less | 96 +++++++++++-------- chat2db-client/src/components/Tabs/index.tsx | 90 ++++++++++++----- chat2db-client/src/pages/demo/index.tsx | 30 +----- .../components/WorkspaceRight/index.less | 5 +- .../components/WorkspaceRight/index.tsx | 26 +++-- 9 files changed, 144 insertions(+), 263 deletions(-) delete mode 100644 chat2db-client/src/components/Tab/index.less delete mode 100644 chat2db-client/src/components/Tab/index.tsx diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index a6c83691f..a3dbc1f8d 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -15,18 +15,19 @@ }, "git.mergeEditor": false, "cSpell.words": [ - "antd", "Appstore", - "Cascader", "CLICKHOUSE", - "datas", - "datasource", - "dbhub", + "Cascader", "Iconfont", "JDBC", "KEYPAIR", "SQLSERVER", "USERANDPASSWORD", + "antd", + "chatgpt", + "datas", + "datasource", + "dbhub", "uuidv", "wireframe" ] diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 0ff2cef77..61d7d18a8 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -1,8 +1,7 @@ .chat_wrapper { - height: 56px; display: flex; align-items: center; - padding: 20px; + padding: 12px 20px; box-sizing: border-box; border-bottom: 1px solid var(--color-border); } diff --git a/chat2db-client/src/components/Tab/index.less b/chat2db-client/src/components/Tab/index.less deleted file mode 100644 index ff544aab8..000000000 --- a/chat2db-client/src/components/Tab/index.less +++ /dev/null @@ -1,63 +0,0 @@ -@import '../../styles/var.less'; - -.tab { - display: flex; - padding: 4px 0px 0px 4px; -} - -.tabList { - display: flex; -} - -.tabItem { - display: flex; - align-items: center; - padding: 0px 10px; - line-height: 26px; - width: 100px; - border: 1px solid var(--color-border); - border-right: 0; - cursor: pointer; - &:nth-last-of-type(1) { - border-right: 1px solid var(--color-border); - } - .text { - flex: 1; - width: 0; - .f-single-line(); - } - .icon { - display: flex; - align-items: center; - flex-shrink: 0; - height: 20px; - margin-left: 2px; - cursor: pointer; - i { - font-size: 12px; - } - &:hover { - color: var(--color-primary); - } - } -} - -.activeTab { - border-bottom: 1px solid var(--color-primary); -} - -.rightBox { - flex: 1; -} - -.addIcon { - width: 30px; - height: 100%; - display: flex; - justify-content: center; - align-items: center; - cursor: pointer; - &:hover { - color: var(--color-primary); - } -} diff --git a/chat2db-client/src/components/Tab/index.tsx b/chat2db-client/src/components/Tab/index.tsx deleted file mode 100644 index 72a439ced..000000000 --- a/chat2db-client/src/components/Tab/index.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React, { memo, useEffect, useState } from 'react'; -import classnames from 'classnames'; -import Iconfont from '@/components/Iconfont'; -import lodash from 'lodash' -import styles from './index.less'; - -export interface IOption { - label: string; - value: string | number; -} - -export interface IOnchangeProps { - type: 'add' | 'delete' | 'switch'; - data?: IOption; -} - -interface IProps { - className?: string; - tabs: IOption[]; - onchange: (props: IOnchangeProps) => {} -} - -export default memo(function Tab(props) { - const { className, tabs, onchange } = props; - const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs)); - const [activeTab, setActiveTab] = useState(internalTabs[0]?.value); - - useEffect(() => { - setInternalTabs(lodash.cloneDeep(tabs)); - }, [tabs]) - - function deleteTab(data: IOption) { - const newTabs = internalTabs?.filter(t => t.value !== data.value); - setInternalTabs(newTabs); - onchange({ - type: 'delete', - data: data - }) - } - - function changeTab(data: IOption) { - setActiveTab(data.value); - onchange({ - type: 'switch', - data: data - }) - } - - function handelAdd() { - onchange({ - type: 'add' - }) - } - - return
- { - !!internalTabs?.length && -
- { - internalTabs.map(t => { - return
-
- {t.label} -
-
- -
-
- }) - } -
- } -
-
- -
-
-
-}) diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 0f94b45dd..cf89f4438 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -1,50 +1,62 @@ -// @import '../../var.less'; +@import '../../styles/var.less'; -.box{ +.tab { display: flex; - position: relative; - .extra{ - flex: 1; +} + +.tabList { + display: flex; +} + +.tabItem { + display: flex; + align-items: center; + padding: 0px 10px; + line-height: 26px; + width: 100px; + border: 1px solid var(--color-border); + border-right: 0; + cursor: pointer; + &:nth-last-of-type(1) { + border-right: 1px solid var(--color-border); } - &::after{ - position: absolute; - content: ''; - bottom: 1px; - left: 0; - width: 100%; - height: 1px; - background-color: var(--color-border); + .text { + flex: 1; + width: 0; + .f-single-line(); } - :global { - .custom-tabs{ - width: 100%; - flex-shrink: 0; - } - .custom-tabs-tab { - margin: 0px 10px; + .icon { + display: flex; + align-items: center; + flex-shrink: 0; + height: 20px; + margin-left: 2px; + cursor: pointer; + i { font-size: 12px; } - .custom-tabs-nav{ - margin: 0 !important; - } - .custom-tabs-nav::before{ - border: 0; - border-bottom: 0 !important; - } - .custom-tabs-tab{ - user-select: none; - padding: 5px 10px; - margin: 0px 0px 5px 0px; - border-radius: 5px; - &:hover{ - color: var(--color-text-85); - background-color: var(--color-bg-hover); - } - } - .custom-tabs-tab-active{ - &:hover{ - background-color: transparent; - } + &:hover { + color: var(--color-primary); } } -} \ No newline at end of file +} + +.activeTab { + border-bottom: 1px solid var(--color-primary); +} + +.rightBox { + flex: 1; +} + +.addIcon { + width: 30px; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + &:hover { + color: var(--color-primary); + } +} diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 9f6cbd180..4f1f46a5f 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -1,37 +1,77 @@ -import React, { memo, ReactNode, useState } from 'react'; -import styles from './index.less'; +import React, { memo, useEffect, useState } from 'react'; import classnames from 'classnames'; -import { Tabs as AntdTabs } from 'antd'; +import Iconfont from '@/components/Iconfont'; +import lodash from 'lodash' +import styles from './index.less'; + +export interface IOption { + label: string; + value: number; +} -export interface ITab { - label: ReactNode; - key: string; +export interface IOnchangeProps { + type: 'add' | 'delete' | 'switch'; + data?: IOption; } interface IProps { className?: string; - tabs: ITab[]; - currentTab?: string; - onChange: (key: string, index: number) => void; - extra?: React.ReactNode + tabs: IOption[]; + onChange: (key: IOption['value']) => void; + onEdit: (action: 'add' | 'remove', key?: IOption['value']) => void; } -export default memo(function Tabs({ className, tabs, currentTab, onChange, extra }: IProps) { - function myChange(key: string) { - const index = tabs.findIndex(t => { - return t.key === key - }) - onChange(key, index) +export default memo(function Tab(props) { + const { className, tabs, onChange, onEdit } = props; + const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs)); + const [activeTab, setActiveTab] = useState(internalTabs[0]?.value); + + useEffect(() => { + setInternalTabs(lodash.cloneDeep(tabs)); + }, [tabs]) + + function deleteTab(data: IOption) { + const newTabs = internalTabs?.filter(t => t.value !== data.value); + setInternalTabs(newTabs); + onEdit('remove', data.value) + } + + function changeTab(data: IOption) { + setActiveTab(data.value); + onChange(data.value) + + } + + function handelAdd() { + onEdit('add') } - return
- -
- {extra} + return
+ { + !!internalTabs?.length && +
+ { + internalTabs.map(t => { + return
+
+ {t.label} +
+
+ +
+
+ }) + } +
+ } +
+
+ +
-
+
}) diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index f13c2d48b..f41753b60 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -4,35 +4,11 @@ import { Button, Steps } from 'antd' import { LoadingOutlined, SmileOutlined, SolutionOutlined, UserOutlined } from '@ant-design/icons'; import styles from './index.less'; import Setting from '@/blocks/Setting'; -import Tab from '@/components/Tab'; +import Tabs from '@/components/Tabs'; export default function Demo() { - const tabs = [ - { - label: 'components/121212', - value: 1 - }, - { - label: 'components/121212components/121212components/121212', - value: 2 - }, - { - label: 'components/121212components/121212components/121212components/121212components/121212components/121212', - value: 3 - }, - { - label: 'components/121212components/121212components/121212components/121212', - value: 4 - }, - { - label: 'components/121212', - value: 5 - }, - ] - return
- + return
+
} \ No newline at end of file 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 fe90a277b..00c15d9a8 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -16,7 +16,7 @@ .consoleBox { position: absolute; - top: 40px; + top: 35px; left: 0; right: 0; bottom: 0; @@ -24,7 +24,8 @@ } .tabBox { - padding: 10px 10px 0px; + padding: 6px 6px 0px; + border-bottom: 1px solid var(--color-border); } .activeConsoleBox { 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 1c0a5b2a5..384c1525d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -5,7 +5,7 @@ import classnames from 'classnames'; import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment, DatabaseTypeCode } from '@/constants'; import { IConsole } from '@/typings'; import historyService from '@/service/history'; -import { Tabs } from 'antd'; +import Tabs from '@/components/Tabs'; import LoadingContent from '@/components/Loading/LoadingContent'; import WorkspaceRightItem from '../WorkspaceRightItem'; @@ -152,27 +152,27 @@ const WorkspaceRight = memo(function (props) { }); } - function onChange(key: string) { + function onChange(key: number | string) { setActiveConsoleId(+key); } - const onEdit = (targetKey: any, action: 'add' | 'remove') => { + const onEdit = (action: 'add' | 'remove', key?: number) => { if (action === 'remove') { - closeWindowTab(targetKey); + closeWindowTab(key!); } }; - const closeWindowTab = (targetKey: string) => { + const closeWindowTab = (key: number) => { let newActiveKey = activeConsoleId; let lastIndex = -1; consoleList?.forEach((item, i) => { - if (item.id === +targetKey) { + if (item.id === key) { lastIndex = i - 1; } }); - const newPanes = consoleList?.filter((item) => item.id !== +targetKey) || []; - if (newPanes.length && newActiveKey === +targetKey) { + const newPanes = consoleList?.filter((item) => item.id !== key) || []; + if (newPanes.length && newActiveKey === key) { if (lastIndex >= 0) { newActiveKey = newPanes[lastIndex].id; } else { @@ -183,11 +183,11 @@ const WorkspaceRight = memo(function (props) { setActiveConsoleId(newActiveKey); let p: any = { - id: targetKey, + id: key, tabOpened: 'n', }; - const window = consoleList?.find((t) => t.id === +targetKey); + const window = consoleList?.find((t) => t.id === key); if (!window?.status) { return; } @@ -209,14 +209,12 @@ const WorkspaceRight = memo(function (props) {
{ + tabs={(consoleList || [])?.map((t, i) => { return { label: t.name, - key: t.id + '', + value: t.id, }; })} /> From 12e9ac0093e1048703c66e0fce442f55d7d510f9 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 27 Jun 2023 22:27:37 +0800 Subject: [PATCH 0094/1069] add azure openai support --- .../web/api/controller/ai/ChatController.java | 74 +++++++++++++++---- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index f6eac6c9a..b10c1a832 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -16,6 +16,8 @@ import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; +import ai.chat2db.server.web.api.controller.ai.listener.AzureOpenAIEventSourceListener; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; @@ -34,6 +36,8 @@ import ai.chat2db.server.web.api.util.OpenAIClient; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; +import com.azure.ai.openai.models.ChatMessage; +import com.azure.ai.openai.models.ChatRole; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.unfbx.chatgpt.entity.chat.Message; @@ -177,10 +181,7 @@ public SseEmitter chat(@RequestParam("message") String msg, @RequestHeader Map messages = new ArrayList<>(); + if (StrUtil.isNotBlank(messageContext)) { + messages = JSONUtil.toList(messageContext, ChatMessage.class); + if (messages.size() >= contextLength) { + messages = messages.subList(1, contextLength); + } + } + ChatMessage currentMessage = new ChatMessage(ChatRole.USER).setContent(msg); + messages.add(currentMessage); + + sseEmitter.send(SseEmitter.event().id(uid).name("sseEmitter connected!!!!").data(LocalDateTime.now()).reconnectTime(3000)); + sseEmitter.onCompletion(() -> { + log.info(LocalDateTime.now() + ", uid#" + uid + ", sseEmitter on completion"); + }); + sseEmitter.onTimeout( + () -> log.info(LocalDateTime.now() + ", uid#" + uid + ", sseEmitter on timeout#" + sseEmitter.getTimeout())); + sseEmitter.onError( + throwable -> { + try { + log.info(LocalDateTime.now() + ", uid#" + "765431" + ", sseEmitter on error#" + throwable.toString()); + sseEmitter.send(SseEmitter.event().id("765431").name("exception occurs!").data(throwable.getMessage()) + .reconnectTime(3000)); + } catch (IOException e) { + e.printStackTrace(); + } + } + ); + AzureOpenAIEventSourceListener sourceListener = new AzureOpenAIEventSourceListener(sseEmitter); + AzureOpenAIClient.getInstance().streamCompletions(messages, sourceListener); + LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); + return sseEmitter; + } + /** * 使用GPT3.5模型 * From e3ee4cbba7a5f36d54c80c351723bf65a61e0838 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 27 Jun 2023 22:59:33 +0800 Subject: [PATCH 0095/1069] style:saved --- .../components/Console/ChatInput/index.less | 2 +- .../src/components/Console/index.less | 24 +++++++++++++++++-- .../src/components/Console/index.tsx | 8 ++++--- .../src/components/Iconfont/index.tsx | 8 +++---- chat2db-client/src/components/Tabs/index.less | 22 ++++++++++++++++- .../src/pages/main/connection/index.less | 1 - .../components/WorkspaceLeft/index.less | 20 +++++++++++++++- .../components/WorkspaceLeft/index.tsx | 6 ++--- .../components/WorkspaceRight/index.tsx | 20 ++++++++++++++++ chat2db-client/src/typings/common.ts | 4 ++-- 10 files changed, 97 insertions(+), 18 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 61d7d18a8..8069eb37a 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -9,5 +9,5 @@ .chat_ai { width: 16px; height: 16px; - margin-right: 20px; + margin-right: 14px; } \ No newline at end of file diff --git a/chat2db-client/src/components/Console/index.less b/chat2db-client/src/components/Console/index.less index a3933d2b9..6b8365f08 100644 --- a/chat2db-client/src/components/Console/index.less +++ b/chat2db-client/src/components/Console/index.less @@ -23,7 +23,6 @@ } .console_options_wrapper { - position: absolute; bottom: 8px; left: 0; @@ -33,4 +32,25 @@ margin: 0 36px; justify-content: space-between; align-items: center; -} \ No newline at end of file +} + +.console_options_left{ + display: flex; + align-items: center; +} + +.run_button{ + display: flex; + width: 70px; + align-items: center; + margin-right: 20px; + height: 28px; + i{ + margin-right: 4px; + } +} + +.save_button{ + width: 70px; + height: 28px; +} diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 710810225..483f82c54 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -14,6 +14,7 @@ import { v4 as uuidv4 } from 'uuid'; import styles from './index.less'; import Loading from '../Loading/Loading'; import { DatabaseTypeCode } from '@/constants'; +import Iconfont from '../Iconfont'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -185,11 +186,12 @@ function Console(props: IProps) {
-
- -
diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 9268788c8..f90ce713b 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/a/font_3633546_idhjlwx11v9.woff2?t=1687859839396') format('woff2'), - url('//at.alicdn.com/t/a/font_3633546_idhjlwx11v9.woff?t=1687859839396') format('woff'), - url('//at.alicdn.com/t/a/font_3633546_idhjlwx11v9.ttf?t=1687859839396') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_cvbwebl7aa.woff2?t=1687877639500') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_cvbwebl7aa.woff?t=1687877639500') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_cvbwebl7aa.ttf?t=1687877639500') format('truetype'); } ` let style = document.createElement("style"); @@ -24,7 +24,7 @@ export default class Iconfont extends PureComponent< { code: string; } & React.DetailedHTMLProps, HTMLElement> -> { + > { render() { return ( diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index cf89f4438..db4da1f42 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -1,5 +1,17 @@ @import '../../styles/var.less'; +.tab-focus(){ + &::after{ + position: absolute; + content: ''; + left: 0; + right: 0; + bottom: -1px; + height: 2px; + background-color: var(--color-primary); + } +} + .tab { display: flex; } @@ -9,6 +21,7 @@ } .tabItem { + position: relative; display: flex; align-items: center; padding: 0px 10px; @@ -16,10 +29,17 @@ width: 100px; border: 1px solid var(--color-border); border-right: 0; + border-bottom: 0; cursor: pointer; &:nth-last-of-type(1) { border-right: 1px solid var(--color-border); } + &:hover{ + .tab-focus(); + .text{ + color: var(--color-primary); + } + } .text { flex: 1; width: 0; @@ -42,7 +62,7 @@ } .activeTab { - border-bottom: 1px solid var(--color-primary); + .tab-focus(); } .rightBox { diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 45d92c6bd..29f292208 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -64,7 +64,6 @@ &:hover { background-color: var(--color-hover-bg); - border: var(--border-radius); .moreButton { display: block; } 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 d9c47aaba..348367bb2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -66,10 +66,28 @@ } } -.tree { +.tree,.saveBoxList { margin: 0px -10px; } +.saveItem{ + cursor: pointer; + padding: 0px 10px; + margin-bottom: 2px; + line-height: 26px; + border-radius: 2px; + user-select: none; + + + &:hover { + background-color: var(--color-hover-bg); + color: var(--color-primary); + .moreButton { + display: block; + } + } +} + .left_box_title { margin-bottom: 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 912ffeb64..de176c678 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -32,7 +32,7 @@ const WorkspaceLeft = memo(function (props) {
- +
); @@ -217,11 +217,11 @@ const RenderSaveBox = dvaModel(function (props: any) { return
Saved
-
+
{ savedList?.map(t => { - return
+ return
{t.name}
}) 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 384c1525d..dc8eba4c5 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -160,8 +160,28 @@ const WorkspaceRight = memo(function (props) { if (action === 'remove') { closeWindowTab(key!); } + if (action === 'add') { + addConsole(); + } }; + const addConsole = () => { + const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams + let p = { + name: `new console${consoleList?.length}`, + ddl: consoleTopComment, + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName: schemaName!, + type: databaseType, + status: ConsoleStatus.DRAFT, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + } + historyService.saveConsole(p).then(res => { + getConsoleList() + }) + } + const closeWindowTab = (key: number) => { let newActiveKey = activeConsoleId; let lastIndex = -1; diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 62a6c41a9..c918ac420 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -23,10 +23,10 @@ export interface IConsole { dataSourceName: string; schemaName: string; type: DatabaseTypeCode; - status: string; + status: ConsoleStatus; connectable: boolean; tabOpened?: ConsoleOpenedStatus; } -export type ICreateConsole = Omit +export type ICreateConsole = Omit From 927cdad6db72424aa6fbe710b3413eadec4fde33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Wed, 28 Jun 2023 10:13:34 +0800 Subject: [PATCH 0096/1069] fix: key warning --- chat2db-client/index.d.ts | 3 +++ .../components/Console/MonacoEditor/index.tsx | 1 + .../src/components/Console/index.tsx | 4 +--- chat2db-client/src/components/Tabs/index.tsx | 2 +- .../src/pages/main/connection/index.tsx | 4 ++-- chat2db-client/src/pages/main/index.tsx | 24 +++++++++---------- .../components/WorkspaceLeft/index.tsx | 2 +- .../components/WorkspaceRight/index.tsx | 2 +- .../src/pages/main/workspace/index.tsx | 2 +- 9 files changed, 23 insertions(+), 21 deletions(-) create mode 100644 chat2db-client/index.d.ts diff --git a/chat2db-client/index.d.ts b/chat2db-client/index.d.ts new file mode 100644 index 000000000..17cf61f0b --- /dev/null +++ b/chat2db-client/index.d.ts @@ -0,0 +1,3 @@ +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'; diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 71fbc478a..c6a9e2b0b 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -252,6 +252,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }); }); }; + return
; } diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 483f82c54..505014222 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -8,7 +8,6 @@ import { format } from 'sql-formatter'; import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import MonacoEditor from 'react-monaco-editor'; -import { useReducerContext } from '@/pages/main/workspace/index'; import { v4 as uuidv4 } from 'uuid'; import styles from './index.less'; @@ -57,8 +56,6 @@ function Console(props: IProps) { const editorRef = useRef(); const [context, setContext] = useState(); const [isLoading, setIsLoading] = useState(false); - const { state, dispatch } = useReducerContext(); - const { currentWorkspaceData } = state; useEffect(() => { setContext(value); @@ -116,6 +113,7 @@ function Console(props: IProps) { sql: sqlContent, ...executeParams, }; + sqlServer .executeSql(p) .then((res) => { diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 4f1f46a5f..c21bc0ec8 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -1,7 +1,7 @@ import React, { memo, useEffect, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; -import lodash from 'lodash' +import lodash from 'lodash'; import styles from './index.less'; export interface IOption { diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 0923dc24d..708858601 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -170,8 +170,8 @@ function Connections(props: IProps) {
); })} - {Array.from({ length: 20 }).map((t) => { - return
; + {Array.from({ length: 20 }).map((t, index) => { + return
; })}
)} diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index e1f9b75f7..cb19ae00d 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -25,18 +25,18 @@ const navConfig: INavItem[] = [ iconFontSize: 16, component: , }, - { - key: 'dashboard', - icon: '\ue629', - iconFontSize: 24, - component: , - }, - { - key: 'connections', - icon: '\ue622', - iconFontSize: 20, - component: , - }, + // { + // key: 'dashboard', + // icon: '\ue629', + // iconFontSize: 24, + // component: , + // }, + // { + // key: 'connections', + // icon: '\ue622', + // iconFontSize: 20, + // component: , + // }, // { // key: 'github', // icon: '\ue885', 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 de176c678..f8a784060 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -221,7 +221,7 @@ const RenderSaveBox = dvaModel(function (props: any) { { savedList?.map(t => { - return
+ return
{t.name}
}) 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 dc8eba4c5..1d56bd48c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -240,7 +240,7 @@ const WorkspaceRight = memo(function (props) { />
{consoleList?.map((t, index) => { - return
+ return
({ state: initState, - dispatch: () => {}, + dispatch: () => { }, }); export const useReducerContext = () => { From 01653d94c13cb68b09822d47f229b5e17ce281e7 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Wed, 28 Jun 2023 17:01:44 +0800 Subject: [PATCH 0097/1069] feat: code optimize --- .vscode/settings.json | 1 + chat2db-client/src/blocks/Setting/index.less | 1 + chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/en-us/dashboard.ts | 10 +- chat2db-client/src/i18n/zh-cn/common.ts | 13 +- chat2db-client/src/i18n/zh-cn/dashboard.ts | 3 + chat2db-client/src/models/database.ts | 48 ------- chat2db-client/src/models/workspace.ts | 24 ++-- .../src/pages/main/connection/index.tsx | 29 +++-- .../pages/main/dashboard/chart-item/index.tsx | 4 +- .../main/dashboard/echart-test/index.tsx | 119 ------------------ .../src/pages/main/dashboard/index.less | 30 +++-- .../src/pages/main/dashboard/index.tsx | 50 ++++---- .../{echart-test => left-block}/index.less | 0 .../pages/main/dashboard/left-block/index.tsx | 0 chat2db-client/src/pages/main/index.tsx | 39 +++--- .../components/WorkspaceLeft/index.tsx | 4 +- chat2db-client/src/typings/main.ts | 2 +- 18 files changed, 119 insertions(+), 259 deletions(-) delete mode 100644 chat2db-client/src/models/database.ts delete mode 100644 chat2db-client/src/pages/main/dashboard/echart-test/index.tsx rename chat2db-client/src/pages/main/dashboard/{echart-test => left-block}/index.less (100%) create mode 100644 chat2db-client/src/pages/main/dashboard/left-block/index.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 47ce6195c..228577777 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "cascader", "Dserver", "Dspring", + "echart", "echarts", "favicons", "iconfont", diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index c8f0a6649..2b7c220e6 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -97,6 +97,7 @@ margin: -16px; height: 70vh; overflow-y: auto; + border-radius: var(--border-radius-l-g); } .menus { diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index f76f9891d..4af3da0e1 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -3,6 +3,7 @@ export default { 'common.text.is': 'no', 'common.button.affirm': 'Affirm', 'common.button.edit': 'Edit', + 'common.button.confirm': 'Confirm', 'common.button.cancel': 'Cancel', 'common.data.hour': '{1} {hour|hours}', 'common.data.minute': '{1} {minute|minutes}', diff --git a/chat2db-client/src/i18n/en-us/dashboard.ts b/chat2db-client/src/i18n/en-us/dashboard.ts index 097a586de..46fe2214f 100644 --- a/chat2db-client/src/i18n/en-us/dashboard.ts +++ b/chat2db-client/src/i18n/en-us/dashboard.ts @@ -1,5 +1,9 @@ export default { - 'dashboard.title': 'dashboard', - 'dashboard.edit': "Edit", - 'dashboard.delete': "Delete", + 'dashboard.title': 'Dashboard', + 'dashboard.edit': 'Edit', + 'dashboard.modal.editTitle': 'Edit Dashboard', + 'dashboard.modal.addTitle': 'Add Dashboard', + 'dashboard.modal.name.placeholder': "Please enter dashboard's name.", + 'dashboard.delete': 'Delete', + }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index de2d86aa5..1b830c45f 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -3,6 +3,7 @@ export default { 'common.text.is': '是', 'common.button.affirm': '确认', 'common.button.edit': '修改', + 'common.button.confirm': '确认', 'common.button.cancel': '取消', 'common.data.hour': '{1}小时', 'common.data.minute': '{1}分钟', @@ -21,10 +22,10 @@ export default { 'common.button.save': '保存', 'common.button.取消execute': '执行', 'common.message.successfulConfig': '配置成功', - 'common.text.successful':'成功', - 'common.text.failure':'失败', - 'common.message.modifySuccessfully':'修改成功', + 'common.text.successful': '成功', + 'common.text.failure': '失败', + 'common.message.modifySuccessfully': '修改成功', 'common.message.addedSuccessfully': '添加成功', - 'common.text.custom':'自定义', - 'common.button.delete':'删除' -} \ No newline at end of file + 'common.text.custom': '自定义', + 'common.button.delete': '删除', +}; diff --git a/chat2db-client/src/i18n/zh-cn/dashboard.ts b/chat2db-client/src/i18n/zh-cn/dashboard.ts index d7dcd6771..015fad528 100644 --- a/chat2db-client/src/i18n/zh-cn/dashboard.ts +++ b/chat2db-client/src/i18n/zh-cn/dashboard.ts @@ -1,5 +1,8 @@ export default { 'dashboard.title': '仪表盘', 'dashboard.edit': '编辑', + 'dashboard.modal.editTitle': '编辑仪表盘', + 'dashboard.modal.addTitle': '新增仪表盘', + 'dashboard.modal.name.placeholder': "请输入仪表盘名", 'dashboard.delete': '删除', }; diff --git a/chat2db-client/src/models/database.ts b/chat2db-client/src/models/database.ts deleted file mode 100644 index 386eec107..000000000 --- a/chat2db-client/src/models/database.ts +++ /dev/null @@ -1,48 +0,0 @@ -import sqlService,{MetaSchemaVO} from '@/service/sql'; -import { Effect, Reducer } from 'umi'; - -interface IState { - databaseAndSchema: MetaSchemaVO -} - -export interface DatabaseModelType { - namespace: 'database'; - state: IState; - reducers: { - setDatabaseAndSchema: Reducer; - }; - effects: { - fetchdatabaseAndSchema: Effect; - }; -} - -const DatabaseModel:DatabaseModelType = { - namespace: 'database', - state: { - databaseAndSchema: {} , - }, - - reducers: { - // 设置 database schema 数据 - setDatabaseAndSchema(state, { payload }) { - return { - ...state, - databaseAndSchema: payload, - }; - }, - }, - - effects: { - *fetchdatabaseAndSchema(p, action) { - const { call, put } = action - console.log(p,action) - const res = (yield sqlService.getDatabaseSchemaList({ dataSourceId: 2 })) - yield put({ - type: 'setDatabaseAndSchema', - payload: res, - }); - }, - }, -}; - -export default DatabaseModel; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 2b89d9706..d1708696f 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -2,7 +2,7 @@ import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/util import sqlService, { MetaSchemaVO } from '@/service/sql'; import { DatabaseTypeCode } from '@/constants'; import { Effect, Reducer } from 'umi'; -import { ITreeNode } from '@/typings' +import { ITreeNode } from '@/typings'; export type ICurWorkspaceParams = { dataSourceId: number; @@ -10,7 +10,7 @@ export type ICurWorkspaceParams = { databaseType: DatabaseTypeCode; databaseName?: string; schemaName?: string; -} +}; export interface IState { // 当前连接下的及联databaseAndSchema数据 @@ -21,20 +21,20 @@ export interface IState { doubleClickTreeNodeData: ITreeNode | undefined; } -export interface IWorkspaceModelType { - namespace: 'workspace', - state: IState, +export interface IWorkspaceModelType { + namespace: 'workspace'; + state: IState; reducers: { setDatabaseAndSchema: Reducer; setCurWorkspaceParams: Reducer; setDoubleClickTreeNodeData: Reducer; //TS TODO: }; effects: { - fetchdatabaseAndSchema: Effect; + fetchDatabaseAndSchema: Effect; }; } -const WorkspaceModel:IWorkspaceModelType = { +const WorkspaceModel: IWorkspaceModelType = { namespace: 'workspace', state: { @@ -69,10 +69,10 @@ const WorkspaceModel:IWorkspaceModelType = { }, effects: { - *fetchdatabaseAndSchema(p, action) { - const { call, put } = action - console.log(p,action) - const res = (yield sqlService.getDatabaseSchemaList({ dataSourceId: 2 })) + *fetchDatabaseAndSchema({ payload }, action) { + const { put } = action; + // ts-ignore + const res = yield sqlService.getDatabaseSchemaList(payload); yield put({ type: 'setDatabaseAndSchema', payload: res, @@ -81,4 +81,4 @@ const WorkspaceModel:IWorkspaceModelType = { }, }; -export default WorkspaceModel +export default WorkspaceModel; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 708858601..03bdd2b64 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -4,9 +4,7 @@ import i18n from '@/i18n'; import CreateConnection from '@/components/CreateConnection'; import Iconfont from '@/components/Iconfont'; import connectionService from '@/service/connection'; - import { DatabaseTypeCode, databaseMap, databaseTypeList } from '@/constants'; - import { IDatabase, IConnectionDetails } from '@/typings'; import { Button, Dropdown, Modal } from 'antd'; import { MoreOutlined } from '@ant-design/icons'; @@ -62,7 +60,7 @@ function Connections(props: IProps) { [connectionList], ); - const menuItemDoubleClick = (t: any) => { + const handleMenuItemDoubleClick = (t?: any) => { dispatch({ type: 'connection/setCurConnection', payload: t.meta, @@ -70,9 +68,9 @@ function Connections(props: IProps) { dispatch({ type: 'mainPage/updateCurPage', - payload: 'workspace' - }) - } + payload: 'workspace', + }); + }; const renderMenu = () => { return ( @@ -85,7 +83,7 @@ function Connections(props: IProps) { className={classnames(styles.menuItem, { [styles.menuItemActive]: curConnection.id === key, })} - onDoubleClick={menuItemDoubleClick.bind(null, t)} + onDoubleClick={handleMenuItemDoubleClick.bind(null, t)} onClick={() => { setCurConnection(t.meta); }} @@ -97,6 +95,13 @@ function Connections(props: IProps) { { + handleMenuItemDoubleClick(t); + }, + }, { key: 'Delete', label: i18n('common.button.delete'), @@ -180,7 +185,9 @@ function Connections(props: IProps) { ); } -export default connect(({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ - connectionModel: connection, - workspaceModel: workspace, -}))(Connections); +export default connect( + ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ + connectionModel: connection, + workspaceModel: workspace, + }), +)(Connections); 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 2cd2509ba..ac618ec5f 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -179,7 +179,7 @@ function ChartItem(props: IChartItemProps) { return (
- + /> */}
{ - const getOption = () => { - const option: EChartsOption = { - title: { - text: 'Stacked Area Chart', - }, - tooltip: { - trigger: 'axis', - axisPointer: { - type: 'cross', - label: { - backgroundColor: '#6a7985', - }, - }, - }, - legend: { - data: ['Email', 'Union Ads', 'Video Ads', 'Direct', 'Search Engine'], - }, - toolbox: { - feature: { - saveAsImage: {}, - }, - }, - grid: { - left: '3%', - right: '4%', - bottom: '3%', - containLabel: true, - }, - xAxis: [ - { - type: 'category', - boundaryGap: false, - data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], - }, - ], - yAxis: [ - { - type: 'value', - }, - ], - series: [ - { - name: 'Email', - type: 'line', - stack: 'Total', - areaStyle: {}, - emphasis: { - focus: 'series', - }, - data: [120, 132, 101, 134, 90, 230, 210], - }, - { - name: 'Union Ads', - type: 'line', - stack: 'Total', - areaStyle: {}, - emphasis: { - focus: 'series', - }, - data: [220, 182, 191, 234, 290, 330, 310], - }, - { - name: 'Video Ads', - type: 'line', - stack: 'Total', - areaStyle: {}, - emphasis: { - focus: 'series', - }, - data: [150, 232, 201, 154, 190, 330, 410], - }, - { - name: 'Direct', - type: 'line', - stack: 'Total', - areaStyle: {}, - emphasis: { - focus: 'series', - }, - data: [320, 332, 301, 334, 390, 330, 320], - }, - { - name: 'Search Engine', - type: 'line', - stack: 'Total', - label: { - show: true, - position: 'top', - }, - areaStyle: {}, - emphasis: { - focus: 'series', - }, - data: [820, 932, 901, 934, 1290, 1330, 1320], - }, - ], - }; - return option; - }; - return ( - <> -
-
- -
-
- - ); -}; - -export default EchartsTest; - diff --git a/chat2db-client/src/pages/main/dashboard/index.less b/chat2db-client/src/pages/main/dashboard/index.less index b142562c9..112be652e 100644 --- a/chat2db-client/src/pages/main/dashboard/index.less +++ b/chat2db-client/src/pages/main/dashboard/index.less @@ -12,7 +12,7 @@ max-width: 400px; height: 100%; padding-top: 20px; - background-color: var(--color-bg-container); + background-color: var(--color-bg-elevated); } .createDashboardBtn { @@ -26,17 +26,19 @@ font-size: 20px; line-height: 24px; font-weight: 500; - margin-bottom: 40px; + margin-bottom: 20px; padding-left: 20px; padding-right: 8px; display: flex; justify-content: space-between; align-items: center; } + .plusIcon { font-size: 18px; padding: 2px; cursor: pointer; + &:hover { background-color: var(--color-hover-bg); } @@ -50,23 +52,34 @@ display: flex; justify-content: space-between; align-items: center; + border-radius: var(--border-radius-l-g); + + .itemTitle{ + font-weight: 400; + &:hover{ + color: var(--color-primary); + } + } - .boxLeftItemIcon { + .moreButton { + flex-shrink: 0; display: none; + transform: rotate(90deg); } &:hover { cursor: pointer; - background-color: var(--color-bg-elevated); + background-color: var(--color-hover-bg); - .boxLeftItemIcon { + .moreButton { display: block; } } } .activeItem { - background-color: var(--color-bg-elevated); + color: var(--color-primary); + background-color: var(--color-hover-bg); } .boxRight { @@ -85,8 +98,7 @@ margin-bottom: 24px; } -.boxRightContent { -} +.boxRightContent {} .boxRightContentRow { display: flex; @@ -99,4 +111,4 @@ &:last-child { margin-right: 0px; } -} +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 229dcc876..e5a2b596d 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -93,7 +93,10 @@ function Chart(props: IProps) { className={cs({ [styles.boxLeftItem]: true, [styles.activeItem]: curDashboard?.id === i.id })} onClick={() => onClickDashboardItem(i)} > -
{i.name}
+
+ + {i.name} +
- +
+ +
)); @@ -204,7 +209,7 @@ function Chart(props: IProps) { }} onAdd={() => {}} > */} - {chartList.map((rowData: number[], rowIndex: number) => ( + {/* {chartList.map((rowData: number[], rowIndex: number) => (
{rowData.map((chartId: number, colIndex: number) => (
@@ -221,7 +226,7 @@ function Chart(props: IProps) {
))}
- ))} + ))} */} {/* */}
@@ -238,11 +243,15 @@ function Chart(props: IProps) {
{renderLeft()}
-
{renderContent()}
+
+ { + renderContent() + } +
{ try { @@ -267,11 +276,15 @@ function Chart(props: IProps) { setOpenAddDashboard(false); form.setFieldsValue({}); }} - okText="Confirm" - cancelText="Cancel" + okText={i18n('common.button.confirm')} + cancelText={i18n('common.button.cancel')} > - + @@ -286,22 +299,3 @@ function Chart(props: IProps) { export default connect(({ global }: { global: GlobalState }) => ({ settings: global.settings, }))(Chart); - -{ - /* - - - */ -} diff --git a/chat2db-client/src/pages/main/dashboard/echart-test/index.less b/chat2db-client/src/pages/main/dashboard/left-block/index.less similarity index 100% rename from chat2db-client/src/pages/main/dashboard/echart-test/index.less rename to chat2db-client/src/pages/main/dashboard/left-block/index.less diff --git a/chat2db-client/src/pages/main/dashboard/left-block/index.tsx b/chat2db-client/src/pages/main/dashboard/left-block/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index cb19ae00d..8e0b1bb56 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -25,23 +25,23 @@ const navConfig: INavItem[] = [ iconFontSize: 16, component: , }, - // { - // key: 'dashboard', - // icon: '\ue629', - // iconFontSize: 24, - // component: , - // }, - // { - // key: 'connections', - // icon: '\ue622', - // iconFontSize: 20, - // component: , - // }, - // { - // key: 'github', - // icon: '\ue885', - // openBrowser: 'https://github.com/alibaba/Chat2DB', - // }, + { + key: 'dashboard', + icon: '\ue629', + iconFontSize: 24, + component: , + }, + { + key: 'connections', + icon: '\ue622', + iconFontSize: 20, + component: , + }, + { + key: 'github', + icon: '\ue885', + openBrowser: 'https://github.com/alibaba/Chat2DB', + }, ]; const initPageIndex = navConfig.findIndex(t => `/${t.key}` === window.location.pathname); @@ -85,7 +85,10 @@ function MainPage(props: IProps) { useEffect(() => { if (curConnection?.id) { dispatch({ - type: 'workspace/fetchdatabaseAndSchema', + type: 'workspace/fetchDatabaseAndSchema', + payload: { + dataSourceId: curConnection.id + } }) } }, [curConnection]) 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 f8a784060..d6aab7e7a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -50,7 +50,7 @@ interface IProps { dispatch: any; } -function handelDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { +function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { let newCascaderOptions: Option[] = []; if (databaseAndSchema.databases) { newCascaderOptions = databaseAndSchema.databases.map(t => { @@ -86,7 +86,7 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { const { curConnection } = connectionModel; const cascaderOptions = useMemo(() => { - const res = handelDatabaseAndSchema(databaseAndSchema); + const res = handleDatabaseAndSchema(databaseAndSchema); // 如果databaseAndSchema 发生切变 并且没选中确切的database时,需要默认选中第一个 if (!curWorkspaceParams.dataSourceId) { const curWorkspaceParams = { diff --git a/chat2db-client/src/typings/main.ts b/chat2db-client/src/typings/main.ts index edf282553..1d64200ac 100644 --- a/chat2db-client/src/typings/main.ts +++ b/chat2db-client/src/typings/main.ts @@ -5,5 +5,5 @@ export interface INavItem { icon: string; component?: React.ReactNode; openBrowser?: string; - iconFontSize: number; + iconFontSize?: number; } \ No newline at end of file From f834f1a81f89881f6fb5c7daff500ae3aaadfcd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Wed, 28 Jun 2023 17:13:12 +0800 Subject: [PATCH 0098/1069] =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=99=A8=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E5=80=BC=E6=94=B9=E9=80=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Console/MonacoEditor/index.less | 6 +- .../components/Console/MonacoEditor/index.tsx | 193 ++++++++++-------- .../src/components/Console/index.tsx | 82 ++++---- chat2db-client/src/pages/main/index.tsx | 1 + .../components/WorkspaceLeft/index.tsx | 1 - .../components/WorkspaceRight/index.tsx | 112 +++------- .../components/WorkspaceRightItem/index.tsx | 49 +++-- chat2db-client/src/service/base.ts | 16 +- chat2db-client/src/typings/common.ts | 4 +- 9 files changed, 232 insertions(+), 232 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.less b/chat2db-client/src/components/Console/MonacoEditor/index.less index 20be0d171..28c012a4a 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.less +++ b/chat2db-client/src/components/Console/MonacoEditor/index.less @@ -1,3 +1,3 @@ -.editorContainer{ - // height: 100%; -} \ No newline at end of file +.editorContainer { + height: 100%; +} diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index c6a9e2b0b..b6912b5a3 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -6,73 +6,51 @@ import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; const { keywords: SQLKeys } = language; import { editorDefaultOptions, ThemeType } from '@/constants'; import styles from './index.less'; -import { monacoSqlAutocomplete } from './syntax-parser/plugin/monaco-plugin'; export type IEditorIns = monaco.editor.IStandaloneCodeEditor; export type IEditorOptions = monaco.editor.IStandaloneEditorConstructionOptions; export type IEditorContentChangeEvent = monaco.editor.IModelContentChangedEvent; - interface IProps { id: string | number; - value?: string; + isActive?: boolean; language?: string; - className: string; - onChange?: (v: string, e?: IEditorContentChangeEvent) => void; - didMount?: (editor: IEditorIns) => any; + className?: string; options?: IEditorOptions; needDestroy?: boolean; addAction?: Array<{ id: string; label: string; action: (selectedText: string) => void }>; + // onChange?: (v: string, e?: IEditorContentChangeEvent) => void; + didMount?: (editor: IEditorIns) => any; + onSave?: (value: string) => void; // 快捷键保存的回调 + onExecute?: (value: string) => void; // 快捷键执行的回调 } export interface IExportRefFunction { getCurrentSelectContent: () => string; getAllContent: () => string; + setValue: (text: any, range?: IRangeType) => void; } function MonacoEditor(props: IProps, ref: ForwardedRef) { - const { id, className, value = '', language = 'sql', didMount, options } = props; - + const { + id, + className, + language = 'sql', + didMount, + options, + isActive, + onSave, + onExecute + } = props; const editorRef = useRef(); - const [editorVal, setEditorVal] = useState(''); - - // 受控暂存value - const valRef = useRef(''); - const [appTheme] = useTheme(); - useImperativeHandle(ref, () => ({ - getCurrentSelectContent, - getAllContent, - })); - - /** - * 获取当前选中的内容 - * @returns - */ - const getCurrentSelectContent = () => { - let selection = editorRef.current?.getSelection(); - if (!selection || selection.isEmpty()) { - return ''; - } else { - var selectedText = editorRef.current?.getModel()?.getValueInRange(selection); - return selectedText || ''; - } - }; - - /** 获取文本所有内容 */ - const getAllContent = () => { - const model = editorRef.current?.getModel(); - const value = model?.getValue(); - return value || ''; - }; - // init useEffect(() => { const editorIns = monaco.editor.create(document.getElementById(`monaco-editor-${id}`)!, { ...editorDefaultOptions, ...options, - value, - language: 'sql', + value: '', + language: language, theme: appTheme.backgroundColor === ThemeType.Light ? 'Default' : 'BlackTheme', }); editorRef.current = editorIns; @@ -81,7 +59,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { monaco.editor.defineTheme('BlackTheme', { base: 'vs-dark', inherit: true, - rules: [{ background: '#15161a' }], + rules: [{ background: '#15161a' }] as any, colors: { // 相关颜色属性配置 'editor.foreground': '#ffffff', @@ -92,14 +70,13 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { monaco.editor.defineTheme('Default', { base: 'vs', inherit: true, - rules: [{ background: '#15161a' }], + rules: [{ background: '#15161a' }] as any, colors: { 'editor.foreground': '#000000', 'editor.background': '#fff', //背景色 }, }); - // monacoSqlAutocomplete(monaco, editorIns); handleRegisterTigger(); createAction(editorIns); @@ -108,56 +85,64 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }; }, []); - // value 变了,直接设置editorValue useEffect(() => { - updateEditor(value); - valRef.current = value; - }, [value]); + if (isActive && editorRef.current) { + // 自定义快捷键 + editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, () => { + const value = editorRef.current?.getValue(); + onSave?.(value || ''); + }); - // editor 变了,val 没变,设置 editorValue 为 value - // useEffect(() => { - // if (editorVal !== valRef.current) { - // updateEditor(valRef.current); - // } - // }, [editorVal]); + editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, (event: Event) => { + const value = getCurrentSelectContent() + onExecute?.(value); + }); + } + }, [editorRef.current, isActive]) + // 监听主题色变化切换编辑器主题色 useEffect(() => { - const _ref = editorRef.current?.onDidChangeModelContent((e) => { - const curVal = editorRef.current?.getValue(); - props.onChange?.(curVal || '', e); - setEditorVal(curVal || ''); - }); + monaco.editor.setTheme(appTheme.backgroundColor === ThemeType.Dark ? 'BlackTheme' : 'Default'); + }, [appTheme.backgroundColor]); - return () => _ref && _ref.dispose(); - }, [props.onChange]); + // useEffect(() => { + // const _ref = editorRef.current?.onDidChangeModelContent((e) => { + // const curVal = editorRef.current?.getValue(); + // props.onChange?.(curVal || '', e); + // }); + // return () => _ref && _ref.dispose(); + // }, [props.onChange]); - const updateEditor = (value: string) => { - if (editorRef.current) { - if (value === editorRef.current.getValue()) { - return; - } - const model = editorRef.current.getModel(); - if (!model) return; + useImperativeHandle(ref, () => ({ + getCurrentSelectContent, + getAllContent, + setValue + })); - editorRef.current.pushUndoStop(); + const setValue = (text: any, range?: IRangeType) => { + appendMonacoValue(editorRef.current, text, range) + } - model.pushEditOperations( - [], - [ - { - range: model.getFullModelRange(), - text: value, - }, - ], - () => [editorRef.current.getSelection()], - ); - editorRef.current.pushUndoStop(); + /** + * 获取当前选中的内容 + * @returns + */ + const getCurrentSelectContent = () => { + let selection = editorRef.current?.getSelection(); + if (!selection || selection.isEmpty()) { + return ''; + } else { + var selectedText = editorRef.current?.getModel()?.getValueInRange(selection); + return selectedText || ''; } }; - useEffect(() => { - monaco.editor.setTheme(appTheme.backgroundColor === ThemeType.Dark ? 'BlackTheme' : 'Default'); - }, [appTheme.backgroundColor]); + /** 获取文本所有内容 */ + const getAllContent = () => { + const model = editorRef.current?.getModel(); + const value = model?.getValue(); + return value || ''; + }; const handleRegisterTigger = () => { // SQL关键词、 数据库、 表 、列 @@ -253,7 +238,47 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }); }; - return
; + return
; +} + +// text 需要添加的文本 +// range 添加到的位置 +// 'end' 末尾 +// 'front' 开头 +// 'cover' 覆盖掉原有的文字 +// 自定义位置数组 new monaco.Range [] +export type IRangeType = 'end' | 'front' | 'cover' | any; + +export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'end') => { + if (!editor) { + return + } + const model = editor?.getModel && editor.getModel(editor); + // 创建编辑操作,将当前文档内容替换为新内容 + let newRange: IRangeType = range; + text = `${text}\n` + switch (range) { + case 'cover': + newRange = model.getFullModelRange(); + break; + case 'front': + newRange = new monaco.Range(1, 1, 1, 1); + break; + case 'end': + const lastLine = editor.getModel().getLineCount(); + const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); + newRange = new monaco.Range(lastLine, lastLineLength, lastLine, lastLineLength); + break; + default: + break; + } + const op = { + range: newRange, + text, + }; + // decorations?: IModelDeltaDecoration[]: 一个数组类型的参数,用于指定插入的文本的装饰。可以用来设置文本的样式、颜色、背景色等。如果不需要设置装饰,可以忽略此参数。 + const decorations = [{}]; // 解决新增的文本默认背景色为灰色 + editor.executeEdits('setValue', [op], decorations); } export default forwardRef(MonacoEditor); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 505014222..979b0d072 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -1,13 +1,12 @@ +import React, { useEffect, useMemo, useRef, useState } from 'react'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; -import { Button, Spin } from 'antd'; -import React, { ForwardedRef, useEffect, useMemo, useRef, useState } from 'react'; +import { Button, Spin, message, notification } from 'antd'; import ChatInput from './ChatInput'; -import Editor, { IExportRefFunction } from './MonacoEditor'; +import Editor, { IExportRefFunction, IRangeType } from './MonacoEditor'; import { format } from 'sql-formatter'; import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; -import MonacoEditor from 'react-monaco-editor'; import { v4 as uuidv4 } from 'uuid'; import styles from './index.less'; @@ -31,13 +30,21 @@ enum IPromptTypeText { ChatRobot = 'Chat机器人', } +export type IAppendValue = { + text: any; + range?: IRangeType; +} + interface IProps { + /** 是否是活跃的console,用于快捷键 */ + isActive?: boolean; + /** 添加或修改的内容 */ + appendValue?: IAppendValue; /** 是否开启AI输入 */ hasAiChat: boolean; /** 是否可以开启SQL转到自然语言的相关ai操作 */ hasAi2Lang: boolean; - value?: string; - onChangeValue?: Function; + /** 运行或保存sql需要的父级参数 */ executeParams: { databaseName: string; dataSourceId: number; @@ -49,21 +56,18 @@ interface IProps { onExecuteSQL: (value: any) => void; } -function Console(props: IProps) { - const { hasAiChat = true, value, executeParams, onChangeValue } = props; +function Console(props: IProps, ref: any) { + const { hasAiChat = true, executeParams, appendValue, isActive } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); - const [context, setContext] = useState(); const [isLoading, setIsLoading] = useState(false); useEffect(() => { - setContext(value); - }, [value]); - - useEffect(() => { - onChangeValue?.(value); - }, [context]) + if (appendValue) { + editorRef?.current?.setValue(appendValue.text, appendValue.range); + } + }, [appendValue]) const onPressChatInput = (value: string) => { const params = formatParams({ @@ -77,12 +81,11 @@ function Console(props: IProps) { const isEOF = message === '[DONE]'; if (isEOF) { closeEventSource(); - // setContext(context + '\n' + chatResult.current + '\n\n\n'); setIsLoading(false); return; } chatResult.current += JSON.parse(message).content; - setContext((prevData) => prevData + JSON.parse(message).content); + // setContext((prevData) => prevData + JSON.parse(message).content); } catch (error) { console.log('handleMessage', error); } @@ -119,7 +122,7 @@ function Console(props: IProps) { .then((res) => { props.onExecuteSQL && props.onExecuteSQL(res); // console.log(res) - let p = { + let p: any = { ...executeParams, ddl: sqlContent, }; @@ -131,22 +134,25 @@ function Console(props: IProps) { }); }; - const saveConsole = () => { - // let p = { - // id: windowTab.consoleId, - // name: windowTab?.name, - // type: windowTab.DBType, - // dataSourceId: +params.id, - // databaseName: windowTab.databaseName, - // status: WindowTabStatus.RELEASE, - // ddl: getMonacoEditorValue(), - // }; - // historyServer.updateWindowTab(p).then((res) => { - // message.success('保存成功'); - // }); + const saveConsole = (value?: string) => { + const a = editorRef.current?.getAllContent() + + let p = { + ...executeParams, + id: executeParams.consoleId, + name: executeParams.consoleName, + ddl: value || editorRef.current?.getAllContent()!, + }; + historyServer.updateWindowTab(p).then((res) => { + // message.success('保存成功'); + notification.open({ + type: 'success', + message: '保存成功', + }); + }); }; - const addAction = [ + const addAction = useMemo(() => ([ { id: 'explainSQL', label: '解释SQL', @@ -162,7 +168,7 @@ function Console(props: IProps) { label: 'SQL转化', action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_2_SQL, selectedText), }, - ]; + ]), []) const handleAIRelativeOperation = (id: string, selectedText: string) => { console.log('handleAIRelativeOperation', id, selectedText); @@ -175,11 +181,11 @@ function Console(props: IProps) { {/*
{chatContent.current}
*/} setContext(v)} + isActive={isActive} + ref={editorRef as any} className={hasAiChat ? styles.console_editor_with_chat : styles.console_editor} addAction={addAction} + onSave={saveConsole} /> @@ -189,7 +195,7 @@ function Console(props: IProps) { RUN -
@@ -197,7 +203,7 @@ function Console(props: IProps) { type="text" onClick={() => { const contextTmp = editorRef?.current?.getAllContent(); - setContext(format(contextTmp || '')); + // setContext(format(contextTmp || '')); }} > Format diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 8e0b1bb56..eb8bc3a8c 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -40,6 +40,7 @@ const navConfig: INavItem[] = [ { key: 'github', icon: '\ue885', + iconFontSize: 26, openBrowser: 'https://github.com/alibaba/Chat2DB', }, ]; 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 d6aab7e7a..30fb86acd 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -171,7 +171,6 @@ const RenderTableBox = dvaModel(function (props: any) { }, [curWorkspaceParams]); function getInitialData() { - console.log(curWorkspaceParams); treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, 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 1d56bd48c..2efaeabb7 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -3,7 +3,7 @@ import { connect } from 'umi' import styles from './index.less'; import classnames from 'classnames'; import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment, DatabaseTypeCode } from '@/constants'; -import { IConsole } from '@/typings'; +import { IConsole, ICreateConsole } from '@/typings'; import historyService from '@/service/history'; import Tabs from '@/components/Tabs'; import LoadingContent from '@/components/Loading/LoadingContent'; @@ -24,52 +24,43 @@ const WorkspaceRight = memo(function (props) { const { workspaceModel, dispatch } = props; const { databaseAndSchema, curWorkspaceParams, doubleClickTreeNodeData } = workspaceModel; - useEffect(() => { getConsoleList(); }, [curWorkspaceParams]); useEffect(() => { - if (!doubleClickTreeNodeData) { + // 这里只处理没有console的情况下 + if (!doubleClickTreeNodeData || consoleList?.length) { return } + const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; - let flag = false; const ddl = `SELECT * FROM ${tableName};`; - - consoleList?.forEach((i) => { - if (i.databaseName === databaseName && i.dataSourceId === dataSourceId) { - flag = true; - setActiveConsoleId(i.id); - } + const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); + let p: any = { + name: name, + type: databaseType!, + dataSourceId: dataSourceId!, + databaseName: databaseName, + schemaName: schemaName, + dataSourceName: dataSourceName!, + status: ConsoleStatus.DRAFT, + ddl, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + }; + addConsole(p); + dispatch({ + type: 'workspace/setDoubleClickTreeNodeData', + payload: '' }); + }, [doubleClickTreeNodeData]); - if (!flag) { - const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); - let p = { - name: name, - type: databaseType!, - dataSourceId: dataSourceId!, - databaseName: databaseName!, - schemaName: schemaName!, - dataSourceName: dataSourceName!, - status: ConsoleStatus.DRAFT, - ddl, - tabOpened: ConsoleOpenedStatus.IS_OPEN, - connectable: true, - }; - - historyService.saveConsole(p).then((res) => { - const newConsole: IConsole = { - id: res, - ...p, - }; - setActiveConsoleId(newConsole.id); - setConsoleList([...(consoleList || []), newConsole]); - }); + useEffect(() => { + if (!consoleList?.length) { + setActiveConsoleId(undefined) } - }, [doubleClickTreeNodeData]); + }, [consoleList]) function getConsoleList() { let p: any = { @@ -80,7 +71,6 @@ const WorkspaceRight = memo(function (props) { }; historyService.getSaveList(p).then((res) => { - let flag = false; const newWindowList: IConsole[] = []; res.data?.map((item, index) => { if (item.connectable) { @@ -100,55 +90,15 @@ const WorkspaceRight = memo(function (props) { }); newWindowList.map((item: IConsole, index: number) => { + console.log(!activeConsoleId && index === 0) + console.log(activeConsoleId) if (!activeConsoleId && index === 0) { setActiveConsoleId(item.id); } else if (item.id === activeConsoleId) { - flag = true; setActiveConsoleId(item.id); } }); - setConsoleList(newWindowList); - - // if (!flag) { - // if (activeConsoleId) { - // historyService.getWindowTab({ id: activeConsoleId }).then((res: any) => { - // if (res.connectable) { - // newWindowList.push({ - // id: res.id, - // ddl: res.ddl, - // name: res.name, - // status: res.status, - // type: res.type, - // databaseName: res.databaseName, - // dataSourceName: res.dataSourceName, - // dataSourceId: res.dataSourceId, - // schemaName: res.schemaName, - // connectable: true, - // }); - // setActiveConsoleId(res.id); - // setConsoleList(newWindowList); - // } - // }); - // } else { - // let p = { - // name: 'default name', - // ddl: 'string', - // dataSourceId: currentWorkspaceData.dataSourceId, - // databaseName: currentWorkspaceData.databaseName, - // type: currentWorkspaceData.databaseType, - // status: ConsoleStatus.DRAFT, - // connectable: true, - // tabOpened: ConsoleOpenedStatus.IS_OPEN - // } - // historyService.saveConsole(p).then(res => { - // setActiveConsoleId(res); - // // getConsoleList(); - // }) - // } - // } else { - // setConsoleList(newWindowList); - // } }); } @@ -165,7 +115,7 @@ const WorkspaceRight = memo(function (props) { } }; - const addConsole = () => { + const addConsole = (params?: ICreateConsole) => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams let p = { name: `new console${consoleList?.length}`, @@ -177,8 +127,9 @@ const WorkspaceRight = memo(function (props) { status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, } - historyService.saveConsole(p).then(res => { - getConsoleList() + historyService.saveConsole(params || p).then(res => { + + getConsoleList(); }) } @@ -242,6 +193,7 @@ const WorkspaceRight = memo(function (props) { {consoleList?.map((t, index) => { return
(function WorkspaceRightItem(props) { - const { className, data } = props; +const WorkspaceRightItem = memo(function (props) { + const { className, data, workspaceModel, isActive, dispatch } = props; const draggableRef = useRef(); - const [consoleValue, setConsoleValue] = useState(data.initDDL || ''); + const [appendValue, setAppendValue] = useState({ text: data.initDDL }); const [resultData, setResultData] = useState([]); - const { state, dispatch } = useReducerContext(); - const { dblclickTreeNodeData, currentWorkspaceData } = state; - + const { doubleClickTreeNodeData } = workspaceModel; useEffect(() => { - if (!dblclickTreeNodeData) { + if (!doubleClickTreeNodeData) { return } - const { extraParams } = dblclickTreeNodeData; - const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; + const { extraParams } = doubleClickTreeNodeData; + const { tableName } = extraParams || {}; const ddl = `SELECT * FROM ${tableName};`; - - if (data.databaseName === databaseName && data.dataSourceId === dataSourceId) { - setConsoleValue(`${consoleValue}\n${ddl}`) + if (isActive) { + setAppendValue({ text: ddl }); } - }, [dblclickTreeNodeData]); + dispatch({ + type: 'workspace/setDoubleClickTreeNodeData', + payload: '' + }); + }, [doubleClickTreeNodeData]); return
{ - setConsoleValue(value) - } - } onExecuteSQL={(result) => { setResultData(result); }} @@ -70,3 +71,9 @@ export default memo(function WorkspaceRightItem(props) {
}) + +const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ + workspaceModel: workspace +})) + +export default dvaModel(WorkspaceRightItem) diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 40aa52cb0..51059ec25 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,5 +1,5 @@ import { extend, ResponseError } from 'umi-request'; -import { message } from 'antd'; +import { message, notification } from 'antd'; import { getLang } from '@/utils/localStorage'; export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; @@ -58,7 +58,12 @@ const errorHandler = (error: ResponseError, errorLevel: IErrorLevel) => { const errorText = codeMessage[response.status] || response.statusText; const { status } = response; if (errorLevel === 'toast') { - message.error(`${status}: ${errorText}`); + notification.open({ + type: 'error', + message: status, + description: errorText, + }); + // message.error(`${status}: ${errorText}`); } }; @@ -148,7 +153,12 @@ export default function createRequest

(url: string, options?: I const { success, errorCode, errorMessage, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { - message.error(`${errorCode}: ${errorMessage}`); + notification.open({ + type: 'error', + message: errorCode, + description: errorMessage, + }); + // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index c918ac420..d3cdfce79 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -19,9 +19,9 @@ export interface IConsole { name: string; ddl: string; dataSourceId: number; - databaseName: string; dataSourceName: string; - schemaName: string; + databaseName?: string; + schemaName?: string; type: DatabaseTypeCode; status: ConsoleStatus; connectable: boolean; From 0fa75c8107b2dc37cbdb9d5590b9afe54d50473c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Wed, 28 Jun 2023 21:05:17 +0800 Subject: [PATCH 0099/1069] connection --- .../src/components/Console/index.tsx | 34 ++++++---- chat2db-client/src/components/Tabs/index.tsx | 13 ++-- chat2db-client/src/constants/common.ts | 3 - chat2db-client/src/models/workspace.ts | 35 ++++++++--- .../components/WorkspaceLeft/index.tsx | 63 +++++++++---------- .../components/WorkspaceRight/index.tsx | 35 ++++++----- chat2db-client/src/service/history.ts | 19 +++--- chat2db-client/src/service/sql.ts | 2 +- chat2db-client/src/utils/localStorage.ts | 1 + 9 files changed, 117 insertions(+), 88 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 979b0d072..e50a6e03e 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { connect } from 'umi'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; import { Button, Spin, message, notification } from 'antd'; @@ -8,11 +9,11 @@ import { format } from 'sql-formatter'; import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import { v4 as uuidv4 } from 'uuid'; - import styles from './index.less'; import Loading from '../Loading/Loading'; -import { DatabaseTypeCode } from '@/constants'; +import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import Iconfont from '../Iconfont'; +import { IWorkspaceModelType } from '@/models/workspace'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -54,10 +55,12 @@ interface IProps { consoleName: string; }; onExecuteSQL: (value: any) => void; + workspaceModel: IWorkspaceModelType; + dispatch: any; } function Console(props: IProps, ref: any) { - const { hasAiChat = true, executeParams, appendValue, isActive } = props; + const { hasAiChat = true, executeParams, appendValue, isActive, dispatch } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -138,17 +141,18 @@ function Console(props: IProps, ref: any) { const a = editorRef.current?.getAllContent() let p = { - ...executeParams, id: executeParams.consoleId, - name: executeParams.consoleName, - ddl: value || editorRef.current?.getAllContent()!, + status: ConsoleStatus.RELEASE }; - historyServer.updateWindowTab(p).then((res) => { - // message.success('保存成功'); - notification.open({ - type: 'success', - message: '保存成功', - }); + historyServer.updateSavedConsole(p).then((res) => { + message.success('保存成功'); + dispatch({ + type: 'workspace/fetchGetSavedConsole' + }) + // notification.open({ + // type: 'success', + // message: '保存成功', + // }); }); }; @@ -213,4 +217,8 @@ function Console(props: IProps, ref: any) { ); } -export default Console; +const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ + workspaceModel: workspace +})) + +export default dvaModel(Console); diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index c21bc0ec8..7c3480344 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -17,14 +17,19 @@ export interface IOnchangeProps { interface IProps { className?: string; tabs: IOption[]; + activeTab?: number; onChange: (key: IOption['value']) => void; onEdit: (action: 'add' | 'remove', key?: IOption['value']) => void; } export default memo(function Tab(props) { - const { className, tabs, onChange, onEdit } = props; + const { className, tabs, onChange, onEdit, activeTab } = props; const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs)); - const [activeTab, setActiveTab] = useState(internalTabs[0]?.value); + const [internalActiveTab, setInternalActiveTab] = useState(internalTabs[0]?.value); + + useEffect(() => { + setInternalActiveTab(activeTab) + }, [activeTab]) useEffect(() => { setInternalTabs(lodash.cloneDeep(tabs)); @@ -37,7 +42,7 @@ export default memo(function Tab(props) { } function changeTab(data: IOption) { - setActiveTab(data.value); + setInternalActiveTab(data.value); onChange(data.value) } @@ -53,7 +58,7 @@ export default memo(function Tab(props) { { internalTabs.map(t => { return

diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 0989df46f..6e5491127 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -22,6 +22,3 @@ export enum ConsoleStatus { DRAFT = 'DRAFT', RELEASE = 'RELEASE', } - -/** console顶部注释 */ -export const consoleTopComment = `-- Chat2DB自然语言转SQL等AI功能 >> https://github.com/alibaba/Chat2DB/blob/main/CHAT2DB_AI_SQL.md\n\n`; \ No newline at end of file diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index d1708696f..2f3d88254 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -1,8 +1,9 @@ import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/utils/localStorage'; import sqlService, { MetaSchemaVO } from '@/service/sql'; -import { DatabaseTypeCode } from '@/constants'; +import historyService from '@/service/history' +import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { Effect, Reducer } from 'umi'; -import { ITreeNode } from '@/typings'; +import { ITreeNode, IConsole } from '@/typings'; export type ICurWorkspaceParams = { dataSourceId: number; @@ -14,11 +15,12 @@ export type ICurWorkspaceParams = { export interface IState { // 当前连接下的及联databaseAndSchema数据 - databaseAndSchema: MetaSchemaVO; + databaseAndSchema: MetaSchemaVO | undefined; // 当前工作区所需的参数 curWorkspaceParams: ICurWorkspaceParams; // 双击树node节点 doubleClickTreeNodeData: ITreeNode | undefined; + consoleList: IConsole[]; } export interface IWorkspaceModelType { @@ -28,9 +30,11 @@ export interface IWorkspaceModelType { setDatabaseAndSchema: Reducer; setCurWorkspaceParams: Reducer; setDoubleClickTreeNodeData: Reducer; //TS TODO: + setConsoleList: Reducer; }; effects: { fetchDatabaseAndSchema: Effect; + fetchGetSavedConsole: Effect; }; } @@ -38,9 +42,10 @@ const WorkspaceModel: IWorkspaceModelType = { namespace: 'workspace', state: { - databaseAndSchema: {}, + databaseAndSchema: undefined, curWorkspaceParams: getCurrentWorkspaceDatabase(), doubleClickTreeNodeData: undefined, + consoleList: [] }, reducers: { @@ -66,18 +71,34 @@ const WorkspaceModel: IWorkspaceModelType = { doubleClickTreeNodeData: payload, }; }, + + setConsoleList(state, { payload }) { + return { + ...state, + consoleList: payload, + }; + }, }, effects: { - *fetchDatabaseAndSchema({ payload }, action) { - const { put } = action; - // ts-ignore + *fetchDatabaseAndSchema({ payload }, { put }) { const res = yield sqlService.getDatabaseSchemaList(payload); yield put({ type: 'setDatabaseAndSchema', payload: res, }); }, + *fetchGetSavedConsole({ payload }, { put }) { + const res = yield historyService.getSavedConsoleList({ + pageNo: 1, + pageSize: 999, + status: ConsoleStatus.RELEASE + }); + yield put({ + type: 'setConsoleList', + payload: res.data, + }); + }, }, }; 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 30fb86acd..89b2c98c3 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -10,10 +10,11 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import Tree from '../Tree'; import { treeConfig } from '../Tree/treeConfig'; -import { TreeNodeType } from '@/constants'; +import { TreeNodeType, ConsoleStatus } from '@/constants'; import { ITreeNode } from '@/typings'; import { IConsole } from '@/typings'; import styles from './index.less'; +import { State } from '@/components/StateIndicator'; interface IProps { className?: string; @@ -52,8 +53,8 @@ interface IProps { function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { let newCascaderOptions: Option[] = []; - if (databaseAndSchema.databases) { - newCascaderOptions = databaseAndSchema.databases.map(t => { + if (databaseAndSchema?.databases) { + newCascaderOptions = databaseAndSchema?.databases.map(t => { let schemasList: Option[] = [] if (t.schemas) { schemasList = t.schemas.map(t => { @@ -84,11 +85,12 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { const { connectionModel, workspaceModel, dispatch } = props; const { databaseAndSchema, curWorkspaceParams } = workspaceModel; const { curConnection } = connectionModel; + const [currentSelectedName, setCurrentSelectedName] = useState(''); const cascaderOptions = useMemo(() => { const res = handleDatabaseAndSchema(databaseAndSchema); - // 如果databaseAndSchema 发生切变 并且没选中确切的database时,需要默认选中第一个 - if (!curWorkspaceParams.dataSourceId) { + if (!curWorkspaceParams?.dataSourceId || curWorkspaceParams?.dataSourceId !== curConnection?.id) { + // 如果databaseAndSchema 发生切变 并且没选中确切的database时,需要默认选中第一个 const curWorkspaceParams = { dataSourceId: curConnection?.id, databaseSourceName: curConnection?.alias, @@ -104,6 +106,14 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { return res }, [databaseAndSchema]) + useEffect(() => { + if (curWorkspaceParams) { + const { databaseName, schemaName, databaseSourceName } = curWorkspaceParams; + const currentSelectedArr = [databaseSourceName, databaseName, schemaName].filter((t) => t); + setCurrentSelectedName(currentSelectedArr.join('/')); + } + }, [curWorkspaceParams]) + const onChange: any = (valueArr: any, selectedOptions: any) => { let labelArr: string[] = []; labelArr = selectedOptions.map((t: any) => { @@ -130,12 +140,6 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) {
); - function renderCurrentSelected() { - const { databaseName, schemaName, databaseSourceName } = curWorkspaceParams; - const currentSelectedArr = [databaseSourceName, databaseName, schemaName].filter((t) => t); - return currentSelectedArr.join('/'); - } - return (
-
{renderCurrentSelected() || {i18n('workspace.cascader.placeholder')}}
+
{currentSelectedName || {i18n('workspace.cascader.placeholder')}}
@@ -165,7 +169,7 @@ const RenderTableBox = dvaModel(function (props: any) { const [initialData, setInitialData] = useState([]); useEffect(() => { - if (curWorkspaceParams.dataSourceId) { + if (curWorkspaceParams?.dataSourceId) { getInitialData(); } }, [curWorkspaceParams]); @@ -177,8 +181,6 @@ const RenderTableBox = dvaModel(function (props: any) { ...curWorkspaceParams, extraParams: curWorkspaceParams, }).then((res) => { - console.log(res) - setInitialData(res); }); } @@ -194,32 +196,27 @@ const RenderTableBox = dvaModel(function (props: any) { }) const RenderSaveBox = dvaModel(function (props: any) { - const [savedList, setSaveList] = useState([]); - const { workspaceModel } = props; - const { curWorkspaceParams } = workspaceModel; + const { workspaceModel, dispatch } = props; + const { curWorkspaceParams, consoleList } = workspaceModel; useEffect(() => { - getSaveList(); - }, [curWorkspaceParams]) - - function getSaveList() { - let p: any = { - pageNo: 1, - pageSize: 999, - ...curWorkspaceParams - } - - historyService.getSaveList(p).then(res => { - setSaveList(res.data) + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + pageNo: 1, + pageSize: 999, + status: ConsoleStatus.RELEASE, + ...curWorkspaceParams + } }) - } + }, [curWorkspaceParams]) return
Saved
- + { - savedList?.map(t => { + consoleList?.map((t: IConsole) => { return
{t.name}
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 2efaeabb7..476427007 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -2,7 +2,7 @@ import React, { memo, useRef, useEffect, useState } from 'react'; import { connect } from 'umi' import styles from './index.less'; import classnames from 'classnames'; -import { ConsoleOpenedStatus, ConsoleStatus, consoleTopComment, DatabaseTypeCode } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode } from '@/constants'; import { IConsole, ICreateConsole } from '@/typings'; import historyService from '@/service/history'; import Tabs from '@/components/Tabs'; @@ -58,7 +58,19 @@ const WorkspaceRight = memo(function (props) { useEffect(() => { if (!consoleList?.length) { - setActiveConsoleId(undefined) + setActiveConsoleId(undefined); + } else if (!activeConsoleId) { + setActiveConsoleId(consoleList[0].id); + } else { + let flag = false; + consoleList.forEach(t => { + if (t.id === activeConsoleId) { + flag = true + } + }) + if (!flag) { + setActiveConsoleId(consoleList[consoleList.length - 1].id) + } } }, [consoleList]) @@ -70,7 +82,7 @@ const WorkspaceRight = memo(function (props) { ...curWorkspaceParams, }; - historyService.getSaveList(p).then((res) => { + historyService.getSavedConsoleList(p).then((res) => { const newWindowList: IConsole[] = []; res.data?.map((item, index) => { if (item.connectable) { @@ -88,16 +100,6 @@ const WorkspaceRight = memo(function (props) { }); } }); - - newWindowList.map((item: IConsole, index: number) => { - console.log(!activeConsoleId && index === 0) - console.log(activeConsoleId) - if (!activeConsoleId && index === 0) { - setActiveConsoleId(item.id); - } else if (item.id === activeConsoleId) { - setActiveConsoleId(item.id); - } - }); setConsoleList(newWindowList); }); } @@ -119,7 +121,7 @@ const WorkspaceRight = memo(function (props) { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams let p = { name: `new console${consoleList?.length}`, - ddl: consoleTopComment, + ddl: '', dataSourceId: dataSourceId!, databaseName: databaseName!, schemaName: schemaName!, @@ -128,7 +130,7 @@ const WorkspaceRight = memo(function (props) { tabOpened: ConsoleOpenedStatus.IS_OPEN, } historyService.saveConsole(params || p).then(res => { - + setActiveConsoleId(res); getConsoleList(); }) } @@ -165,7 +167,7 @@ const WorkspaceRight = memo(function (props) { if (window!.status === 'DRAFT') { historyService.deleteWindowTab({ id: window!.id }); } else { - historyService.updateWindowTab(p); + historyService.updateSavedConsole(p); } }; @@ -182,6 +184,7 @@ const WorkspaceRight = memo(function (props) { { return { label: t.name, diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index 0f2cceb70..687369e74 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -1,12 +1,13 @@ import createRequest from "./base"; // import { IPageResponse,IPageParams,IHistoryRecord, IWindowTab, ISavedConsole } from '@/types'; -import { ConsoleOpenedStatus, DatabaseTypeCode } from '@/constants' +import { ConsoleOpenedStatus, DatabaseTypeCode, ConsoleStatus } from '@/constants' import { ICreateConsole, IConsole, IPageResponse, IPageParams } from '@/typings'; export interface IGetSavedListParams extends IPageParams { dataSourceId?: string; databaseName?: string; - ConsoleOpenedStatus?: ConsoleOpenedStatus; + tabOpened?: ConsoleOpenedStatus; + status?: ConsoleStatus } export interface ISaveBasicInfo { name: string; @@ -16,21 +17,17 @@ export interface ISaveBasicInfo { databaseName: string; } -export interface IUpdateWindowParams { +export interface IUpdateConsoleParams { id: number; - name: string; - ddl: string; - dataSourceId: number; - databaseName: string; } const saveConsole = createRequest('/api/operation/saved/create', { method: 'post' }); const getWindowTab = createRequest<{ id: number }, number>('/api/operation/saved/:id', { method: 'get' }); -const updateWindowTab = createRequest('/api/operation/saved/update', { method: 'post' }); +const updateSavedConsole = createRequest & {id: number}, number>('/api/operation/saved/update', { method: 'post' }); -const getSaveList = createRequest>('/api/operation/saved/list', {}); +const getSavedConsoleList = createRequest>('/api/operation/saved/list', {}); const deleteWindowTab = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); @@ -39,8 +36,8 @@ const createHistory = createRequest('/api/operation/log/cr const getHistoryList = createRequest>('/api/operation/log/list', {}); export default { - getSaveList, - updateWindowTab, + getSavedConsoleList, + updateSavedConsole, getHistoryList, saveConsole, deleteWindowTab, diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 910490e59..37521181d 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,5 +1,5 @@ import createRequest from './base'; -import { IPageResponse, ITable, IPageParams } from '@/types'; +import { IPageResponse, IPageParams } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; export interface IGetListParams extends IPageParams { diff --git a/chat2db-client/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts index dd22abed8..9370909d2 100644 --- a/chat2db-client/src/utils/localStorage.ts +++ b/chat2db-client/src/utils/localStorage.ts @@ -34,6 +34,7 @@ export function setCurrentWorkspaceDatabase(value: ICurWorkspaceParams) { export function getCurrentWorkspaceDatabase(): ICurWorkspaceParams { const curWorkspaceParams = localStorage.getItem('current-workspace-database'); + if (curWorkspaceParams) { return JSON.parse(curWorkspaceParams) } From f957e8c0dc56799ada457e36735a935cca88840c Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 28 Jun 2023 21:29:16 +0800 Subject: [PATCH 0100/1069] add azure openai support --- .../ai/azure/client/AzureOpenAiStreamClient.java | 10 ++++------ .../ai/listener/AzureOpenAIEventSourceListener.java | 11 ++++------- 2 files changed, 8 insertions(+), 13 deletions(-) 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 ce4ff7ddf..1c48ec707 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 @@ -13,7 +13,6 @@ import com.azure.ai.openai.models.CompletionsUsage; import com.azure.core.credential.AzureKeyCredential; import com.azure.core.util.IterableStream; -import com.unfbx.chatgpt.entity.chat.Message; import lombok.extern.slf4j.Slf4j; import okhttp3.sse.EventSourceListener; import org.apache.commons.collections4.CollectionUtils; @@ -73,18 +72,16 @@ public void streamCompletions(List chatMessages, EventSourceListene chatCompletionsStream.forEach(chatCompletions -> { String text = ""; - System.out.printf("Model ID=%s is created at %d.%n", chatCompletions.getId(), + log.info("Model ID={} is created at {}.", chatCompletions.getId(), chatCompletions.getCreated()); for (ChatChoice choice : chatCompletions.getChoices()) { ChatMessage message = choice.getDelta(); if (message != null) { - log.info("Index: {}, Chat Role: {}.%n", choice.getIndex(), message.getRole()); + log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole()); text = message.getContent(); } } - Message message = new Message(); if (StringUtils.isNotBlank(text)) { - message.setContent(text); eventSourceListener.onEvent(null, "[DATA]", null, text); } CompletionsUsage usage = chatCompletions.getUsage(); @@ -95,12 +92,13 @@ public void streamCompletions(List chatMessages, EventSourceListene usage.getCompletionTokens(), usage.getTotalTokens()); } }); - eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]"); log.info("结束调用非流式输出自定义AI"); } catch (Exception e) { log.error("请求参数解析异常", e); eventSourceListener.onFailure(null, e, null); throw new ParamBusinessException(); + } finally { + eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]"); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java index 1d6dedfe1..2950578cb 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java @@ -11,6 +11,7 @@ import okhttp3.ResponseBody; import okhttp3.sse.EventSource; import okhttp3.sse.EventSourceListener; +import org.apache.commons.lang3.StringUtils; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; /** @@ -52,15 +53,11 @@ public void onEvent(EventSource eventSource, String id, String type, String data sseEmitter.complete(); return; } - ObjectMapper mapper = new ObjectMapper(); - // 读取Json - Completions completionResponse = mapper.readValue(data, Completions.class); - String text = completionResponse.getChoices().get(0).getText() ; Message message = new Message(); - if (text != null) { - message.setContent(text); + if (StringUtils.isNotBlank(data)) { + message.setContent(data); sseEmitter.send(SseEmitter.event() - .id(completionResponse.getId()) + .id(null) .data(message) .reconnectTime(3000)); } From 120c67af7fb6006a04bd5a113def4e8d66921f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Wed, 28 Jun 2023 21:40:25 +0800 Subject: [PATCH 0101/1069] zancun --- .../src/components/Console/index.tsx | 29 +++++------ .../src/components/SearchResult/index.tsx | 8 +-- .../src/components/Tabs2/index.less | 50 +++++++++++++++++++ chat2db-client/src/components/Tabs2/index.tsx | 37 ++++++++++++++ .../components/WorkspaceRightItem/index.less | 1 - .../components/WorkspaceRightItem/index.tsx | 18 ++++--- 6 files changed, 115 insertions(+), 28 deletions(-) create mode 100644 chat2db-client/src/components/Tabs2/index.less create mode 100644 chat2db-client/src/components/Tabs2/index.tsx diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index e50a6e03e..0eaebd470 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -111,6 +111,7 @@ function Console(props: IProps, ref: any) { if (!sqlContent) { sqlContent = editorRef?.current?.getAllContent(); } + if (!sqlContent) { return; } @@ -119,22 +120,16 @@ function Console(props: IProps, ref: any) { sql: sqlContent, ...executeParams, }; - - sqlServer - .executeSql(p) - .then((res) => { - props.onExecuteSQL && props.onExecuteSQL(res); - // console.log(res) - let p: any = { - ...executeParams, - ddl: sqlContent, - }; - historyServer.createHistory(p); - // setManageResultDataList(res); - }) - .catch((error) => { - // setManageResultDataList([]); - }); + props.onExecuteSQL?.(undefined); + sqlServer.executeSql(p).then((res) => { + props.onExecuteSQL?.(res); + // console.log(res) + let p: any = { + ...executeParams, + ddl: sqlContent, + }; + historyServer.createHistory(p); + }) }; const saveConsole = (value?: string) => { @@ -207,7 +202,7 @@ function Console(props: IProps, ref: any) { type="text" onClick={() => { const contextTmp = editorRef?.current?.getAllContent(); - // setContext(format(contextTmp || '')); + editorRef?.current?.setValue(format(contextTmp || ''), 'cover'); }} > Format diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index a40225dd1..4c8971607 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,6 +1,6 @@ import React, { memo, useEffect, useState, useRef } from 'react'; import classnames from 'classnames'; -import Tabs from '@/components/Tabs'; +import Tabs from '@/components/Tabs2'; import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; import LoadingContent from '@/components/Loading/LoadingContent'; @@ -13,7 +13,7 @@ import styles from './index.less'; interface IProps { className?: string; - manageResultDataList: IManageResultData[]; + manageResultDataList?: IManageResultData[]; } interface DataType { @@ -172,8 +172,8 @@ export function TableBox(props: ITableProps) { {dataList !== null ? (
) : ( - - )} + + )} void; + extra?: React.ReactNode +} + +export default memo(function Tabs({ className, tabs, currentTab, onChange, extra }: IProps) { + function myChange(key: string) { + const index = tabs.findIndex(t => { + return t.key === key + }) + onChange(key, index) + } + + return
+ +
+ {extra} +
+
+}) diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less index 068c2b198..b76828b8e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less @@ -15,7 +15,6 @@ .boxRightResult { border-top: 1px solid var(--color-border); - padding: 10px; height: 0px; flex: 1; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 634c76293..282abc510 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -30,8 +30,9 @@ const WorkspaceRightItem = memo(function (props) { const { className, data, workspaceModel, isActive, dispatch } = props; const draggableRef = useRef(); const [appendValue, setAppendValue] = useState({ text: data.initDDL }); - const [resultData, setResultData] = useState([]); + const [resultData, setResultData] = useState(); const { doubleClickTreeNodeData } = workspaceModel; + const [showResult, setShowResult] = useState(false); useEffect(() => { if (!doubleClickTreeNodeData) { @@ -58,15 +59,20 @@ const WorkspaceRightItem = memo(function (props) { executeParams={{ ...data }} hasAiChat={true} hasAi2Lang={true} - onExecuteSQL={(result) => { - setResultData(result); + onExecuteSQL={(res: any) => { + setResultData(res); + setShowResult(true); }} /> +
- - - + { + showResult && + + + + }
From 4163bc2f8d8cf89895de2315025d6116b496cc18 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Wed, 28 Jun 2023 21:40:44 +0800 Subject: [PATCH 0102/1069] fix conflict --- .vscode/settings.json | 1 + .../src/components/Console/index.tsx | 94 +++++++++------ .../src/components/Iconfont/index.tsx | 6 +- chat2db-client/src/models/connection.ts | 8 +- .../main/dashboard/chart-item/index.less | 33 ++++- .../pages/main/dashboard/chart-item/index.tsx | 113 ++++++++++++++---- .../src/pages/main/dashboard/index.tsx | 36 ++---- .../components/WorkspaceLeft/index.tsx | 102 ++++++++-------- chat2db-client/src/typings/common.ts | 10 +- chat2db-client/src/utils/database.ts | 35 ++++++ 10 files changed, 287 insertions(+), 151 deletions(-) create mode 100644 chat2db-client/src/utils/database.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 228577777..8a7400aef 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,7 @@ "echarts", "favicons", "iconfont", + "monaco", "nsis", "pgsql", "Sercurity", diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 0eaebd470..fc080c2f7 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -34,7 +34,7 @@ enum IPromptTypeText { export type IAppendValue = { text: any; range?: IRangeType; -} +}; interface IProps { /** 是否是活跃的console,用于快捷键 */ @@ -44,23 +44,34 @@ interface IProps { /** 是否开启AI输入 */ hasAiChat: boolean; /** 是否可以开启SQL转到自然语言的相关ai操作 */ - hasAi2Lang: boolean; - /** 运行或保存sql需要的父级参数 */ + hasAi2Lang?: boolean; + hasSaveBtn?: boolean; + value?: string; + onChangeValue?: Function; executeParams: { - databaseName: string; - dataSourceId: number; - type: DatabaseTypeCode; - consoleId: number; + databaseName?: string; + dataSourceId?: number; + type?: DatabaseTypeCode; + consoleId?: number; schemaName?: string; - consoleName: string; + consoleName?: string; }; onExecuteSQL: (value: any) => void; workspaceModel: IWorkspaceModelType; dispatch: any; } -function Console(props: IProps, ref: any) { - const { hasAiChat = true, executeParams, appendValue, isActive, dispatch } = props; +function Console(props: IProps) { + const { + hasAiChat = true, + executeParams, + appendValue, + isActive, + dispatch, + hasSaveBtn = true, + value, + onChangeValue, + } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -70,7 +81,7 @@ function Console(props: IProps, ref: any) { if (appendValue) { editorRef?.current?.setValue(appendValue.text, appendValue.range); } - }, [appendValue]) + }, [appendValue]); const onPressChatInput = (value: string) => { const params = formatParams({ @@ -99,7 +110,7 @@ function Console(props: IProps, ref: any) { }; const closeEventSource = connectToEventSource({ - url: `/api/ai/chat1?${params}`, + url: `/api/ai/chat?${params}`, uid, onMessage: handleMessage, onError: handleError, @@ -133,17 +144,17 @@ function Console(props: IProps, ref: any) { }; const saveConsole = (value?: string) => { - const a = editorRef.current?.getAllContent() + const a = editorRef.current?.getAllContent(); let p = { id: executeParams.consoleId, - status: ConsoleStatus.RELEASE + status: ConsoleStatus.RELEASE, }; historyServer.updateSavedConsole(p).then((res) => { message.success('保存成功'); dispatch({ - type: 'workspace/fetchGetSavedConsole' - }) + type: 'workspace/fetchGetSavedConsole', + }); // notification.open({ // type: 'success', // message: '保存成功', @@ -151,23 +162,26 @@ function Console(props: IProps, ref: any) { }); }; - const addAction = useMemo(() => ([ - { - id: 'explainSQL', - label: '解释SQL', - action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_EXPLAIN, selectedText), - }, - { - id: 'optimizeSQL', - label: '优化SQL', - action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_OPTIMIZER, selectedText), - }, - { - id: 'changeSQL', - label: 'SQL转化', - action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_2_SQL, selectedText), - }, - ]), []) + const addAction = useMemo( + () => [ + { + id: 'explainSQL', + label: '解释SQL', + action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_EXPLAIN, selectedText), + }, + { + id: 'optimizeSQL', + label: '优化SQL', + action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_OPTIMIZER, selectedText), + }, + { + id: 'changeSQL', + label: 'SQL转化', + action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_2_SQL, selectedText), + }, + ], + [], + ); const handleAIRelativeOperation = (id: string, selectedText: string) => { console.log('handleAIRelativeOperation', id, selectedText); @@ -191,12 +205,14 @@ function Console(props: IProps, ref: any) {
- + {hasSaveBtn && ( + + )}
+ */}
- {chartData?.sqlData || isEditing ?
{renderChart()}
: renderEmptyBlock()} - {isEditing &&
{renderEditorBlock()}
} + {chartData?.sqlData || isEditing ? renderChart() : renderEmptyBlock()} + {isEditing && renderEditorBlock()}
{props.id}
); diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index e5a2b596d..1decf0d42 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import { Button, Dropdown, Form, Input, Modal, message } from 'antd'; import { connect, Dispatch } from 'umi'; import cs from 'classnames'; -import { IChartItem, IChartType, IDashboardItem } from '@/typings'; +import { IChartItem, IChartType, IConnectionDetails, IDashboardItem } from '@/typings'; import DraggableContainer from '@/components/DraggableContainer'; import Iconfont from '@/components/Iconfont'; import ChartItem from './chart-item'; @@ -20,10 +20,11 @@ import { import { MoreOutlined } from '@ant-design/icons'; import styles from './index.less'; import i18n from '@/i18n'; +import { IConnectionModelState, IConnectionModelType } from '@/models/connection'; interface IProps { className?: string; - setting: GlobalState['settings']; + connectionList: IConnectionDetails[]; dispatch: Dispatch; } @@ -35,8 +36,7 @@ export const initChartItem: IChartItem = { }; function Chart(props: IProps) { - const { className } = props; - + const { className, connectionList } = props; const [dashboardList, setDashboardList] = useState([]); const [curDashboard, setCurDashboard] = useState(); const [openAddDashboard, setOpenAddDashboard] = useState(false); @@ -168,7 +168,7 @@ function Chart(props: IProps) { setCurDashboard(newDashboard); }; - const onDelete = async (chartId: number, rowIndex: number, colIndex: number) => { + const onDeleteChart = async (chartId: number, rowIndex: number, colIndex: number) => { const { id, schema, chartIds } = curDashboard || {}; const chartList: number[][] = JSON.parse(schema || '') || [[]]; @@ -202,14 +202,7 @@ function Chart(props: IProps) {
- {/* { - // throw new Error('Function not implemented.'); - }} - onAdd={() => {}} - > */} - {/* {chartList.map((rowData: number[], rowIndex: number) => ( + {chartList.map((rowData: number[], rowIndex: number) => (
{rowData.map((chartId: number, colIndex: number) => (
@@ -221,13 +214,14 @@ function Chart(props: IProps) { addChartBottom={() => onAddChart('bottom', rowIndex, colIndex)} addChartLeft={() => onAddChart('left', rowIndex, colIndex)} addChartRight={() => onAddChart('right', rowIndex, colIndex)} - onDelete={(id: number) => onDelete(id, rowIndex, colIndex)} + onDelete={(id: number) => onDeleteChart(id, rowIndex, colIndex)} + connectionList={connectionList || []} + // cascaderOption={cascaderOption || []} />
))}
- ))} */} - {/*
*/} + ))}
); @@ -243,11 +237,7 @@ function Chart(props: IProps) { {renderLeft()} -
- { - renderContent() - } -
+
{renderContent()}
({ - settings: global.settings, +export default connect(({ connection }: { connection: IConnectionModelState }) => ({ + connectionList: connection.connectionList, }))(Chart); 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 89b2c98c3..6fd50031c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -1,5 +1,5 @@ import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react'; -import { connect } from 'umi' +import { connect } from 'umi'; import i18n from '@/i18n'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; @@ -20,10 +20,12 @@ interface IProps { className?: string; } -const dvaModel = connect(({ connection, workspace }: { connection: IConnectionModelType, workspace: IWorkspaceModelType }) => ({ - connectionModel: connection, - workspaceModel: workspace -})) +const dvaModel = connect( + ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ + connectionModel: connection, + workspaceModel: workspace, + }), +); const WorkspaceLeft = memo(function (props) { const { className } = props; @@ -54,31 +56,31 @@ interface IProps { function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { let newCascaderOptions: Option[] = []; if (databaseAndSchema?.databases) { - newCascaderOptions = databaseAndSchema?.databases.map(t => { - let schemasList: Option[] = [] + newCascaderOptions = (databaseAndSchema?.databases || []).map((t) => { + let schemasList: Option[] = []; if (t.schemas) { - schemasList = t.schemas.map(t => { + schemasList = t.schemas.map((t) => { return { value: t.name, - label: t.name - } - }) + label: t.name, + }; + }); } return { value: t.name, label: t.name, - children: schemasList - } - }) + children: schemasList, + }; + }); } else if (databaseAndSchema?.schemas) { - newCascaderOptions = databaseAndSchema.schemas.map(t => { + newCascaderOptions = (databaseAndSchema?.schemas || []).map((t) => { return { value: t.name, - label: t.name - } - }) + label: t.name, + }; + }); } - return newCascaderOptions + return newCascaderOptions; } const RenderSelectDatabase = dvaModel(function (props: IProps) { @@ -103,8 +105,8 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { payload: curWorkspaceParams, }); } - return res - }, [databaseAndSchema]) + return res; + }, [databaseAndSchema]); useEffect(() => { if (curWorkspaceParams) { @@ -112,7 +114,7 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { const currentSelectedArr = [databaseSourceName, databaseName, schemaName].filter((t) => t); setCurrentSelectedName(currentSelectedArr.join('/')); } - }, [curWorkspaceParams]) + }, [curWorkspaceParams]); const onChange: any = (valueArr: any, selectedOptions: any) => { let labelArr: string[] = []; @@ -134,11 +136,7 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { }); }; - const dropdownRender = (menus: React.ReactNode) => ( -
- {menus} -
- ); + const dropdownRender = (menus: React.ReactNode) =>
{menus}
; return (
@@ -150,7 +148,9 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { dropdownRender={dropdownRender} >
-
{currentSelectedName || {i18n('workspace.cascader.placeholder')}}
+
+ {currentSelectedName || {i18n('workspace.cascader.placeholder')}}{' '} +
@@ -161,7 +161,7 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) {
*/} ); -}) +}); const RenderTableBox = dvaModel(function (props: any) { const { workspaceModel } = props; @@ -193,7 +193,7 @@ const RenderTableBox = dvaModel(function (props: any) { ); -}) +}); const RenderSaveBox = dvaModel(function (props: any) { const { workspaceModel, dispatch } = props; @@ -206,25 +206,27 @@ const RenderSaveBox = dvaModel(function (props: any) { pageNo: 1, pageSize: 999, status: ConsoleStatus.RELEASE, - ...curWorkspaceParams - } - }) - }, [curWorkspaceParams]) - - return
-
Saved
-
- - { - consoleList?.map((t: IConsole) => { - return
- {t.name} -
- }) - } -
+ ...curWorkspaceParams, + }, + }); + }, [curWorkspaceParams]); + + return ( +
+
Saved
+
+ + {consoleList?.map((t: IConsole) => { + return ( +
+ {t.name} +
+ ); + })} +
+
-
-}) + ); +}); -export default dvaModel(WorkspaceLeft) +export default dvaModel(WorkspaceLeft); diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index d3cdfce79..1727e2dd1 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -1,4 +1,5 @@ import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode } from '@/constants'; + export interface IPageResponse { data: T[]; pageNo: number; @@ -7,7 +8,6 @@ export interface IPageResponse { hasNextPage?: boolean; } - export interface IPageParams { searchKey?: string; pageNo: number; @@ -28,5 +28,11 @@ export interface IConsole { tabOpened?: ConsoleOpenedStatus; } -export type ICreateConsole = Omit +export interface Option { + value: number | string; + label: string; + isLeaf?: boolean; + children?: Option[]; +} +export type ICreateConsole = Omit; diff --git a/chat2db-client/src/utils/database.ts b/chat2db-client/src/utils/database.ts new file mode 100644 index 000000000..dbcefa6e4 --- /dev/null +++ b/chat2db-client/src/utils/database.ts @@ -0,0 +1,35 @@ +import { IWorkspaceModelType } from '@/models/workspace'; +import { Option } from '@/typings/common'; + +export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state']['databaseAndSchema']) { + let newCascaderOptions: Option[] = []; + if (databaseAndSchema.databases) { + newCascaderOptions = (databaseAndSchema?.databases || []).map((t) => { + let schemasList: Option[] = []; + if (t.schemas) { + schemasList = t.schemas.map((t) => { + return { + value: t.name, + label: t.name, + type: 'schema', + }; + }); + } + return { + value: t.name, + label: t.name, + type: 'database', + children: schemasList, + }; + }); + } else if (databaseAndSchema?.schemas) { + newCascaderOptions = (databaseAndSchema?.schemas || []).map((t) => { + return { + value: t.name, + label: t.name, + type: 'schema', + }; + }); + } + return newCascaderOptions; +} From faf18fe7ce510aa4788dc78f9c11264169d73f75 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Thu, 29 Jun 2023 17:02:12 +0800 Subject: [PATCH 0103/1069] feat: Optimize dashboards --- .../components/Console/ChatInput/index.less | 2 +- .../components/Console/MonacoEditor/index.tsx | 74 ++-- .../src/components/Console/index.less | 2 +- .../src/components/Console/index.tsx | 16 +- chat2db-client/src/constants/theme.ts | 15 +- chat2db-client/src/hooks/useTheme.ts | 2 +- chat2db-client/src/i18n/en-us/dashboard.ts | 2 +- chat2db-client/src/i18n/zh-cn/dashboard.ts | 1 + .../main/dashboard/chart-item/index.less | 35 +- .../pages/main/dashboard/chart-item/index.tsx | 378 +++++++++++------- .../pages/main/dashboard/chart/pie/index.tsx | 20 +- .../src/pages/main/dashboard/index.tsx | 1 - chat2db-client/src/service/base.ts | 1 + chat2db-client/src/service/sql.ts | 8 +- chat2db-client/src/utils/check.ts | 2 +- 15 files changed, 331 insertions(+), 228 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 8069eb37a..f558f9447 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -3,7 +3,7 @@ align-items: center; padding: 12px 20px; box-sizing: border-box; - border-bottom: 1px solid var(--color-border); + border-bottom: 1px solid var(--color-border-secondary); } .chat_ai { diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index b6912b5a3..e1bff577e 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -4,7 +4,7 @@ import { useTheme } from '@/hooks'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; const { keywords: SQLKeys } = language; -import { editorDefaultOptions, ThemeType } from '@/constants'; +import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import styles from './index.less'; export type IEditorIns = monaco.editor.IStandaloneCodeEditor; @@ -21,7 +21,7 @@ interface IProps { // onChange?: (v: string, e?: IEditorContentChangeEvent) => void; didMount?: (editor: IEditorIns) => any; onSave?: (value: string) => void; // 快捷键保存的回调 - onExecute?: (value: string) => void; // 快捷键执行的回调 + onExecute?: (value: string) => void; // 快捷键执行的回调 } export interface IExportRefFunction { @@ -31,16 +31,7 @@ export interface IExportRefFunction { } function MonacoEditor(props: IProps, ref: ForwardedRef) { - const { - id, - className, - language = 'sql', - didMount, - options, - isActive, - onSave, - onExecute - } = props; + const { id, className, language = 'sql', didMount, options, isActive, onSave, onExecute } = props; const editorRef = useRef(); const [appTheme] = useTheme(); @@ -48,15 +39,25 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { useEffect(() => { const editorIns = monaco.editor.create(document.getElementById(`monaco-editor-${id}`)!, { ...editorDefaultOptions, - ...options, value: '', language: language, - theme: appTheme.backgroundColor === ThemeType.Light ? 'Default' : 'BlackTheme', + theme: appTheme.backgroundColor === ThemeType.Light ? EditorThemeType.Default : EditorThemeType.BlackTheme, + ...options, }); editorRef.current = editorIns; - didMount && didMount(editorIns); // incase parent component wanna handle editor + didMount && didMount(editorIns); - monaco.editor.defineTheme('BlackTheme', { + monaco.editor.defineTheme(EditorThemeType.Default, { + base: 'vs', + inherit: true, + rules: [{ background: '#15161a' }] as any, + colors: { + 'editor.foreground': '#000000', + 'editor.background': '#fff', //背景色 + }, + }); + + monaco.editor.defineTheme(EditorThemeType.BlackTheme, { base: 'vs-dark', inherit: true, rules: [{ background: '#15161a' }] as any, @@ -67,13 +68,23 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }, }); - monaco.editor.defineTheme('Default', { + monaco.editor.defineTheme(EditorThemeType.DashboardLightTheme, { base: 'vs', inherit: true, rules: [{ background: '#15161a' }] as any, colors: { 'editor.foreground': '#000000', - 'editor.background': '#fff', //背景色 + 'editor.background': '#f8f9fa', //背景色 + }, + }); + + monaco.editor.defineTheme(EditorThemeType.DashboardBlackTheme, { + base: 'vs-dark', + inherit: true, + rules: [{ background: '#15161a' }] as any, + colors: { + 'editor.foreground': '#ffffff', + 'editor.background': '#131418', //背景色 }, }); @@ -94,16 +105,21 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }); editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, (event: Event) => { - const value = getCurrentSelectContent() + const value = getCurrentSelectContent(); onExecute?.(value); }); } - }, [editorRef.current, isActive]) + }, [editorRef.current, isActive]); // 监听主题色变化切换编辑器主题色 useEffect(() => { - monaco.editor.setTheme(appTheme.backgroundColor === ThemeType.Dark ? 'BlackTheme' : 'Default'); - }, [appTheme.backgroundColor]); + const isDark = appTheme.backgroundColor === ThemeType.Dark; + if (options?.theme) { + monaco.editor.setTheme(options.theme); + } else { + monaco.editor.setTheme(isDark ? 'BlackTheme' : 'Default'); + } + }, [appTheme.backgroundColor, options?.theme]); // useEffect(() => { // const _ref = editorRef.current?.onDidChangeModelContent((e) => { @@ -116,12 +132,12 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { useImperativeHandle(ref, () => ({ getCurrentSelectContent, getAllContent, - setValue + setValue, })); const setValue = (text: any, range?: IRangeType) => { - appendMonacoValue(editorRef.current, text, range) - } + appendMonacoValue(editorRef.current, text, range); + }; /** * 获取当前选中的内容 @@ -243,7 +259,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // text 需要添加的文本 // range 添加到的位置 -// 'end' 末尾 +// 'end' 末尾 // 'front' 开头 // 'cover' 覆盖掉原有的文字 // 自定义位置数组 new monaco.Range [] @@ -251,12 +267,12 @@ export type IRangeType = 'end' | 'front' | 'cover' | any; export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'end') => { if (!editor) { - return + return; } const model = editor?.getModel && editor.getModel(editor); // 创建编辑操作,将当前文档内容替换为新内容 let newRange: IRangeType = range; - text = `${text}\n` + text = `${text}\n`; switch (range) { case 'cover': newRange = model.getFullModelRange(); @@ -279,6 +295,6 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e // decorations?: IModelDeltaDecoration[]: 一个数组类型的参数,用于指定插入的文本的装饰。可以用来设置文本的样式、颜色、背景色等。如果不需要设置装饰,可以忽略此参数。 const decorations = [{}]; // 解决新增的文本默认背景色为灰色 editor.executeEdits('setValue', [op], decorations); -} +}; export default forwardRef(MonacoEditor); diff --git a/chat2db-client/src/components/Console/index.less b/chat2db-client/src/components/Console/index.less index 6b8365f08..d69a6c6f8 100644 --- a/chat2db-client/src/components/Console/index.less +++ b/chat2db-client/src/components/Console/index.less @@ -29,7 +29,7 @@ right: 0; display: flex; - margin: 0 36px; + margin: 0 12px; justify-content: space-between; align-items: center; } diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index fc080c2f7..1c86cb4f3 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -4,7 +4,7 @@ import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; import { Button, Spin, message, notification } from 'antd'; import ChatInput from './ChatInput'; -import Editor, { IExportRefFunction, IRangeType } from './MonacoEditor'; +import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; import { format } from 'sql-formatter'; import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; @@ -47,7 +47,6 @@ interface IProps { hasAi2Lang?: boolean; hasSaveBtn?: boolean; value?: string; - onChangeValue?: Function; executeParams: { databaseName?: string; dataSourceId?: number; @@ -56,7 +55,9 @@ interface IProps { schemaName?: string; consoleName?: string; }; - onExecuteSQL: (value: any) => void; + editorOptions: IEditorOptions; + // onSQLContentChange: (v: string) => void; + onExecuteSQL: (result: any, sql: string) => void; workspaceModel: IWorkspaceModelType; dispatch: any; } @@ -70,7 +71,6 @@ function Console(props: IProps) { dispatch, hasSaveBtn = true, value, - onChangeValue, } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); @@ -131,16 +131,16 @@ function Console(props: IProps) { sql: sqlContent, ...executeParams, }; - props.onExecuteSQL?.(undefined); + // props.onExecuteSQL?.(undefined); sqlServer.executeSql(p).then((res) => { - props.onExecuteSQL?.(res); + props.onExecuteSQL?.(res, sqlContent!); // console.log(res) let p: any = { ...executeParams, ddl: sqlContent, }; historyServer.createHistory(p); - }) + }); }; const saveConsole = (value?: string) => { @@ -199,6 +199,8 @@ function Console(props: IProps) { className={hasAiChat ? styles.console_editor_with_chat : styles.console_editor} addAction={addAction} onSave={saveConsole} + options={props.editorOptions} + // onChange={} /> diff --git a/chat2db-client/src/constants/theme.ts b/chat2db-client/src/constants/theme.ts index eacc277c1..31a3382b8 100644 --- a/chat2db-client/src/constants/theme.ts +++ b/chat2db-client/src/constants/theme.ts @@ -1,16 +1,23 @@ export enum ThemeType { Light = 'light', Dark = 'dark', - FollowOs = 'followOs' + FollowOs = 'followOs', } export enum PrimaryColorType { Polar_Green = 'polar-green', Golden_Purple = 'golden-purple', - Polar_Blue = 'polar-blue' + Polar_Blue = 'polar-blue', } export enum LangType { EN_US = 'en-us', - ZH_CN = 'zh-cn' -} \ No newline at end of file + ZH_CN = 'zh-cn', +} + +export enum EditorThemeType { + Default = 'Default', + BlackTheme = 'BlackTheme', + DashboardLightTheme = 'DashboardLightTheme', + DashboardBlackTheme = 'DashboardBlackTheme', +} diff --git a/chat2db-client/src/hooks/useTheme.ts b/chat2db-client/src/hooks/useTheme.ts index 7c49a102b..5b1903ff2 100644 --- a/chat2db-client/src/hooks/useTheme.ts +++ b/chat2db-client/src/hooks/useTheme.ts @@ -29,7 +29,7 @@ export function useTheme(): [ React.Dispatch>, ] { const [appTheme, setAppTheme] = useState(initialTheme()); - + useEffect(() => { const uuid = addColorSchemeListener(setAppTheme); return () => { diff --git a/chat2db-client/src/i18n/en-us/dashboard.ts b/chat2db-client/src/i18n/en-us/dashboard.ts index 46fe2214f..9cabbdd81 100644 --- a/chat2db-client/src/i18n/en-us/dashboard.ts +++ b/chat2db-client/src/i18n/en-us/dashboard.ts @@ -5,5 +5,5 @@ export default { 'dashboard.modal.addTitle': 'Add Dashboard', 'dashboard.modal.name.placeholder': "Please enter dashboard's name.", 'dashboard.delete': 'Delete', - + 'dashboard.editor.cascader.placeholder': 'Please select a connection pool', }; diff --git a/chat2db-client/src/i18n/zh-cn/dashboard.ts b/chat2db-client/src/i18n/zh-cn/dashboard.ts index 015fad528..8a3af1890 100644 --- a/chat2db-client/src/i18n/zh-cn/dashboard.ts +++ b/chat2db-client/src/i18n/zh-cn/dashboard.ts @@ -5,4 +5,5 @@ export default { 'dashboard.modal.addTitle': '新增仪表盘', 'dashboard.modal.name.placeholder': "请输入仪表盘名", 'dashboard.delete': '删除', + 'dashboard.editor.cascader.placeholder': '请选择连接池', }; 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 ceeb67d88..6901e00e1 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -120,17 +120,19 @@ margin-bottom: 40px; } - +.editBlock { + border-radius: var(--border-radius-l-g); + background-color: var(--color-bg-elevated); +} .editorBlock { display: flex; - // border: 1px solid #eee; + border-bottom: 1px solid var(--color-border-secondary); flex-wrap: wrap; } .editor { - flex: 1; - border: 1px solid #eee; + flex: 2; min-width: 320px; min-height: 320px; max-height: 640px; @@ -145,8 +147,25 @@ right: 24px; } -.chartParamsForm{ - min-width: 280px; - padding: 20px; - border: 1px solid #eee; +.chartParamsForm { + 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; + align-items: start; + padding: 24px; +} + +.chartParamsFormTitle { + font-size: 14px; + font-weight: bold; + line-height: 14px; + margin-bottom: 24px; +} + +.editorOptionBlock { + padding: 12px; } \ No newline at end of file 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 5b116f5de..335a12627 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -7,15 +7,17 @@ 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, MenuProps, Select } from 'antd'; -import { initChartItem } from '..'; -import { deleteChart, getChartById } from '@/service/dashboard'; +import { Button, Cascader, Dropdown, Form, MenuProps, notification, Select } from 'antd'; +import { deleteChart, getChartById, updateChart } from '@/service/dashboard'; import { data } from '../../../../../mock/sqlResult.json'; import Console from '@/components/Console'; import Iconfont from '@/components/Iconfont'; -import sqlService, { MetaSchemaVO } from '@/service/sql'; +import sqlService, { IExecuteSqlParams, MetaSchemaVO } 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'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; @@ -49,6 +51,7 @@ function countArrayElements(arr: T[]): { name: T; value: number }[] { interface IChartItemProps { id: number; + isEditing?: boolean; canAddRowItem: boolean; connectionList: IConnectionDetails[]; onDelete?: (id: number) => void; @@ -62,20 +65,21 @@ function ChartItem(props: IChartItemProps) { const { connectionList } = props; const [cascaderOption, setCascaderOption] = useState([]); const [curConnection, setCurConnection] = useState(); - const [consoleParams, setConsoleParams] = useState({}); - - const [chartData, setChartData] = useState(); + const [chartData, setChartData] = useState({}); const [chartMetaData, setChartMetaData] = useState(); - const [consoleValue, setConsoleValue] = useState(); - const [isEditing, setIsEditing] = useState(false); - const [toggle, setToggle] = useState(false); + const [cascaderValue, setCascaderValue] = useState<(string | number)[]>([]); + const [isEditing, setIsEditing] = useState(props.isEditing ?? false); + const [initDDL, setInitDDL] = useState(''); const [form] = Form.useForm(); // 创建一个表单实例 const chartRef = useRef(); - // const consoleParams = useRef({}); + const [appTheme] = useTheme(); + const { id } = props; useEffect(() => { - queryChartData(); + if (id !== undefined) { + queryChartData(); + } }, [id]); useEffect(() => { @@ -95,8 +99,8 @@ function ChartItem(props: IChartItemProps) { if (!curConnection) { return; } - setConsoleParams({ - ...consoleParams, + setChartData({ + ...chartData, dataSourceId: curConnection.id, type: curConnection.type, }); @@ -104,6 +108,10 @@ function ChartItem(props: IChartItemProps) { queryDatabaseAndSchemaList(curConnection.id); }, [curConnection]); + useEffect(() => { + handleChartConfigChange(); + }, [chartData.sqlData]) + const queryDatabaseAndSchemaList = async (dataSourceId: number) => { const res = await sqlService.getDatabaseSchemaList({ dataSourceId }); const dataSource = (cascaderOption || []).find((c) => c.value === dataSourceId); @@ -112,11 +120,111 @@ function ChartItem(props: IChartItemProps) { dataSource.children = handleDatabaseAndSchema(res); setCascaderOption([...cascaderOption]); }; - + /** 根据id请求Chart数据 */ const queryChartData = async () => { const { id } = props; let res = await getChartById({ id }); + const { dataSourceId, databaseName, ddl } = res; setChartData(res); + + // 设置级联value + const cascaderKey = ['dataSourceId', 'databaseName', 'schemaName']; + const cascaderValue = cascaderKey.map((k: string) => res[k]).filter((i) => !!i); + setCascaderValue(cascaderValue); + + // 设置Chart参数,eg ChartType、xAxis、yAxis + const formValue = JSON.parse(res.schema || '{}'); + form.setFieldsValue(formValue); + + if (ddl) { + setInitDDL(ddl || ''); + let p: IExecuteSqlParams = { + sql: res?.ddl ?? '', + dataSourceId, + databaseName, + }; + sqlService.executeSql(p).then((result) => { + let sqlData; + if (result && result[0]) { + sqlData = handleSQLResult2ChartData(result[0]); + } + setChartData({ + ...chartData, + sqlData, + }); + }); + } + }; + + const onExport2Image = () => { + const echartInstance = chartRef.current.getEchartsInstance(); + let img = new Image(); + img.src = echartInstance.getDataURL({ + type: 'png', + devicePixelRatio: 4, + backgroundColor: '#FFF', + }); + img.onload = function () { + let canvas = document.createElement('canvas'); + canvas.width = img.width; + canvas.height = img.height; + let ctx = canvas.getContext('2d'); + ctx?.drawImage(img, 0, 0); + let dataURL = canvas.toDataURL('image/png'); + + var a = document.createElement('a'); + let event = new MouseEvent('click'); + a.download = 'image.png'; + a.href = dataURL; + a.dispatchEvent(event); + }; + }; + + const onDeleteChart = () => { + const { id } = props; + deleteChart({ id }); + props.onDelete && props.onDelete(id); + }; + + const handleSaveChart = async () => { + const params: IChartItem = { + id: props.id, + ...chartData, + schema: JSON.stringify(form.getFieldsValue(true)), + }; + await updateChart(params); + notification.success({ + message: '保存成功', + }); + }; + + const handleChartConfigChange = () => { + const { sqlData = {} } = chartData || {}; + const { chartType, xAxis, yAxis } = form.getFieldsValue(true); + // let xAxisOptions: Array<{ label: string; value: string }> = []; + // let yAxisOptions: Array<{ label: string; value: string }> = []; + + if (chartType === IChartType.Pie) { + const dimension = sqlData[xAxis]; + const { data = [] } = dimension || {}; + const finallyData = countArrayElements(data); + setChartMetaData(finallyData); + } else if (chartType === IChartType.Line) { + const dimensionX = sqlData[xAxis]?.data; + const dimensionY = sqlData[yAxis]?.data; + setChartMetaData({ + xAxis: dimensionX, + yAxis: dimensionY, + }); + } else if (chartType === IChartType.Column) { + const dimensionX = sqlData[xAxis]?.data; + const dimensionY = sqlData[yAxis]?.data; + setChartMetaData({ + xAxis: dimensionX, + yAxis: dimensionY, + }); + } else { + } }; const renderPlusIcon = () => { @@ -170,36 +278,6 @@ function ChartItem(props: IChartItemProps) { } }; - const onExport2Image = () => { - const echartInstance = chartRef.current.getEchartsInstance(); - let img = new Image(); - img.src = echartInstance.getDataURL({ - type: 'png', - devicePixelRatio: 4, - backgroundColor: '#FFF', - }); - img.onload = function () { - let canvas = document.createElement('canvas'); - canvas.width = img.width; - canvas.height = img.height; - let ctx = canvas.getContext('2d'); - ctx?.drawImage(img, 0, 0); - let dataURL = canvas.toDataURL('image/png'); - - var a = document.createElement('a'); - let event = new MouseEvent('click'); - a.download = 'image.png'; - a.href = dataURL; - a.dispatchEvent(event); - }; - }; - - const onDeleteChart = () => { - const { id } = props; - deleteChart({ id }); - props.onDelete && props.onDelete(id); - }; - const renderEmptyBlock = () => { return (
@@ -223,128 +301,125 @@ function ChartItem(props: IChartItemProps) { const options = Object.keys(sqlData).map((i) => ({ label: i, value: i })); return ( -
-
- { - console.log('onExecuteSQL', result); - setChartData({ - ...chartData, - sqlData: handleSQLResult2ChartData(result[0]), - }); - // setResultData(result); - }} - /> - - { - console.log('onChange', selectedOptions); - (selectedOptions || []).forEach((o) => { - if (o.type) { - setConsoleParams({ - ...consoleParams, - [`${o.type}Name`]: o.value, - }); +
+
+
+ { + console.log('onExecuteSQL', result); + let sqlData; + if (result && result[0]) { + sqlData = handleSQLResult2ChartData(result[0]); } - }); + setChartData({ + ...chartData, + ddl: sql, + sqlData, + }); + }} + editorOptions={{ + lineNumbers: 'off', + theme: + appTheme.backgroundColor === ThemeType.Light + ? EditorThemeType.DashboardLightTheme + : EditorThemeType.DashboardBlackTheme, + }} + /> + + { + console.log('onChange', value, selectedOptions); + let p: any = {}; //包含了dataSourceId、databaseName、schemaName + (selectedOptions || []).forEach((o) => { + if (o.type) { + p[`${o.type}Name`] = o.value; + } else { + p.dataSourceId = o.value; + } + }); + setCascaderValue(value); + setChartData({ + ...chartData, + ...p, + }); + }} + className={styles.dataSourceSelect} + placeholder={i18n('dashboard.editor.cascader.placeholder')} + // style={{ width: '100%' }} + /> +
+
+
Charts:
+
+ + + + - - - -
); }; - const handleChartConfigChange = () => { - const { sqlData = {} } = chartData || {}; - const { chartType, xAxis, yAxis } = form.getFieldsValue(true); - // let xAxisOptions: Array<{ label: string; value: string }> = []; - // let yAxisOptions: Array<{ label: string; value: string }> = []; - - if (chartType === IChartType.Pie) { - const dimension = sqlData[xAxis]; - const { data = [] } = dimension || {}; - const finallyData = countArrayElements(data); - setChartMetaData(finallyData); - } else if (chartType === IChartType.Line) { - const dimensionX = sqlData[xAxis]?.data; - const dimensionY = sqlData[yAxis]?.data; - setChartMetaData({ - xAxis: dimensionX, - yAxis: dimensionY, - }); - } else if (chartType === IChartType.Column) { - const dimensionX = sqlData[xAxis]?.data; - const dimensionY = sqlData[yAxis]?.data; - setChartMetaData({ - xAxis: dimensionX, - yAxis: dimensionY, - }); - } else { - } - }; - - const handleToggleData = () => { - if (toggle) { - setChartData({ - ...chartData, - sqlData: undefined, - }); - } else { - const mockData = handleSQLResult2ChartData(data[0]); - setChartData({ - ...chartData, - sqlData: mockData, - }); - } - - setToggle(!toggle); - }; - return (
{renderPlusIcon()}
{chartData?.name}
- {/* */} { + setIsEditing(true); + }, + }, { key: 'Export', label: 'Export to image', @@ -365,7 +440,6 @@ function ChartItem(props: IChartItemProps) {
{chartData?.sqlData || isEditing ? renderChart() : renderEmptyBlock()} {isEditing && renderEditorBlock()} -
{props.id}
); } 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 2e10c43c8..aa538a0b9 100644 --- a/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx @@ -20,30 +20,14 @@ const PieChart = (props: IProps, ref: ForwardedRef<{ getEchartsInstance: Functio }, legend: { orient: 'horizontal', - // top: 'center', - align: 'auto' + align: 'auto', + type: 'scroll', //分页类型 }, - // color:['#45C2E0', '#C1EBDD', '#FFC851','#5A5476','#1869A0','#FF9393'], series: [ { // name: 'Access From', type: 'pie', radius: ['40%', '70%'], - // avoidLabelOverlap: false, - // label: { - // show: false, - // position: 'center', - // }, - // emphasis: { - // label: { - // show: true, - // fontSize: 20, - // fontWeight: 'bold', - // }, - // }, - // labelLine: { - // show: false, - // }, data: props.data, }, ], diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 1decf0d42..7351c6ffa 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -216,7 +216,6 @@ function Chart(props: IProps) { addChartRight={() => onAddChart('right', rowIndex, colIndex)} onDelete={(id: number) => onDeleteChart(id, rowIndex, colIndex)} connectionList={connectionList || []} - // cascaderOption={cascaderOption || []} />
))} diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 51059ec25..61057c62f 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -62,6 +62,7 @@ const errorHandler = (error: ResponseError, errorLevel: IErrorLevel) => { type: 'error', message: status, description: errorText, + placement: 'topRight', }); // message.error(`${status}: ${errorText}`); } diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 37521181d..32339f971 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -10,10 +10,10 @@ export interface IGetListParams extends IPageParams { } export interface IExecuteSqlParams { - sql: string; - dataSourceId: number; - databaseName: string; - consoleId: number; + sql?: string; + dataSourceId?: number; + databaseName?: string; + consoleId?: number; } export interface IExecuteSqlResponse { diff --git a/chat2db-client/src/utils/check.ts b/chat2db-client/src/utils/check.ts index dde6068a7..f003c34ca 100644 --- a/chat2db-client/src/utils/check.ts +++ b/chat2db-client/src/utils/check.ts @@ -1,4 +1,4 @@ import { getLang } from './localStorage'; export const isEn = getLang() === 'en-us'; -export const isZH = getLang() === 'zh-cn'; +export const isZH = getLang() === 'zh-cn'; \ No newline at end of file From 4c137f3345373ccc5c32897f174eb2375e9278a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Thu, 29 Jun 2023 19:25:26 +0800 Subject: [PATCH 0104/1069] =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/index.less | 3 +- .../components/Console/MonacoEditor/index.tsx | 11 ++- .../src/components/Console/index.tsx | 15 ++- .../src/components/CreateConnection/index.tsx | 1 - .../components/DraggableContainer/index.less | 4 +- .../components/DraggableContainer/index.tsx | 4 +- .../src/components/SearchResult/index.less | 30 +++--- .../src/components/SearchResult/index.tsx | 96 +++++++++++-------- chat2db-client/src/components/Tabs/index.less | 70 +++++++++++--- chat2db-client/src/components/Tabs/index.tsx | 51 ++++++---- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/zh-cn/common.ts | 1 + .../components/WorkspaceRight/index.less | 6 +- .../components/WorkspaceRightItem/index.tsx | 5 +- chat2db-client/src/styles/global.less | 7 +- chat2db-client/src/typings/database.ts | 1 + 16 files changed, 185 insertions(+), 121 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index 2b7c220e6..4dadd756f 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -4,7 +4,7 @@ .f-icon-button(); } -.settingIcon{ +.settingIcon { color: var(--custom-color-icon); } @@ -133,7 +133,6 @@ min-height: 400px; padding: 20px; flex: 1; - background-color: var(--color-bg-base); .menuContentTitle { font-size: 16px; diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index b6912b5a3..4ccb61ef6 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -22,6 +22,7 @@ interface IProps { didMount?: (editor: IEditorIns) => any; onSave?: (value: string) => void; // 快捷键保存的回调 onExecute?: (value: string) => void; // 快捷键执行的回调 + defaultValue?: string; } export interface IExportRefFunction { @@ -33,6 +34,7 @@ export interface IExportRefFunction { function MonacoEditor(props: IProps, ref: ForwardedRef) { const { id, + defaultValue, className, language = 'sql', didMount, @@ -44,14 +46,19 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const editorRef = useRef(); const [appTheme] = useTheme(); + const themeMap = { + [ThemeType.Light]: 'Default', + [ThemeType.Dark]: 'BlackTheme', + } + // init useEffect(() => { const editorIns = monaco.editor.create(document.getElementById(`monaco-editor-${id}`)!, { ...editorDefaultOptions, ...options, - value: '', + value: defaultValue, language: language, - theme: appTheme.backgroundColor === ThemeType.Light ? 'Default' : 'BlackTheme', + theme: themeMap[appTheme.backgroundColor] }); editorRef.current = editorIns; didMount && didMount(editorIns); // incase parent component wanna handle editor diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index fc080c2f7..ae4249412 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -10,7 +10,6 @@ import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import { v4 as uuidv4 } from 'uuid'; import styles from './index.less'; -import Loading from '../Loading/Loading'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import Iconfont from '../Iconfont'; import { IWorkspaceModelType } from '@/models/workspace'; @@ -117,17 +116,14 @@ function Console(props: IProps) { }); }; - const executeSQL = () => { - let sqlContent = editorRef?.current?.getCurrentSelectContent(); - if (!sqlContent) { - sqlContent = editorRef?.current?.getAllContent(); - } + const executeSQL = (sql?: string) => { + const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); if (!sqlContent) { return; } - let p = { + let p: any = { sql: sqlContent, ...executeParams, }; @@ -146,7 +142,7 @@ function Console(props: IProps) { const saveConsole = (value?: string) => { const a = editorRef.current?.getAllContent(); - let p = { + let p: any = { id: executeParams.consoleId, status: ConsoleStatus.RELEASE, }; @@ -199,12 +195,13 @@ function Console(props: IProps) { className={hasAiChat ? styles.console_editor_with_chat : styles.console_editor} addAction={addAction} onSave={saveConsole} + onExecute={executeSQL} />
- diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index a6c115675..1ae6e47b9 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -14,7 +14,6 @@ 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 { useTheme } from '@/hooks/useTheme'; const { Option } = Select; diff --git a/chat2db-client/src/components/DraggableContainer/index.less b/chat2db-client/src/components/DraggableContainer/index.less index cfc90fbcf..c3aa4455c 100644 --- a/chat2db-client/src/components/DraggableContainer/index.less +++ b/chat2db-client/src/components/DraggableContainer/index.less @@ -3,7 +3,7 @@ .box { display: flex; .divider { - width: 1px; + width: 0px; position: relative; } @@ -27,7 +27,7 @@ height: 100%; flex-direction: column; .divider { - height: 1px; + height: 0px; width: 100%; position: relative; } diff --git a/chat2db-client/src/components/DraggableContainer/index.tsx b/chat2db-client/src/components/DraggableContainer/index.tsx index d83eb16fa..c964743fd 100644 --- a/chat2db-client/src/components/DraggableContainer/index.tsx +++ b/chat2db-client/src/components/DraggableContainer/index.tsx @@ -4,7 +4,7 @@ import classnames from 'classnames'; interface IProps { className?: string; - children: React.ReactNode[]; + children: any; //TODO: TS,约定接受一个数组,第一项child需要携带ref min?: number; layout?: 'row' | 'column'; callback?: Function; @@ -70,8 +70,6 @@ export default memo(function DraggableContainer({ clientStart: any, volatileBoxXY: any, ) => { - // TODO:当有多个拖动时有bug - return let computedXY = nowClientXY - clientStart; let finalXY = 0; diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 5641c7a2f..154fa7359 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -6,6 +6,11 @@ flex-direction: column; } +.tabs { + padding-top: 10px; + border-bottom: 1px solid var(--color-border); +} + .recordIcon { font-size: 16px; margin-right: 4px; @@ -14,20 +19,13 @@ .resultHeader { flex-shrink: 0; overflow-x: scroll; - padding: 0px 10px; - background-color: var(--color-bg-300); - border-bottom: 1px solid var(--color-border); - + background-color: var(--color-bg-elevated); &::-webkit-scrollbar { display: none; } - & ::after { - height: 0px !important; - } - .statusIcon { - margin-right: 4px; + margin-right: 6px; font-size: 12px; } @@ -71,15 +69,13 @@ left: 0; right: 0; bottom: 0; - z-index: 0; - opacity: 0; + display: none; overflow: auto; background-color: var(--color-bg-100); } .cursorTableBox { - z-index: 1; - opacity: 1; + display: block; } .tableIndex { @@ -90,7 +86,7 @@ display: none; align-items: center; position: absolute; - background-color: var(--color-bg-200); + background-color: var(--color-bg); top: 0; right: 0; bottom: 0; @@ -117,4 +113,8 @@ &:hover .tableHoverBox { display: flex; } -} \ No newline at end of file +} + +.monacoEditor { + margin: -15px; +} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 4c8971607..1480e0fdf 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,6 +1,6 @@ -import React, { memo, useEffect, useState, useRef } from 'react'; +import React, { memo, useEffect, useState, useRef, useMemo } from 'react'; import classnames from 'classnames'; -import Tabs from '@/components/Tabs2'; +import Tabs, { IOption } from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; import LoadingContent from '@/components/Loading/LoadingContent'; @@ -10,6 +10,8 @@ import { StatusType, TableDataType } from '@/constants'; import { formatDate } from '@/utils/date'; import { IManageResultData, ITableHeaderItem } from '@/typings'; import styles from './index.less'; +import i18n from '@/i18n'; +import { v4 as uuidv4 } from 'uuid'; interface IProps { className?: string; @@ -22,11 +24,7 @@ interface DataType { export default memo(function SearchResult({ className, manageResultDataList = [] }) { const [isUnfold, setIsUnfold] = useState(true); - const [currentTab, setCurrentTab] = useState('0'); - - useEffect(() => { - setCurrentTab('0'); - }, [manageResultDataList]); + const [currentTab, setCurrentTab] = useState(0); const renderStatus = (text: string) => { return ( @@ -37,52 +35,62 @@ export default memo(function SearchResult({ className, manageResultDataL ); }; - function onChange(index: string) { + function onChange(index: string | number) { setCurrentTab(index); } - const makerResultHeaderList = () => { - const list: any = []; - manageResultDataList?.map((item, index) => { - list.push({ + const tabs: IOption[] = useMemo(() => { + return manageResultDataList.map((item, index) => { + return { label: ( -
+ <> - 执行结果{index + 1} -
+ {`${i18n('common.text.executionResult')}-${index + 1}`} + ), - key: index, - }); + value: index, + } }); - return list; - }; + }, [manageResultDataList]) + + function onEdit(type: 'add' | 'remove', value?: number | string) { + if (type === 'remove') { + manageResultDataList.filter(t => t.uuid !== value) + } + } return (
- +
- - {manageResultDataList.map((item, index) => { - if (item.success) { - return ( - - ); - } else { - return ; - } - })} - + {manageResultDataList?.map((item, index) => { + if (item.success) { + return ( + + ); + } else { + return ; + } + })}
); @@ -170,7 +178,7 @@ export function TableBox(props: ITableProps) { return (
{dataList !== null ? ( -
+
) : ( )} @@ -182,16 +190,22 @@ export function TableBox(props: ITableProps) { maskClosable={false} footer={ <> - { + {/* { - } + } */} } >
- +
diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index db4da1f42..c0ed1763d 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -1,19 +1,26 @@ @import '../../styles/var.less'; -.tab-focus(){ - &::after{ - position: absolute; +.tab-focus() { + border-radius: 8px 8px 0 0; + background-color: var(--color-bg-base); +} + +.tab-focus-line() { + color: var(--color-primary); + &::after { content: ''; + height: 1px; + position: absolute; left: 0; right: 0; bottom: -1px; - height: 2px; background-color: var(--color-primary); } } .tab { display: flex; + padding: 18px 10px 0px 10px; } .tabList { @@ -25,18 +32,51 @@ display: flex; align-items: center; padding: 0px 10px; - line-height: 26px; + line-height: 30px; + height: 30px; width: 100px; - border: 1px solid var(--color-border); - border-right: 0; - border-bottom: 0; + margin: 0px 2px; cursor: pointer; - &:nth-last-of-type(1) { - border-right: 1px solid var(--color-border); - } - &:hover{ + &:hover { .tab-focus(); - .text{ + .text { + color: var(--color-primary); + } + } + .text { + flex: 1; + width: 0; + .f-single-line(); + } + .icon { + display: flex; + align-items: center; + flex-shrink: 0; + height: 20px; + margin-left: 2px; + cursor: pointer; + i { + font-size: 12px; + } + &:hover { + color: var(--color-primary); + } + } +} + +.tabItemLine { + position: relative; + display: flex; + align-items: center; + padding: 0px 10px; + line-height: 30px; + height: 30px; + width: 100px; + margin: 0px 2px; + cursor: pointer; + &:hover { + .tab-focus-line(); + .text { color: var(--color-primary); } } @@ -65,6 +105,10 @@ .tab-focus(); } +.activeTabLine { + .tab-focus-line(); +} + .rightBox { flex: 1; } diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 7c3480344..05bd8eb04 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -5,8 +5,8 @@ import lodash from 'lodash'; import styles from './index.less'; export interface IOption { - label: string; - value: number; + label: string | React.ReactNode; + value: number | string; } export interface IOnchangeProps { @@ -16,39 +16,40 @@ export interface IOnchangeProps { interface IProps { className?: string; - tabs: IOption[]; + tabs: IOption[] | undefined; activeTab?: number; onChange: (key: IOption['value']) => void; - onEdit: (action: 'add' | 'remove', key?: IOption['value']) => void; + onEdit?: (action: 'add' | 'remove', key?: IOption['value']) => void; + hideAdd?: boolean; + type?: 'line' } export default memo(function Tab(props) { - const { className, tabs, onChange, onEdit, activeTab } = props; - const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs)); - const [internalActiveTab, setInternalActiveTab] = useState(internalTabs[0]?.value); + const { className, tabs, onChange, onEdit, activeTab, hideAdd, type } = props; + const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs || [])); + const [internalActiveTab, setInternalActiveTab] = useState(internalTabs[0]?.value); useEffect(() => { - setInternalActiveTab(activeTab) + setInternalActiveTab(activeTab); }, [activeTab]) useEffect(() => { - setInternalTabs(lodash.cloneDeep(tabs)); + setInternalTabs(lodash.cloneDeep(tabs || [])); }, [tabs]) function deleteTab(data: IOption) { const newTabs = internalTabs?.filter(t => t.value !== data.value); setInternalTabs(newTabs); - onEdit('remove', data.value) + onEdit?.('remove', data.value) } function changeTab(data: IOption) { setInternalActiveTab(data.value); - onChange(data.value) - + onChange(data.value); } function handelAdd() { - onEdit('add') + onEdit?.('add') } return
@@ -56,13 +57,20 @@ export default memo(function Tab(props) { !!internalTabs?.length &&
{ - internalTabs.map(t => { + internalTabs.map((t, index) => { return
-
+
{t.label}
@@ -73,10 +81,13 @@ export default memo(function Tab(props) { }
} -
-
- + { + !hideAdd &&
+
+ +
-
+ } +
}) diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 4af3da0e1..b2c357365 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -28,4 +28,5 @@ export default { 'common.message.addedSuccessfully': 'successfully added', 'common.text.custom': 'custom', 'common.button.delete': 'Delete', + 'common.text.executionResult': 'Result' }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 1b830c45f..9f1ab92b7 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -28,4 +28,5 @@ export default { 'common.message.addedSuccessfully': '添加成功', 'common.text.custom': '自定义', 'common.button.delete': '删除', + 'common.text.executionResult': '执行结果' }; 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 00c15d9a8..cdff634d1 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -16,7 +16,7 @@ .consoleBox { position: absolute; - top: 35px; + top: 48px; left: 0; right: 0; bottom: 0; @@ -24,8 +24,8 @@ } .tabBox { - padding: 6px 6px 0px; - border-bottom: 1px solid var(--color-border); + height: 48px; + background-color: var(--color-bg-elevated); } .activeConsoleBox { diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 282abc510..a8c2a7cea 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -4,7 +4,6 @@ import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import Console, { IAppendValue } from '@/components/Console'; -import LoadingContent from '@/components/Loading/LoadingContent'; import SearchResult from '@/components/SearchResult'; import { DatabaseTypeCode } from '@/constants'; import { IManageResultData } from '@/typings'; @@ -69,9 +68,7 @@ const WorkspaceRightItem = memo(function (props) {
{ showResult && - - - + }
diff --git a/chat2db-client/src/styles/global.less b/chat2db-client/src/styles/global.less index 085fff708..81a0e74ef 100644 --- a/chat2db-client/src/styles/global.less +++ b/chat2db-client/src/styles/global.less @@ -6,12 +6,7 @@ body { height: 100%; color: var(--color-text); font-size: var(--font-size); -} - -:root { - height: 100%; - overflow: overlay; /*滚动条会覆盖在页面之上*/ - background-color: var(--color-bg); + background-color: var(--color-bg-base); } // 修改账号密码自动回填后input背景变色问题 diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 2e2543705..318075c29 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -19,4 +19,5 @@ export interface IManageResultData { message: string; sql: string; success: boolean; + uuid?: string; } From 2b6d23bdcd586903767e9f29ee34a2308e6f9450 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Thu, 29 Jun 2023 19:35:42 +0800 Subject: [PATCH 0105/1069] feat: Optimize dashboard --- .../src/components/Console/ChatInput/index.less | 10 ++++++++++ .../src/components/Console/ChatInput/index.tsx | 15 +++++++++++++-- chat2db-client/src/i18n/en-us/chat.ts | 3 +++ chat2db-client/src/i18n/en-us/index.ts | 4 +++- chat2db-client/src/i18n/zh-cn/chat.ts | 3 +++ chat2db-client/src/i18n/zh-cn/index.ts | 4 +++- .../pages/main/dashboard/chart-item/index.less | 4 ++-- .../src/pages/main/dashboard/chart/pie/index.tsx | 15 ++++++++++++++- 8 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 chat2db-client/src/i18n/en-us/chat.ts create mode 100644 chat2db-client/src/i18n/zh-cn/chat.ts diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index f558f9447..981fa28a9 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -10,4 +10,14 @@ width: 16px; height: 16px; margin-right: 14px; +} + +.suffixBlock{ + +} + +.remainBlock{ + border-radius: 16px; + background-color: var(--color-bg-elevated); + padding: 4px 12px; } \ No newline at end of file diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index e87bb7aa8..8147ccd24 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -2,7 +2,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; import { Input } from 'antd'; -import i18n from '@/i18n'; +import i18n from '@/i18n/'; interface IProps { value?: string; @@ -12,12 +12,22 @@ interface IProps { function ChatInput(props: IProps) { const onPressEnter = (e: any) => { - console.log('press enter', e.target.value); if (e.target.value) { props.onPressEnter && props.onPressEnter(e.target.value); } }; + const renderSuffix = () => { + const remainCnt = 10; + return ( +
+
+ {i18n('chat.input.remain', remainCnt)} +
+
+ ); + }; + return (
@@ -26,6 +36,7 @@ function ChatInput(props: IProps) { bordered={false} placeholder={i18n('workspace.ai.input.placeholder')} onPressEnter={onPressEnter} + suffix={renderSuffix()} />
); diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts new file mode 100644 index 000000000..8542eccf0 --- /dev/null +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -0,0 +1,3 @@ +export default{ + 'chat.input.remain': '{1} remaining' +} \ No newline at end of file diff --git a/chat2db-client/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts index 05bd6d5b7..256cc7a9a 100644 --- a/chat2db-client/src/i18n/en-us/index.ts +++ b/chat2db-client/src/i18n/en-us/index.ts @@ -4,6 +4,7 @@ import menu from './menu'; import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; +import chat from './chat'; export default { lang: 'en', @@ -12,5 +13,6 @@ export default { ...connection, ...workspace, ...menu, - ...dashboard + ...dashboard, + ...chat }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts new file mode 100644 index 000000000..bce077515 --- /dev/null +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -0,0 +1,3 @@ +export default{ + 'chat.input.remain': '剩余 {1} 次' +} \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts index 08878bac4..867621af0 100644 --- a/chat2db-client/src/i18n/zh-cn/index.ts +++ b/chat2db-client/src/i18n/zh-cn/index.ts @@ -5,6 +5,7 @@ import connection from './connection'; import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; +import chat from './chat'; export default { lang: LangType.ZH_CN, @@ -14,5 +15,6 @@ export default { ...workspace, ...menu, ...connection, - ...dashboard + ...dashboard, + ...chat }; 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 6901e00e1..ae2f2ed1f 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -123,6 +123,7 @@ .editBlock { border-radius: var(--border-radius-l-g); background-color: var(--color-bg-elevated); + max-height: 600px; } .editorBlock { @@ -134,8 +135,7 @@ .editor { flex: 2; min-width: 320px; - min-height: 320px; - max-height: 640px; + max-height: 320px; display: flex; flex-direction: column; position: relative; 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 aa538a0b9..3b9c1368b 100644 --- a/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart/pie/index.tsx @@ -23,14 +23,27 @@ const PieChart = (props: IProps, ref: ForwardedRef<{ getEchartsInstance: Functio align: 'auto', type: 'scroll', //分页类型 }, + + series: [ { - // name: 'Access From', type: 'pie', radius: ['40%', '70%'], data: props.data, + // label: { + // show: false, + // position: 'center' + // }, + emphasis: { + label: { + show: true, + fontSize: 16, + fontWeight: 'bold' + } + }, }, ], + }), [props.data], ); From 7a8cfc58d845f2a1d64a6d28403b664985708865 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Thu, 29 Jun 2023 19:47:44 +0800 Subject: [PATCH 0106/1069] add azure openai support --- .../web/api/controller/ai/azure/client/AzureOpenAIClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java index 96c3103d1..893c489b7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java @@ -56,7 +56,7 @@ private static AzureOpenAiStreamClient singleton() { public static void refresh() { String apikey = ""; String apiEndpoint = ""; - String deployId = ""; + String deployId = "gpt-3.5-turbo"; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(AZURE_CHATGPT_ENDPOINT).getData(); if (apiHostConfig != null) { From fcf4adcbebe02fa5fd6ab511cc6ca8b43376912c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Thu, 29 Jun 2023 19:50:43 +0800 Subject: [PATCH 0107/1069] zancun --- .../src/components/SearchResult/index.tsx | 13 ++++++++++--- chat2db-client/src/components/Tabs/index.tsx | 2 +- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 1480e0fdf..32ee60f61 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -25,6 +25,7 @@ interface DataType { export default memo(function SearchResult({ className, manageResultDataList = [] }) { const [isUnfold, setIsUnfold] = useState(true); const [currentTab, setCurrentTab] = useState(0); + const [resultDataList, setResultDataList] = useState([]); const renderStatus = (text: string) => { return ( @@ -55,11 +56,15 @@ export default memo(function SearchResult({ className, manageResultDataL value: index, } }); - }, [manageResultDataList]) + }, []) function onEdit(type: 'add' | 'remove', value?: number | string) { if (type === 'remove') { - manageResultDataList.filter(t => t.uuid !== value) + if (currentTab === value) { + setCurrentTab(0) + } + // manageResultDataList = manageResultDataList.filter(t => t.uuid !== value) + manageResultDataList.splice(value as number, 1); } } @@ -73,6 +78,7 @@ export default memo(function SearchResult({ className, manageResultDataL onChange={onChange} tabs={tabs} className={styles.tabs} + activeTab={currentTab} />
@@ -101,6 +107,7 @@ interface ITableProps { dataList: string[][]; className?: string; data: IManageResultData; + key: number, } interface IViewTableCellData { @@ -109,7 +116,7 @@ interface IViewTableCellData { } export function TableBox(props: ITableProps) { - const { headerList, dataList, className, data, ...rest } = props; + const { headerList, dataList, className, data, key, ...rest } = props; const [columns, setColumns] = useState(); const [tableData, setTableData] = useState(); const [viewTableCellData, setViewTableCellData] = useState(null); diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 05bd8eb04..48ef080d3 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -17,7 +17,7 @@ export interface IOnchangeProps { interface IProps { className?: string; tabs: IOption[] | undefined; - activeTab?: number; + activeTab?: number | string; onChange: (key: IOption['value']) => void; onEdit?: (action: 'add' | 'remove', key?: IOption['value']) => void; hideAdd?: boolean; From da84023e5a4dc2f1833bdf697d2d6dfc2d41e226 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Thu, 29 Jun 2023 20:05:59 +0800 Subject: [PATCH 0108/1069] add azure openai support --- .../controller/config/ConfigController.java | 31 ++++++++++++++++--- .../config/request/AISystemConfigRequest.java | 2 +- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index c0420d6ee..002d60f36 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -14,6 +14,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; import ai.chat2db.server.web.api.controller.config.request.AISystemConfigRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; import ai.chat2db.server.web.api.util.OpenAIClient; @@ -64,10 +65,14 @@ public ActionResult addChatGptSystemConfig(@RequestBody AISystemConfigRequest re SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource) .build(); configService.createOrUpdate(param); - if (AiSqlSourceEnum.OPENAI.getCode().equals(sqlSource)) { - saveOpenAIConfig(request); - } else { - saveRestAIConfig(request); + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); + switch (Objects.requireNonNull(aiSqlSourceEnum)) { + case OPENAI : + saveOpenAIConfig(request); + case RESTAI : + saveRestAIConfig(request); + case AZUREAI : + saveAzureAIConfig(request); } return ActionResult.isSuccess(); } @@ -111,6 +116,24 @@ private void saveRestAIConfig(AISystemConfigRequest request) { RestAIClient.refresh(); } + /** + * 保存azure配置 + * + * @param request + */ + private void saveAzureAIConfig(AISystemConfigRequest request) { + SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY).content( + request.getAzureApiKey()).build(); + configService.createOrUpdate(apikeyParam); + SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT).content( + request.getAzureEndpoint()).build(); + configService.createOrUpdate(endpointParam); + SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID).content( + request.getAzureDeploymentId()).build(); + configService.createOrUpdate(modelParam); + RestAIClient.refresh(); + } + @GetMapping("/system_config/{code}") public DataResult getSystemConfig(@PathVariable("code") String code) { DataResult result = configService.find(code); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java index a5e22d04f..fcc34d886 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java @@ -68,7 +68,7 @@ public class AISystemConfigRequest { private String azureEndpoint; /** - * deployment id of the deployed model + * deploymentId of the deployed model, default gpt-3.5-turbo */ private String azureDeploymentId; } From e36157e5f3bb19b5ea8e6e23e72211070ecdfa74 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 29 Jun 2023 21:09:56 +0800 Subject: [PATCH 0109/1069] feat:dva curTableList --- chat2db-client/src/models/workspace.ts | 26 +++++++++++++++-- .../components/WorkspaceLeft/index.tsx | 28 ++++++++----------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 2f3d88254..f08a45a97 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -1,9 +1,10 @@ import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/utils/localStorage'; import sqlService, { MetaSchemaVO } from '@/service/sql'; import historyService from '@/service/history' -import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; +import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants'; import { Effect, Reducer } from 'umi'; import { ITreeNode, IConsole } from '@/typings'; +import { treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; export type ICurWorkspaceParams = { dataSourceId: number; @@ -21,6 +22,7 @@ export interface IState { // 双击树node节点 doubleClickTreeNodeData: ITreeNode | undefined; consoleList: IConsole[]; + curTableList: ITreeNode[]; } export interface IWorkspaceModelType { @@ -31,10 +33,12 @@ export interface IWorkspaceModelType { setCurWorkspaceParams: Reducer; setDoubleClickTreeNodeData: Reducer; //TS TODO: setConsoleList: Reducer; + setCurTableList: Reducer; }; effects: { fetchDatabaseAndSchema: Effect; fetchGetSavedConsole: Effect; + fetchGetCurTableList: Effect; }; } @@ -45,7 +49,8 @@ const WorkspaceModel: IWorkspaceModelType = { databaseAndSchema: undefined, curWorkspaceParams: getCurrentWorkspaceDatabase(), doubleClickTreeNodeData: undefined, - consoleList: [] + consoleList: [], + curTableList: [] }, reducers: { @@ -78,6 +83,12 @@ const WorkspaceModel: IWorkspaceModelType = { consoleList: payload, }; }, + setCurTableList(state, { payload }) { + return { + ...state, + curTableList: payload, + }; + }, }, effects: { @@ -99,6 +110,17 @@ const WorkspaceModel: IWorkspaceModelType = { payload: res.data, }); }, + *fetchGetCurTableList({ payload }, { put }) { + const res = yield treeConfig[TreeNodeType.TABLES].getChildren!({ + pageNo: 1, + pageSize: 999, + ...payload + }); + yield put({ + type: 'setCurTableList', + payload: res, + }); + }, }, }; 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 6fd50031c..219137fc8 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -164,32 +164,28 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { }); const RenderTableBox = dvaModel(function (props: any) { - const { workspaceModel } = props; - const { curWorkspaceParams } = workspaceModel; - const [initialData, setInitialData] = useState([]); + const { workspaceModel, dispatch } = props; + const { curWorkspaceParams, curTableList } = workspaceModel; useEffect(() => { if (curWorkspaceParams?.dataSourceId) { - getInitialData(); + dispatch({ + type: 'workspace/fetchGetCurTableList', + payload: { + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + } + }) } }, [curWorkspaceParams]); - function getInitialData() { - treeConfig[TreeNodeType.TABLES].getChildren!({ - pageNo: 1, - pageSize: 999, - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - }).then((res) => { - setInitialData(res); - }); - } + console.log(curTableList) return (
Table
- - + +
); From 4ac89e845d7fad935fcad6d85bded4e443d13a42 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 29 Jun 2023 22:32:57 +0800 Subject: [PATCH 0110/1069] =?UTF-8?q?saved=20table=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E6=90=9C=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/WorkspaceLeft/index.less | 21 ++- .../components/WorkspaceLeft/index.tsx | 124 +++++++++++++++--- chat2db-client/src/utils/index.ts | 40 +++++- 3 files changed, 163 insertions(+), 22 deletions(-) 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 348367bb2..2cc2c243c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -88,6 +88,25 @@ } } -.left_box_title { +.leftModuleTitle { + margin-bottom: 10px; + .leftModuleTitleText{ + display: flex; + justify-content: space-between; + align-items: center; + height: 26px; + .modelName{ + font-weight: bold; + } + .iconBox{ + cursor: pointer; + &:hover{ + color: var(--color-primary); + } + } + } + .leftModuleTitleSearch{ + height: 26px; + } } 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 219137fc8..8fea8fa67 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -1,20 +1,17 @@ import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react'; -import { connect } from 'umi'; -import i18n from '@/i18n'; import classnames from 'classnames'; +import i18n from '@/i18n'; +import { connect } from 'umi'; +import { Cascader, Divider, Input } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; -import { Cascader, Divider } from 'antd'; -import historyService from '@/service/history'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import Tree from '../Tree'; -import { treeConfig } from '../Tree/treeConfig'; import { TreeNodeType, ConsoleStatus } from '@/constants'; -import { ITreeNode } from '@/typings'; -import { IConsole } from '@/typings'; +import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; -import { State } from '@/components/StateIndicator'; +import { approximateTreeNode, approximateList } from '@/utils'; interface IProps { className?: string; @@ -166,6 +163,9 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { const RenderTableBox = dvaModel(function (props: any) { const { workspaceModel, dispatch } = props; const { curWorkspaceParams, curTableList } = workspaceModel; + const [searching, setSearching] = useState(false); + const inputRef = useRef(); + const [searchedTableList, setSearchedTableList] = useState(); useEffect(() => { if (curWorkspaceParams?.dataSourceId) { @@ -179,13 +179,57 @@ const RenderTableBox = dvaModel(function (props: any) { } }, [curWorkspaceParams]); - console.log(curTableList) + useEffect(() => { + if (searching) { + inputRef.current!.focus({ + cursor: 'start', + }); + } + }, [searching]) + + function openSearch() { + setSearching(true); + } + + function onBlur() { + if (!inputRef.current.input.value) { + setSearching(false); + setSearchedTableList(undefined); + } + } + + function onChange(value: string) { + setSearchedTableList(approximateTreeNode(curTableList, value)) + } + return (
-
Table
+
+ { + searching ? +
+ } + onBlur={onBlur} + onChange={(e) => onChange(e.target.value)} + allowClear + /> +
+ : +
+
Table
+
openSearch()}> + +
+
+ } +
- +
); @@ -194,6 +238,9 @@ const RenderTableBox = dvaModel(function (props: any) { const RenderSaveBox = dvaModel(function (props: any) { const { workspaceModel, dispatch } = props; const { curWorkspaceParams, consoleList } = workspaceModel; + const [searching, setSearching] = useState(false); + const inputRef = useRef(); + const [searchedList, setSearchedList] = useState(); useEffect(() => { dispatch({ @@ -207,16 +254,61 @@ const RenderSaveBox = dvaModel(function (props: any) { }); }, [curWorkspaceParams]); + + useEffect(() => { + if (searching) { + inputRef.current!.focus({ + cursor: 'start', + }); + } + }, [searching]) + + + function openSearch() { + setSearching(true); + } + + function onBlur() { + if (!inputRef.current.input.value) { + setSearching(false); + setSearchedList(undefined); + } + } + + function onChange(value: string) { + setSearchedList(approximateList(consoleList, value,)) + } + return (
-
Saved
+
+ { + searching ? +
+ } + onBlur={onBlur} + onChange={(e) => onChange(e.target.value)} + allowClear + /> +
+ : +
+
Saved
+
openSearch()}> + +
+
+ } +
- {consoleList?.map((t: IConsole) => { + {(searchedList || consoleList)?.map((t: IConsole) => { return ( -
- {t.name} -
+
); })} diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 9c1bf95e1..44332af57 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -1,5 +1,6 @@ import { ThemeType } from '@/constants'; import { ITreeNode } from '@/typings'; +import lodash from 'lodash' export function getOsTheme() { return window.matchMedia && @@ -80,16 +81,13 @@ export function approximateTreeNode( isDelete = true, ) { if (target) { - const newTree: ITreeNode[] = JSON.parse(JSON.stringify(treeData)); + const newTree: ITreeNode[] = lodash.cloneDeep(treeData || []); newTree.map((item, index) => { // 暂时不递归,只搜索datasource // if(item.children?.length){ // item.children = approximateTreeNode(item.children, target,false); // } - if ( - item.name?.toUpperCase()?.indexOf(target?.toUpperCase()) == -1 && - isDelete - ) { + if (item.name?.toUpperCase()?.indexOf(target?.toUpperCase()) == -1 && isDelete) { delete newTree[index]; } else { item.name = item.name?.replace( @@ -104,6 +102,38 @@ export function approximateTreeNode( } } +// 模糊匹配树并且高亮 +export function approximateList( + data: T[], + target: string, + // @ts-ignore' + keyName: K = 'name', + isDelete = true, +) { + if (target) { + const newData: T[] = lodash.cloneDeep(data || []); + newData.map((item, index) => { + // 暂时不递归,只搜索datasource + // if(item.children?.length){ + // item.children = approximateTreeNode(item.children, target,false); + // } + // @ts-ignore' + if (item[keyName]?.toUpperCase()?.indexOf(target?.toUpperCase()) == -1 && isDelete) { + delete newData[index]; + } else { + // @ts-ignore' + item[keyName] = item[keyName]?.replace( + target, + `${target}`, + ); + } + }); + return newData.filter((i) => i); + } else { + return data; + } +} + // 获取var变量的值 export const callVar = (css: string) => { return getComputedStyle(document.documentElement) From 235dedf9d0f80c9791870f515754dfd666244aad Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Thu, 29 Jun 2023 22:51:15 +0800 Subject: [PATCH 0111/1069] feat: optimize workspace --- chat2db-client/.umirc.ts | 3 +- .../components/Console/ChatInput/index.less | 18 +++++++++-- .../components/Console/ChatInput/index.tsx | 30 +++++++++++++++++-- .../src/components/Console/index.tsx | 11 +++++-- chat2db-client/src/layouts/index.tsx | 5 ++++ chat2db-client/src/service/base.ts | 1 - chat2db-client/src/utils/eventSource.ts | 16 ++++------ chat2db-client/src/utils/localStorage.ts | 17 +++++------ 8 files changed, 71 insertions(+), 30 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 3fefdf91d..373d3c6bd 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -1,5 +1,6 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; +import { getLang } from '@/utils/localStorage'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); console.log(process.env.UMI_ENV); @@ -34,7 +35,7 @@ export default defineConfig({ proxy: { '/api': { target: 'http://127.0.0.1:10821', - changeOrigin: true, + changeOrigin: true }, }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 981fa28a9..96fb98b7d 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -12,12 +12,26 @@ margin-right: 14px; } -.suffixBlock{ +.suffixBlock { + display: flex; + align-items: center; +} + +.tableSelectBlock { + margin-right: 8px; + cursor: pointer; + padding: 4px; + border-radius: 4px 12px; + + &:hover { + background-color: var(--color-bg-elevated); + } } -.remainBlock{ +.remainBlock { border-radius: 16px; background-color: var(--color-bg-elevated); padding: 4px 12px; + } \ No newline at end of file diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 8147ccd24..62996dba7 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -1,13 +1,17 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; -import { Input } from 'antd'; +import { Checkbox, Dropdown, Input, Popover } from 'antd'; import i18n from '@/i18n/'; +import Iconfont from '@/components/Iconfont'; interface IProps { value?: string; result?: string; + tables?: string[]; + selectedTables?: string[]; onPressEnter: (value: string) => void; + onSelectTables: (tables: (string | number | boolean)[]) => void; } function ChatInput(props: IProps) { @@ -17,13 +21,33 @@ function ChatInput(props: IProps) { } }; + const renderSelectTable = () => { + const { tables, selectedTables, onSelectTables } = props; + return tables && tables.length ? ( +
+ { + onSelectTables && onSelectTables(v); + }} + /> +
+ ) : ( +
暂无表
+ ); + }; + const renderSuffix = () => { const remainCnt = 10; return (
-
- {i18n('chat.input.remain', remainCnt)} +
+ + +
+
{i18n('chat.input.remain', remainCnt)}
); }; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 9c4a53fe7..11a50dcf3 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -56,7 +56,7 @@ interface IProps { }; editorOptions: IEditorOptions; // onSQLContentChange: (v: string) => void; - onExecuteSQL: (result: any, sql: string) => void; + onExecuteSQL: (result: any, sql?: string) => void; workspaceModel: IWorkspaceModelType; dispatch: any; } @@ -83,8 +83,14 @@ function Console(props: IProps) { }, [appendValue]); const onPressChatInput = (value: string) => { + const { dataSourceId, databaseName, schemaName } = executeParams; const params = formatParams({ message: value, + dataSourceId, + databaseName, + schemaName, + //TODO: + promptType: IPromptType.NL_2_SQL, }); // setIsLoading(true); @@ -95,6 +101,7 @@ function Console(props: IProps) { if (isEOF) { closeEventSource(); setIsLoading(false); + console.log('chatResult', chatResult.current); return; } chatResult.current += JSON.parse(message).content; @@ -127,7 +134,7 @@ function Console(props: IProps) { sql: sqlContent, ...executeParams, }; - // props.onExecuteSQL?.(undefined); + props.onExecuteSQL?.(undefined); sqlServer.executeSql(p).then((res) => { props.onExecuteSQL?.(res, sqlContent!); // console.log(res) diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 26c392057..bdd857dde 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -59,6 +59,11 @@ function AppContainer() { const [initEnd, setInitEnd] = useState(false); const [appTheme, setAppTheme] = useTheme(); + useEffect(() => { + let date = new Date('2030-12-30 12:30:00').toUTCString(); + document.cookie = `CHAT2DB.LOCALE=${getLang()};Expires=${date}`; + }, []); + useEffect(() => { InjectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); }, [token]); diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 61057c62f..9226fa5fe 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -87,7 +87,6 @@ request.interceptors.request.use((url, options) => { if (localStorage.getItem('DBHUB')) { myOptions.headers.DBHUB = localStorage.getItem('DBHUB'); } - myOptions.headers.lang = getLang() || 'en-us'; return { options: myOptions, }; diff --git a/chat2db-client/src/utils/eventSource.ts b/chat2db-client/src/utils/eventSource.ts index 5a2adbd4c..f89e756b9 100644 --- a/chat2db-client/src/utils/eventSource.ts +++ b/chat2db-client/src/utils/eventSource.ts @@ -1,11 +1,6 @@ import { EventSourcePolyfill } from 'event-source-polyfill'; -const connectToEventSource = (params: { - url: string; - uid: string; - onMessage: Function; - onError: Function; -}) => { +const connectToEventSource = (params: { url: string; uid: string; onMessage: Function; onError: Function }) => { const { url, uid, onMessage, onError } = params; if (!url || !onMessage || !onError) { @@ -17,13 +12,12 @@ const connectToEventSource = (params: { headers: { uid, DBHUB, - } - } - const eventSource = new EventSourcePolyfill(`${window._BaseURL}${url}`, { - p - }); + }, + }; + const eventSource = new EventSourcePolyfill(`${window._BaseURL}${url}`, p); eventSource.onmessage = (event) => { + console.log('onmessage', event); onMessage(event.data); }; diff --git a/chat2db-client/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts index 9370909d2..9258d463a 100644 --- a/chat2db-client/src/utils/localStorage.ts +++ b/chat2db-client/src/utils/localStorage.ts @@ -2,7 +2,7 @@ import { ThemeType, PrimaryColorType, LangType } from '@/constants'; import { ICurWorkspaceParams } from '@/models/workspace'; export function getLang(): LangType { - return localStorage.getItem('lang') as LangType; + return (localStorage.getItem('lang') as LangType) || 'en-us'; } export function setLang(lang: LangType) { @@ -18,10 +18,7 @@ export function setTheme(theme: ThemeType) { } export function getPrimaryColor(): PrimaryColorType { - return ( - (localStorage.getItem('primary-color') as PrimaryColorType) || - PrimaryColorType.Polar_Blue - ); + return (localStorage.getItem('primary-color') as PrimaryColorType) || PrimaryColorType.Polar_Blue; } export function setPrimaryColor(primaryColor: PrimaryColorType) { @@ -36,15 +33,15 @@ export function getCurrentWorkspaceDatabase(): ICurWorkspaceParams { const curWorkspaceParams = localStorage.getItem('current-workspace-database'); if (curWorkspaceParams) { - return JSON.parse(curWorkspaceParams) + return JSON.parse(curWorkspaceParams); } return {} as ICurWorkspaceParams; -} +} export function getCurConnection() { - const curConnection = localStorage.getItem('cur-connection') + const curConnection = localStorage.getItem('cur-connection'); if (curConnection) { - return JSON.parse(curConnection) + return JSON.parse(curConnection); } - return undefined + return undefined; } From 3fc663a5b8c292841757132e7d5d64d989fcff09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Fri, 30 Jun 2023 14:32:20 +0800 Subject: [PATCH 0112/1069] =?UTF-8?q?feat:=E5=88=A0=E9=99=A4=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CreateConnection/config/enum.ts | 2 +- .../components/CreateConnection/index.less | 2 +- .../src/components/Tabs2/index.less | 50 ----- chat2db-client/src/components/Tabs2/index.tsx | 37 ---- chat2db-client/src/i18n/en-us/common.ts | 4 +- chat2db-client/src/i18n/zh-cn/common.ts | 4 +- chat2db-client/src/models/workspace.ts | 6 +- .../Tree/TreeNodeRightClick/index.less | 9 +- .../Tree/TreeNodeRightClick/index.tsx | 194 ++++++++++++------ .../main/workspace/components/Tree/index.less | 12 ++ .../main/workspace/components/Tree/index.tsx | 88 ++++---- .../workspace/components/Tree/treeConfig.tsx | 20 +- .../components/WorkspaceLeft/index.less | 44 +++- .../components/WorkspaceLeft/index.tsx | 7 +- .../src/pages/main/workspace/index.tsx | 33 +-- chat2db-client/src/typings/tree.ts | 1 + 16 files changed, 276 insertions(+), 237 deletions(-) delete mode 100644 chat2db-client/src/components/Tabs2/index.less delete mode 100644 chat2db-client/src/components/Tabs2/index.tsx diff --git a/chat2db-client/src/components/CreateConnection/config/enum.ts b/chat2db-client/src/components/CreateConnection/config/enum.ts index 5218d8036..8be8dc82f 100644 --- a/chat2db-client/src/components/CreateConnection/config/enum.ts +++ b/chat2db-client/src/components/CreateConnection/config/enum.ts @@ -18,7 +18,7 @@ export enum SSHAuthenticationType { // 树右键支持的功能 export enum OperationColumn { ShiftOut = 'shiftOut', // 移出数据源 - REFRESH = 'refresh', // 刷新各级菜单 + Refresh = 'refresh', // 刷新各级菜单 CreateTable = 'createTable', //创建表 CreateConsole = 'createConsole', // 新建console DeleteTable = 'deleteTable', // 删除表 diff --git a/chat2db-client/src/components/CreateConnection/index.less b/chat2db-client/src/components/CreateConnection/index.less index caac54a2f..56915076f 100644 --- a/chat2db-client/src/components/CreateConnection/index.less +++ b/chat2db-client/src/components/CreateConnection/index.less @@ -112,7 +112,7 @@ .extendTable { max-height: 300px; - overflow-y: scroll; + overflow-y: auto; input { border: 0px; } diff --git a/chat2db-client/src/components/Tabs2/index.less b/chat2db-client/src/components/Tabs2/index.less deleted file mode 100644 index 77e3bd7bf..000000000 --- a/chat2db-client/src/components/Tabs2/index.less +++ /dev/null @@ -1,50 +0,0 @@ -@import '../../styles/var.less'; - -.box { - display: flex; - position: relative; - .extra { - flex: 1; - } - &::after { - position: absolute; - content: ''; - bottom: 1px; - left: 0; - width: 100%; - height: 1px; - background-color: var(--color-border); - } - :global { - .custom-tabs { - width: 100%; - flex-shrink: 0; - } - .custom-tabs-tab { - margin: 0px 10px; - font-size: 12px; - } - .custom-tabs-nav { - margin: 0 !important; - } - .custom-tabs-nav::before { - border: 0; - border-bottom: 0 !important; - } - .custom-tabs-tab { - user-select: none; - padding: 5px 10px; - margin: 0px 0px 5px 0px; - border-radius: 5px; - &:hover { - color: var(--color-text-85); - background-color: var(--color-bg-hover); - } - } - .custom-tabs-tab-active { - &:hover { - background-color: transparent; - } - } - } -} diff --git a/chat2db-client/src/components/Tabs2/index.tsx b/chat2db-client/src/components/Tabs2/index.tsx deleted file mode 100644 index 9f6cbd180..000000000 --- a/chat2db-client/src/components/Tabs2/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { memo, ReactNode, useState } from 'react'; -import styles from './index.less'; -import classnames from 'classnames'; -import { Tabs as AntdTabs } from 'antd'; - -export interface ITab { - label: ReactNode; - key: string; -} - -interface IProps { - className?: string; - tabs: ITab[]; - currentTab?: string; - onChange: (key: string, index: number) => void; - extra?: React.ReactNode -} - -export default memo(function Tabs({ className, tabs, currentTab, onChange, extra }: IProps) { - function myChange(key: string) { - const index = tabs.findIndex(t => { - return t.key === key - }) - onChange(key, index) - } - - return
- -
- {extra} -
-
-}) diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index b2c357365..a13baba78 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -28,5 +28,7 @@ export default { 'common.message.addedSuccessfully': 'successfully added', 'common.text.custom': 'custom', 'common.button.delete': 'Delete', - 'common.text.executionResult': 'Result' + 'common.text.executionResult': 'Result', + 'common.tips.deleteTable': 'Are you sure delete this Table?', + 'common.text.tableName': 'Table Name' }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 9f1ab92b7..d3a037b8d 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -28,5 +28,7 @@ export default { 'common.message.addedSuccessfully': '添加成功', 'common.text.custom': '自定义', 'common.button.delete': '删除', - 'common.text.executionResult': '执行结果' + 'common.text.executionResult': '执行结果', + 'common.tips.deleteTable': '你确定好删除这张表吗?', + 'common.text.tableName': '表名称' }; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index f08a45a97..0852a3b62 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -22,7 +22,7 @@ export interface IState { // 双击树node节点 doubleClickTreeNodeData: ITreeNode | undefined; consoleList: IConsole[]; - curTableList: ITreeNode[]; + curTableList: ITreeNode[] | undefined; } export interface IWorkspaceModelType { @@ -111,6 +111,10 @@ const WorkspaceModel: IWorkspaceModelType = { }); }, *fetchGetCurTableList({ payload }, { put }) { + yield put({ + type: 'setCurTableList', + payload: undefined, + }); const res = yield treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less index 4bfb1bae7..55931146e 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less @@ -1,4 +1,11 @@ @import '../../../../../../styles/var.less'; -.box { +.operationItem { + display: flex; + align-items: center; + .operationIcon { + } + .operationTitle { + margin-left: 14px; + } } diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index fbbd1511e..ac80eb22f 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -1,21 +1,23 @@ -import React, { memo, useContext, useState } from 'react'; +import React, { memo, useContext, useMemo, useState } from 'react'; +import i18n from '@/i18n'; import classnames from 'classnames'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; -import { MenuProps, message } from 'antd'; -import { Modal, Input } from 'antd'; +import { MenuProps, message, Modal, Input, Dropdown, notification } from 'antd'; +import { ExclamationCircleFilled } from '@ant-design/icons'; // import { Menu } from 'antd'; -import Menu, { IMenu, MenuItem } from '@/components/Menu'; -import { IOperationData } from '@/components/OperationTableModal'; -import { TreeNodeType, DatabaseTypeCode } from '@/utils/constants'; -import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/components/Tree/treeConfig'; -import { ITreeNode } from '@/types'; -import { DatabaseContext } from '@/context/database'; +// import Menu, { IMenu, MenuItem } from '@/components/Menu'; +// import { IOperationData } from '@/components/OperationTableModal'; +import { TreeNodeType, DatabaseTypeCode } from '@/constants'; +import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; +import { ITreeNode } from '@/typings'; +// import { DatabaseContext } from '@/context/database'; import connectionServer from '@/service/connection'; import mysqlServer from '@/service/sql'; import { OperationColumn } from '../treeConfig'; -import { dataSourceFormConfigs } from '@/config/dataSource'; -import { IConnectionConfig } from '@/config/types'; +import { dataSourceFormConfigs } from '@/components/CreateConnection/config/dataSource'; +import { IConnectionConfig } from '@/components/CreateConnection/config/types'; +import { IWorkspaceModelType } from '@/models/workspace'; type MenuItem = Required['items'][number]; @@ -24,7 +26,8 @@ export type IProps = { className?: string; setIsLoading: (value: boolean) => void; data: ITreeNode; - setTreeData: Function; + dispatch: any; + workspaceModel: IWorkspaceModelType['state'] } export interface IOperationColumnConfigItem { @@ -34,17 +37,20 @@ export interface IOperationColumnConfigItem { } function TreeNodeRightClick(props: IProps) { - const { className, setTreeData, data, setIsLoading } = props; - const { setCreateConsoleDialog, setOperationDataDialog, setNeedRefreshNodeTree, setEditDataSourceData } = useContext(DatabaseContext); + const { className, data, setIsLoading, dispatch, workspaceModel } = props; + // const { setCreateConsoleDialog, setOperationDataDialog, setNeedRefreshNodeTree, setEditDataSourceData } = useContext(DatabaseContext); const [verifyDialog, setVerifyDialog] = useState(); const [verifyTableName, setVerifyTableName] = useState(''); - const treeNodeConfig: ITreeConfigItem = treeConfig[data.nodeType] + const [modalApi, modelDom] = Modal.useModal(); + const [notificationApi, notificationDom] = notification.useNotification(); + const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType] const { getChildren, operationColumn } = treeNodeConfig; + const { curWorkspaceParams } = workspaceModel; const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { - return t.type === data.dataType + return t.type === data.extraParams?.databaseType })! const OperationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { - [OperationColumn.REFRESH]: (data) => { + [OperationColumn.Refresh]: (data) => { return { text: '刷新', icon: '\uec08', @@ -63,7 +69,7 @@ function TreeNodeRightClick(props: IProps) { nodeData: data } if (operationData.type === 'export') { - setOperationDataDialog(operationData); + // setOperationDataDialog(operationData); } } } @@ -75,7 +81,7 @@ function TreeNodeRightClick(props: IProps) { handle: () => { connectionServer.remove({ id: +data.key }).then(res => { treeConfig[TreeNodeType.DATA_SOURCES]?.getChildren!({} as any).then(res => { - setTreeData(res); + // setTreeData(res); }) }) } @@ -90,7 +96,7 @@ function TreeNodeRightClick(props: IProps) { type: 'new', nodeData: data } - setOperationDataDialog(operationData) + // setOperationDataDialog(operationData) } } }, @@ -100,13 +106,13 @@ function TreeNodeRightClick(props: IProps) { icon: '\ue619', handle: () => { console.log(data) - setCreateConsoleDialog({ - dataSourceId: data.dataSourceId!, - dataSourceName: data.dataSourceName!, - databaseName: data.databaseName!, - schemaName: data.schemaName!, - databaseType: data.dataType! as DatabaseTypeCode - }) + // setCreateConsoleDialog({ + // dataSourceId: data.dataSourceId!, + // dataSourceName: data.dataSourceName!, + // databaseName: data.databaseName!, + // schemaName: data.schemaName!, + // databaseType: data.dataType! as DatabaseTypeCode + // }) } } }, @@ -115,13 +121,41 @@ function TreeNodeRightClick(props: IProps) { text: '删除表', icon: '\ue6a7', handle: () => { - setCreateConsoleDialog({ - dataSourceId: data.dataSourceId!, - dataSourceName: data.dataSourceName!, - databaseName: data.databaseName!, - schemaName: data.schemaName!, - databaseType: data.dataType! as DatabaseTypeCode - }) + modalApi.confirm({ + title: i18n('common.tips.deleteTable'), + icon: , + content: `${i18n('common.text.tableName')}:${data.name}`, + okText: i18n('common.button.delete'), + okType: 'danger', + cancelText: i18n('common.button.cancel'), + onOk() { + let p: any = { + ...data.extraParams, + tableName: data.name, + } + mysqlServer.deleteTable(p).then(res => { + notificationApi.success( + { + message: '删除成功', + } + ) + dispatch({ + type: 'workspace/fetchGetCurTableList', + payload: { + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + } + }) + }) + }, + }); + // setCreateConsoleDialog({ + // dataSourceId: data.dataSourceId!, + // dataSourceName: data.dataSourceName!, + // databaseName: data.databaseName!, + // schemaName: data.schemaName!, + // databaseType: data.dataType! as DatabaseTypeCode + // }) } } }, @@ -130,19 +164,35 @@ function TreeNodeRightClick(props: IProps) { text: '编辑数据源', icon: '\ue623', handle: () => { - setEditDataSourceData({ - dataType: data.dataType as any, - id: +data.key - }) + // setEditDataSourceData({ + // dataType: data.dataType as any, + // id: +data.key + // }) } } - } + }, + [OperationColumn.Top]: (data) => { + return { + text: data.pinned ? '取消置顶' : '置顶', + icon: data.pinned ? '\ue61d' : '\ue627', + handle: () => { + handelTop() + } + } + }, + } + + function handelTop() { + data.pinned } function refresh() { data.children = []; setIsLoading(true); - getChildren?.(data).then(res => { + getChildren?.({ + ...data, + ...data.extraParams + }).then(res => { setTimeout(() => { data.children = res; setIsLoading(false); @@ -150,14 +200,6 @@ function TreeNodeRightClick(props: IProps) { }) } - function closeMenu() { - // TODO: 关闭下拉弹窗 有木有更好的方法 - const customDropdown: any = document.getElementsByClassName('custom-dropdown'); - for (let i = 0; i < customDropdown.length; i++) { - customDropdown[i].classList.add('custom-dropdown-hidden') - } - } - function handleOk() { let p = { tableName: verifyTableName, @@ -167,11 +209,11 @@ function TreeNodeRightClick(props: IProps) { if (verifyTableName === data.tableName) { mysqlServer.deleteTable(p).then(res => { setVerifyDialog(false); - setNeedRefreshNodeTree({ - databaseName: data.databaseName, - dataSourceId: data.dataSourceId, - nodeType: TreeNodeType.TABLES - }) + // setNeedRefreshNodeTree({ + // databaseName: data.databaseName, + // dataSourceId: data.dataSourceId, + // nodeType: TreeNodeType.TABLES + // }) }) } else { message.error('输入的表名与要删除的表名不一致,请再次确认') @@ -195,20 +237,54 @@ function TreeNodeRightClick(props: IProps) { return newOperationColumn } + const dropdowns = useMemo(() => { + if (dataSourceFormConfig) { + return excludeSomeOperation().map((t, i) => { + const concrete = OperationColumnConfig[t](data); + return { + key: i, + label:
+ +
+ {concrete.text} +
+
, + onClick: concrete.handle + } + }); + } + return [] + }, [dataSourceFormConfig]) + return <> -
- + {modelDom} + {notificationDom} + { + !!dropdowns.length && + +
+ +
+
+ } + {/*
+
{ excludeSomeOperation()?.map((item, index) => { const concrete = OperationColumnConfig[item](data); - return { closeMenu(); concrete.handle(); }}> + return
{ concrete.handle(); }}> {concrete.text} - +
}) } -
-
+
+
*/} { const recognizeIcon = (treeNodeType: TreeNodeType) => { if (treeNodeType === TreeNodeType.DATA_SOURCE) { - return databaseMap[data.databaseType!]?.icon + return databaseMap[data.extraParams?.databaseType!]?.icon } else { return switchIcon[treeNodeType]?.[showChildren ? 'unfoldIcon' : 'icon'] || switchIcon[treeNodeType]?.icon } @@ -174,44 +175,53 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { } return show ? <> - -
-
- { - indentArr.map((item, i) => { - return
- }) - } -
-
- { - !data.isLeaf && -
- { - isLoading - ? -
- -
- : - - } - {/* */} -
- } -
-
- -
-
-
- {data.treeNodeType === TreeNodeType.COLUMN &&
{data.columnType}
} -
+ {/* + */} +
+
+ { + indentArr.map((item, i) => { + return
+ }) + } +
+
+ { + !data.isLeaf && +
+ { + isLoading + ? +
+ +
+ : + + } + {/* */} +
+ } +
+
+
+
+
+ {data.treeNodeType === TreeNodeType.COLUMN &&
{data.columnType}
} +
+
+
+
- +
{ data.children?.map((item: any, i: number) => { return +
{ searching ? @@ -228,7 +227,7 @@ const RenderTableBox = dvaModel(function (props: any) {
}
- +
@@ -280,7 +279,7 @@ const RenderSaveBox = dvaModel(function (props: any) { } return ( -
+
{ searching ? diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index e78d524be..90eb50608 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -3,39 +3,22 @@ import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; -import { reducer, initState, IState } from './context'; interface IProps { className?: string; } -interface IContext { - state: IState; - dispatch: any; -} -export const WorkspaceContext = React.createContext({ - state: initState, - dispatch: () => { }, -}); - -export const useReducerContext = () => { - return useContext(WorkspaceContext); -}; - export default memo(function workspace(props) { const draggableRef = useRef(); - const [state, dispatch] = useReducer(reducer, initState); return ( - - -
- -
-
- -
-
-
+ +
+ +
+
+ +
+
); }); diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index e594642b6..829bb462c 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -17,4 +17,5 @@ export interface ITreeNode { children?: ITreeNode[]; columnType?: string; extraParams?: IExtraParams; + pinned?: boolean; } \ No newline at end of file From 85b124a1a5a5e48cddc88361b3d4fd906c9d3c11 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Fri, 30 Jun 2023 17:31:34 +0800 Subject: [PATCH 0113/1069] merge --- .../components/Console/ChatInput/index.tsx | 4 +- .../components/Console/MonacoEditor/index.tsx | 2 +- .../src/components/Console/index.less | 22 +-- .../src/components/Console/index.tsx | 129 +++++++++++------- .../src/components/SearchResult/index.tsx | 16 ++- chat2db-client/src/models/workspace.ts | 28 ++-- .../components/WorkspaceRightItem/index.tsx | 65 ++++----- chat2db-client/src/utils/common.ts | 21 ++- 8 files changed, 166 insertions(+), 121 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 62996dba7..d871891a4 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -11,7 +11,7 @@ interface IProps { tables?: string[]; selectedTables?: string[]; onPressEnter: (value: string) => void; - onSelectTables: (tables: (string | number | boolean)[]) => void; + onSelectTables?: (tables: string[]) => void; } function ChatInput(props: IProps) { @@ -25,7 +25,9 @@ function ChatInput(props: IProps) { const { tables, selectedTables, onSelectTables } = props; return tables && tables.length ? (
+ { diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 9aca08ed3..38bb195e3 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -273,7 +273,7 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e const model = editor?.getModel && editor.getModel(editor); // 创建编辑操作,将当前文档内容替换为新内容 let newRange: IRangeType = range; - text = `${text}\n`; + text = `${text}`; switch (range) { case 'cover': newRange = model.getFullModelRange(); diff --git a/chat2db-client/src/components/Console/index.less b/chat2db-client/src/components/Console/index.less index d69a6c6f8..85356cd73 100644 --- a/chat2db-client/src/components/Console/index.less +++ b/chat2db-client/src/components/Console/index.less @@ -14,15 +14,15 @@ } -.console_editor { +.consoleEditor { height: 100%; } -.console_editor_with_chat { +.consoleEditorWithChat { height: calc(100% - 56px); } -.console_options_wrapper { +.consoleOptionsWrapper { position: absolute; bottom: 8px; left: 0; @@ -34,23 +34,29 @@ align-items: center; } -.console_options_left{ +.consoleOptionsLeft { display: flex; align-items: center; } -.run_button{ +.aiBlock { + font-size: 14px; + line-height: 20px; +} + +.runButton { display: flex; width: 70px; align-items: center; margin-right: 20px; height: 28px; - i{ + + i { margin-right: 4px; } } -.save_button{ +.saveButton { width: 70px; height: 28px; -} +} \ No newline at end of file diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 11a50dcf3..0b55e9240 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -2,17 +2,17 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { connect } from 'umi'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; -import { Button, Spin, message, notification } from 'antd'; +import { Button, Spin, message, notification, Drawer, Modal } from 'antd'; import ChatInput from './ChatInput'; import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; import { format } from 'sql-formatter'; import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import { v4 as uuidv4 } from 'uuid'; -import styles from './index.less'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import Iconfont from '../Iconfont'; -import { IWorkspaceModelType } from '@/models/workspace'; +import { ITreeNode } from '@/typings'; +import styles from './index.less'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -44,8 +44,10 @@ interface IProps { hasAiChat: boolean; /** 是否可以开启SQL转到自然语言的相关ai操作 */ hasAi2Lang?: boolean; + /** 是否有 */ hasSaveBtn?: boolean; value?: string; + tables: ITreeNode[]; executeParams: { databaseName?: string; dataSourceId?: number; @@ -54,27 +56,22 @@ interface IProps { schemaName?: string; consoleName?: string; }; - editorOptions: IEditorOptions; + editorOptions?: IEditorOptions; // onSQLContentChange: (v: string) => void; onExecuteSQL: (result: any, sql?: string) => void; - workspaceModel: IWorkspaceModelType; - dispatch: any; + onConsoleSave: () => void; } function Console(props: IProps) { - const { - hasAiChat = true, - executeParams, - appendValue, - isActive, - dispatch, - hasSaveBtn = true, - value, - } = props; + const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); + const [selectedTables, setSelectedTables] = useState([]); const [isLoading, setIsLoading] = useState(false); + const [aiContent, setAiContent] = useState(''); + const [isAiDrawerOpen, setIsAiDrawerOpen] = useState(false); + const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); useEffect(() => { if (appendValue) { @@ -82,37 +79,59 @@ function Console(props: IProps) { } }, [appendValue]); - const onPressChatInput = (value: string) => { + const tableListName = useMemo(() => (props.tables || []).map((t) => t.name), [props.tables]); + + const handleAiChat = (content: string, promptType: IPromptType) => { const { dataSourceId, databaseName, schemaName } = executeParams; + const isNL2SQL = promptType === IPromptType.NL_2_SQL; + if (isNL2SQL) { + setIsLoading(true); + } else { + setIsAiDrawerOpen(true); + setIsAiDrawerLoading(true); + } const params = formatParams({ - message: value, + message: content, + promptType, dataSourceId, databaseName, schemaName, - //TODO: - promptType: IPromptType.NL_2_SQL, + tableNames: selectedTables, }); - // setIsLoading(true); - const handleMessage = (message: string) => { + setIsLoading(false); + try { const isEOF = message === '[DONE]'; if (isEOF) { closeEventSource(); setIsLoading(false); - console.log('chatResult', chatResult.current); + if (isNL2SQL) { + editorRef?.current?.setValue('\n\n\n'); + } else { + setIsAiDrawerLoading(false); + chatResult.current += '\n\n\n'; + setAiContent(chatResult.current); + chatResult.current = ''; + } return; } - chatResult.current += JSON.parse(message).content; - // setContext((prevData) => prevData + JSON.parse(message).content); + + if (isNL2SQL) { + editorRef?.current?.setValue(JSON.parse(message).content); + } else { + chatResult.current += JSON.parse(message).content; + } } catch (error) { console.log('handleMessage', error); + setIsLoading(false); } }; const handleError = (error: any) => { console.error('Error:', error); + setIsLoading(false); }; const closeEventSource = connectToEventSource({ @@ -123,6 +142,10 @@ function Console(props: IProps) { }); }; + const onPressChatInput = (value: string) => { + handleAiChat(value, IPromptType.NL_2_SQL); + }; + const executeSQL = (sql?: string) => { const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); @@ -134,7 +157,7 @@ function Console(props: IProps) { sql: sqlContent, ...executeParams, }; - props.onExecuteSQL?.(undefined); + // props.onExecuteSQL?.(undefined); sqlServer.executeSql(p).then((res) => { props.onExecuteSQL?.(res, sqlContent!); // console.log(res) @@ -147,7 +170,7 @@ function Console(props: IProps) { }; const saveConsole = (value?: string) => { - const a = editorRef.current?.getAllContent(); + // const a = editorRef.current?.getAllContent(); let p: any = { id: executeParams.consoleId, @@ -155,13 +178,7 @@ function Console(props: IProps) { }; historyServer.updateSavedConsole(p).then((res) => { message.success('保存成功'); - dispatch({ - type: 'workspace/fetchGetSavedConsole', - }); - // notification.open({ - // type: 'success', - // message: '保存成功', - // }); + props.onConsoleSave && props.onConsoleSave(); }); }; @@ -170,52 +187,64 @@ function Console(props: IProps) { { id: 'explainSQL', label: '解释SQL', - action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_EXPLAIN, selectedText), + action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_EXPLAIN), }, { id: 'optimizeSQL', label: '优化SQL', - action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_OPTIMIZER, selectedText), + action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_OPTIMIZER), }, { id: 'changeSQL', label: 'SQL转化', - action: (selectedText: string) => handleAIRelativeOperation(IPromptType.SQL_2_SQL, selectedText), + action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_2_SQL), }, ], [], ); - const handleAIRelativeOperation = (id: string, selectedText: string) => { - console.log('handleAIRelativeOperation', id, selectedText); - }; - return (
- {hasAiChat && } + {hasAiChat && ( + { + setSelectedTables(tables); + }} + /> + )} {/*
{chatContent.current}
*/} + + {/* {modelConfig.content} */} + setIsAiDrawerOpen(false)}> + +
{aiContent}
+
+
-
-
- {hasSaveBtn && ( - )} @@ -234,8 +263,4 @@ function Console(props: IProps) { ); } -const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace, -})); - -export default dvaModel(Console); +export default Console; diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 32ee60f61..fb02c7a7a 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -54,14 +54,14 @@ export default memo(function SearchResult({ className, manageResultDataL ), value: index, - } + }; }); - }, []) + }, []); function onEdit(type: 'add' | 'remove', value?: number | string) { if (type === 'remove') { if (currentTab === value) { - setCurrentTab(0) + setCurrentTab(0); } // manageResultDataList = manageResultDataList.filter(t => t.uuid !== value) manageResultDataList.splice(value as number, 1); @@ -73,7 +73,7 @@ export default memo(function SearchResult({ className, manageResultDataL
), + column: index === headerList.length - 1 ? undefined : 320, + fixed: index === 0 ? 'left' : undefined }; return data; }); @@ -185,7 +187,7 @@ export function TableBox(props: ITableProps) { return (
{dataList !== null ? ( -
+
) : ( )} @@ -210,7 +212,7 @@ export function TableBox(props: ITableProps) { id="view_table-Cell_data" defaultValue={viewTableCellData?.value} options={{ - readOnly: true + readOnly: true, }} > diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 0852a3b62..767f7d326 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -1,9 +1,9 @@ import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/utils/localStorage'; import sqlService, { MetaSchemaVO } from '@/service/sql'; -import historyService from '@/service/history' +import historyService from '@/service/history'; import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants'; import { Effect, Reducer } from 'umi'; -import { ITreeNode, IConsole } from '@/typings'; +import { ITreeNode, IConsole, IPageResponse } from '@/typings'; import { treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; export type ICurWorkspaceParams = { @@ -50,7 +50,7 @@ const WorkspaceModel: IWorkspaceModelType = { curWorkspaceParams: getCurrentWorkspaceDatabase(), doubleClickTreeNodeData: undefined, consoleList: [], - curTableList: [] + curTableList: [], }, reducers: { @@ -93,33 +93,33 @@ const WorkspaceModel: IWorkspaceModelType = { effects: { *fetchDatabaseAndSchema({ payload }, { put }) { - const res = yield sqlService.getDatabaseSchemaList(payload); + const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; yield put({ type: 'setDatabaseAndSchema', payload: res, }); }, *fetchGetSavedConsole({ payload }, { put }) { - const res = yield historyService.getSavedConsoleList({ + const res = (yield historyService.getSavedConsoleList({ pageNo: 1, pageSize: 999, - status: ConsoleStatus.RELEASE - }); + status: ConsoleStatus.RELEASE, + })) as IPageResponse; yield put({ type: 'setConsoleList', payload: res.data, }); }, *fetchGetCurTableList({ payload }, { put }) { - yield put({ - type: 'setCurTableList', - payload: undefined, - }); - const res = yield treeConfig[TreeNodeType.TABLES].getChildren!({ + // yield put({ + // type: 'setCurTableList', + // payload: undefined, + // }); + const res = (yield treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, - ...payload - }); + ...payload, + })) as ITreeNode[]; yield put({ type: 'setCurTableList', payload: res, diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index a8c2a7cea..472d11751 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -7,7 +7,7 @@ import Console, { IAppendValue } from '@/components/Console'; import SearchResult from '@/components/SearchResult'; import { DatabaseTypeCode } from '@/constants'; import { IManageResultData } from '@/typings'; -import { IWorkspaceModelType } from '@/models/workspace' +import { IWorkspaceModelType } from '@/models/workspace'; interface IProps { className?: string; @@ -30,12 +30,12 @@ const WorkspaceRightItem = memo(function (props) { const draggableRef = useRef(); const [appendValue, setAppendValue] = useState({ text: data.initDDL }); const [resultData, setResultData] = useState(); - const { doubleClickTreeNodeData } = workspaceModel; + const { doubleClickTreeNodeData, curTableList } = workspaceModel; const [showResult, setShowResult] = useState(false); useEffect(() => { if (!doubleClickTreeNodeData) { - return + return; } const { extraParams } = doubleClickTreeNodeData; const { tableName } = extraParams || {}; @@ -45,38 +45,41 @@ const WorkspaceRightItem = memo(function (props) { } dispatch({ type: 'workspace/setDoubleClickTreeNodeData', - payload: '' + payload: '', }); }, [doubleClickTreeNodeData]); - return
- -
- { - setResultData(res); - setShowResult(true); - }} - /> -
+ return ( +
+ +
+ { + setResultData(res); + setShowResult(true); + }} + onConsoleSave={() => { + dispatch({ + type: 'workspace/fetchGetSavedConsole', + }); + }} + tables={curTableList || []} + /> +
-
- { - showResult && - - } -
-
-
-}) +
{showResult && }
+
+
+ ); +}); const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace -})) + workspaceModel: workspace, +})); -export default dvaModel(WorkspaceRightItem) +export default dvaModel(WorkspaceRightItem); diff --git a/chat2db-client/src/utils/common.ts b/chat2db-client/src/utils/common.ts index a172c524f..007da3d13 100644 --- a/chat2db-client/src/utils/common.ts +++ b/chat2db-client/src/utils/common.ts @@ -1,9 +1,16 @@ export function formatParams(obj: { [key: string]: any }) { - let params = ''; - for (let key in obj) { - if (obj[key]) { - params += `${key}=${obj[key]}&`; + const params = new URLSearchParams(); + Object.entries(obj).forEach(([key, value]) => { + if (value === undefined || value === null) { + return; } - } - return params; -} \ No newline at end of file + if (Array.isArray(value)) { + value.forEach((item) => { + params.append(key, item); + }); + } else { + params.append(key, value); + } + }); + return params.toString(); +} From 1e3bd7d636876cf6f53ca4904223e8a0c7ba7176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Fri, 30 Jun 2023 19:48:36 +0800 Subject: [PATCH 0114/1069] delete table add table pin --- .../src/components/EditDialog/index.less | 4 + .../src/components/EditDialog/index.tsx | 43 ++++++ chat2db-client/src/components/Tabs/index.less | 2 +- chat2db-client/src/models/workspace.ts | 6 +- .../Tree/TreeNodeRightClick/index.tsx | 130 +++++++----------- .../workspace/components/Tree/treeConfig.tsx | 2 +- .../components/WorkspaceLeft/index.less | 1 - .../src/pages/main/workspace/index.less | 3 + chat2db-client/src/service/sql.ts | 16 ++- chat2db-client/src/typings/common.ts | 7 + 10 files changed, 130 insertions(+), 84 deletions(-) create mode 100644 chat2db-client/src/components/EditDialog/index.less create mode 100644 chat2db-client/src/components/EditDialog/index.tsx diff --git a/chat2db-client/src/components/EditDialog/index.less b/chat2db-client/src/components/EditDialog/index.less new file mode 100644 index 000000000..2e57988d1 --- /dev/null +++ b/chat2db-client/src/components/EditDialog/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/components/EditDialog/index.tsx b/chat2db-client/src/components/EditDialog/index.tsx new file mode 100644 index 000000000..030945b53 --- /dev/null +++ b/chat2db-client/src/components/EditDialog/index.tsx @@ -0,0 +1,43 @@ +import React, { memo, useEffect, useRef, useState, ForwardedRef } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import MonacoEditor, { IExportRefFunction, IRangeType } from '@/components/Console/MonacoEditor'; +import { Modal } from 'antd' + +interface IProps { + className?: string; + verifyDialog: boolean; + title: string; + value: { + text: string; + range: IRangeType; + } +} + +export default memo(function EditDialog(props) { + const { className, verifyDialog, value, title } = props; + const [open, setOpen] = useState(); + const monacoEditorRef = useRef(); + + useEffect(() => { + setOpen(verifyDialog) + }, [verifyDialog]) + + + useEffect(() => { + if (monacoEditorRef.current) { + monacoEditorRef.current?.setValue(value.text, value.range) + } + }, [value]) + + return
+ { setVerifyDialog(false) })} + > + + +
+}) diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index c0ed1763d..4e948c488 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -20,7 +20,7 @@ .tab { display: flex; - padding: 18px 10px 0px 10px; + padding: 18px 4px 0px 4px; } .tabList { diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 767f7d326..b2a3d247f 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -110,7 +110,7 @@ const WorkspaceModel: IWorkspaceModelType = { payload: res.data, }); }, - *fetchGetCurTableList({ payload }, { put }) { + *fetchGetCurTableList({ payload, callback }, { put,call }) { // yield put({ // type: 'setCurTableList', // payload: undefined, @@ -120,6 +120,10 @@ const WorkspaceModel: IWorkspaceModelType = { pageSize: 999, ...payload, })) as ITreeNode[]; + // 异步操作完成后调用回调函数 + if (callback && typeof callback === 'function') { + callback(res); + } yield put({ type: 'setCurTableList', payload: res, diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index ac80eb22f..d78f452a1 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -13,12 +13,14 @@ import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace import { ITreeNode } from '@/typings'; // import { DatabaseContext } from '@/context/database'; import connectionServer from '@/service/connection'; +import historyService from '@/service/history'; import mysqlServer from '@/service/sql'; import { OperationColumn } from '../treeConfig'; import { dataSourceFormConfigs } from '@/components/CreateConnection/config/dataSource'; import { IConnectionConfig } from '@/components/CreateConnection/config/types'; import { IWorkspaceModelType } from '@/models/workspace'; - +import EditDialog from '@/components/EditDialog'; +import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants' type MenuItem = Required['items'][number]; @@ -64,13 +66,7 @@ function TreeNodeRightClick(props: IProps) { text: '导出ddl', icon: '\ue613', handle: () => { - const operationData: IOperationData = { - type: 'export', - nodeData: data - } - if (operationData.type === 'export') { - // setOperationDataDialog(operationData); - } + } } }, @@ -105,14 +101,7 @@ function TreeNodeRightClick(props: IProps) { text: '新建查询', icon: '\ue619', handle: () => { - console.log(data) - // setCreateConsoleDialog({ - // dataSourceId: data.dataSourceId!, - // dataSourceName: data.dataSourceName!, - // databaseName: data.databaseName!, - // schemaName: data.schemaName!, - // databaseType: data.dataType! as DatabaseTypeCode - // }) + } } }, @@ -121,41 +110,17 @@ function TreeNodeRightClick(props: IProps) { text: '删除表', icon: '\ue6a7', handle: () => { - modalApi.confirm({ - title: i18n('common.tips.deleteTable'), - icon: , - content: `${i18n('common.text.tableName')}:${data.name}`, - okText: i18n('common.button.delete'), - okType: 'danger', - cancelText: i18n('common.button.cancel'), - onOk() { - let p: any = { - ...data.extraParams, - tableName: data.name, - } - mysqlServer.deleteTable(p).then(res => { - notificationApi.success( - { - message: '删除成功', - } - ) - dispatch({ - type: 'workspace/fetchGetCurTableList', - payload: { - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - } - }) - }) - }, - }); - // setCreateConsoleDialog({ - // dataSourceId: data.dataSourceId!, - // dataSourceName: data.dataSourceName!, - // databaseName: data.databaseName!, - // schemaName: data.schemaName!, - // databaseType: data.dataType! as DatabaseTypeCode - // }) + setVerifyDialog(true); + // modalApi.confirm({ + // title: i18n('common.tips.deleteTable'), + // icon: , + // content: `${i18n('common.text.tableName')}:${data.name}`, + // okText: i18n('common.button.delete'), + // okType: 'danger', + // cancelText: i18n('common.button.cancel'), + // onOk() { + // }, + // }); } } }, @@ -183,7 +148,19 @@ function TreeNodeRightClick(props: IProps) { } function handelTop() { - data.pinned + const api = data.pinned ? 'deleteTablePin' : 'addTablePin' + mysqlServer[api]({ + ...curWorkspaceParams, + tableName: data.name + } as any).then(res => { + dispatch({ + type: 'workspace/fetchGetCurTableList', + payload: { + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + } + }) + }) } function refresh() { @@ -201,19 +178,29 @@ function TreeNodeRightClick(props: IProps) { } function handleOk() { - let p = { - tableName: verifyTableName, - dataSourceId: data.dataSourceId!, - databaseName: data.databaseName! - } - if (verifyTableName === data.tableName) { + if (verifyTableName === data.name) { + let p: any = { + ...data.extraParams, + tableName: data.name, + } mysqlServer.deleteTable(p).then(res => { - setVerifyDialog(false); - // setNeedRefreshNodeTree({ - // databaseName: data.databaseName, - // dataSourceId: data.dataSourceId, - // nodeType: TreeNodeType.TABLES - // }) + notificationApi.success( + { + message: '删除成功', + } + ) + dispatch({ + type: 'workspace/fetchGetCurTableList', + payload: { + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + }, + callback: () => { + setVerifyDialog(false); + setVerifyTableName(''); + } + }) + }) } else { message.error('输入的表名与要删除的表名不一致,请再次确认') @@ -272,22 +259,9 @@ function TreeNodeRightClick(props: IProps) { } - {/*
-
- { - excludeSomeOperation()?.map((item, index) => { - const concrete = OperationColumnConfig[item](data); - return
{ concrete.handle(); }}> - {concrete.text} - -
- }) - } -
-
*/} ( + '/api/pin/table/add', + { method: 'post' } +); + +const deleteTablePin = createRequest( + '/api/pin/table/delete', + { method: 'post' } +); + export default { getList, executeSql, @@ -115,5 +125,7 @@ export default { getIndexList, getKeyList, getSchemaList, - getDatabaseSchemaList + getDatabaseSchemaList, + addTablePin, + deleteTablePin }; diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 1727e2dd1..1fcc12fba 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -36,3 +36,10 @@ export interface Option { } export type ICreateConsole = Omit; + +export interface IUniversalTableParams { + dataSourceId: string; + databaseName?: string; + schemaName?: string; + tableName?: string; +} From 03de8fc0ebf1b04633f633d9725278d5f5051f7a Mon Sep 17 00:00:00 2001 From: moji Date: Fri, 30 Jun 2023 20:24:18 +0800 Subject: [PATCH 0115/1069] bugfix: pinController not work --- .../ai/chat2db/server/domain/core/impl/PinServiceImpl.java | 2 ++ .../server/web/api/controller/pin/PinController.java | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java index c7ead4b61..690b3e1aa 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java @@ -7,6 +7,7 @@ import ai.chat2db.server.domain.repository.mapper.PinTableMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.commons.lang3.StringUtils; @@ -30,6 +31,7 @@ public class PinServiceImpl implements PinService { @Override public ActionResult pinTable(PinTableParam param) { PinTableDO entity = pinTableConverter.param2do(param); + entity.setUserId(ContextUtils.getUserId()); pinTableMapper.insert(entity); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java index d0b9dcf9e..d736f6842 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/pin/PinController.java @@ -5,9 +5,11 @@ import ai.chat2db.server.web.api.controller.pin.converter.PinWebConverter; import ai.chat2db.server.web.api.controller.pin.request.PinTableRequest; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -22,12 +24,12 @@ public class PinController { private PinWebConverter pinWebConverter; @PostMapping("/table/add") - public ActionResult add(PinTableRequest request) { + public ActionResult add(@Valid @RequestBody PinTableRequest request) { return pinService.pinTable(pinWebConverter.req2param(request)); } @PostMapping("/table/delete") - public ActionResult delete(PinTableRequest request) { + public ActionResult delete(@Valid @RequestBody PinTableRequest request) { return pinService.deletePinTable(pinWebConverter.req2param(request)); } From 8cc3a5693a7f6b91ef3d8c5edb640768c9ddcc55 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Fri, 30 Jun 2023 20:46:49 +0800 Subject: [PATCH 0116/1069] feat: improve --- chat2db-client/package.json | 3 +- .../src/components/SearchResult/TableBox.less | 58 +++++ .../src/components/SearchResult/TableBox.tsx | 135 ++++++++++ .../src/components/SearchResult/index.less | 43 +--- .../src/components/SearchResult/index.tsx | 235 +++++------------- .../components/WorkspaceRightItem/index.tsx | 6 +- chat2db-client/src/typings/database.ts | 1 + chat2db-client/yarn.lock | 97 +++++++- 8 files changed, 357 insertions(+), 221 deletions(-) create mode 100644 chat2db-client/src/components/SearchResult/TableBox.less create mode 100644 chat2db-client/src/components/SearchResult/TableBox.tsx diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 40a3553aa..1882a9fe1 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -26,6 +26,7 @@ }, "dependencies": { "ahooks": "^3.7.7", + "ali-react-table": "^2.6.1", "antd": "^5.6.0", "echarts": "^5.4.2", "echarts-for-react": "^3.0.2", @@ -101,4 +102,4 @@ "icon": "src/assets/logo/logo.png" } } -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less new file mode 100644 index 000000000..4df520508 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -0,0 +1,58 @@ +@import '../../styles/var.less'; + +.tableBox { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: none; + overflow: auto; + background-color: var(--color-bg-100); +} + +.cursorTableBox { + display: block; +} + + +.statusBar { + position: absolute; + bottom: 0; + left: 0; + right: 0; + padding: 4px 8px; + font-size: 12px; + display: flex; + align-items: center; + background-color: var(--color-bg-elevated); +} + +.tableItem { + width: 100%; + .f-lines(1); + cursor: pointer; + + &:hover .tableHoverBox { + display: flex; + } +} + +.tableHoverBox { + display: none; + align-items: center; + position: absolute; + background-color: var(--color-bg); + top: 0; + right: 0; + bottom: 0; + + i { + font-size: 15px; + margin: 0px 2px; + } + + i:hover { + color: var(--custom-primary-color); + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx new file mode 100644 index 000000000..b55ec7019 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -0,0 +1,135 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { TableDataType } from '@/constants/table'; +import { IManageResultData, ITableHeaderItem } from '@/typings/database'; +import { formatDate } from '@/utils/date'; +import { message, Modal, Table } from 'antd'; +import { applyTransforms, BaseTable, BaseTableProps, collectNodes, makeColumnResizeTransform } from 'ali-react-table'; +import Iconfont from '../Iconfont'; +import classnames from 'classnames'; +import StateIndicator from '../StateIndicator'; +import MonacoEditor from '../Console/MonacoEditor'; +import styles from './TableBox.less'; + +interface ITableProps { + className?: string; + data: IManageResultData; + key: number; +} + +interface IViewTableCellData { + name: string; + value: any; +} + +export default function TableBox(props: ITableProps) { + const { className, data, key } = props; + const { headerList, dataList, duration, description } = data || {}; + const [viewTableCellData, setViewTableCellData] = useState(null); + + function viewTableCell(data: IViewTableCellData) { + setViewTableCellData(data); + } + + function copyTableCell(data: IViewTableCellData) { + navigator.clipboard.writeText(data?.value || viewTableCellData?.value); + message.success('复制成功'); + } + + function handleCancel() { + setViewTableCellData(null); + } + + const columns = useMemo( + () => + (headerList || []).map((item: any) => ({ + title: item.name, + dataIndex: item.name, + code: item.name, + key: item.name, + type: item.dataType, + sorter: (a: any, b: any) => a[item.name] - b[item.name], + render: (value: any) => ( +
+
+ + +
+ {value} +
+ ), + })), + [headerList], + ); + + const tableData = useMemo(() => { + if (!columns?.length) { + return []; + } else { + return (dataList || []).map((item, rowIndex) => { + const rowData: any = {}; + item.map((i: string | null, index: number) => { + const { dataType: type } = headerList[index] || {}; + // console.log('headerList[rowIndex]', headerList[rowIndex]); + if (type === TableDataType.DATETIME && i) { + rowData[columns[index].title] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); + } else if (i === null) { + rowData[columns[index].title] = '[null]'; + } else { + rowData[columns[index].title] = i; + } + }); + rowData.key = rowIndex; + return rowData; + }); + } + }, [dataList, columns]); + + console.log('dataList', dataList); + return ( +
+ {columns.length ? ( +
+ ) : ( + + )} +
{`结果:${description}. 耗时:${duration}ms`}
+ + {/* { + + } */} + + } + > +
+ +
+
+ + ); +} +function pipeline(arg0: { + sizes: number; + onChangeSizes: React.Dispatch>; + appendExpander: boolean; + expanderVisibility: string; + disableUserSelectWhenResizing: boolean; + minSize: number; + maxSize: number; +}): import('ali-react-table').Transform<{ columns: any; dataSource: any }> { + throw new Error('Function not implemented.'); +} diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 154fa7359..4b3070241 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -20,6 +20,7 @@ flex-shrink: 0; overflow-x: scroll; background-color: var(--color-bg-elevated); + &::-webkit-scrollbar { display: none; } @@ -63,58 +64,22 @@ } } -.tableBox { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - display: none; - overflow: auto; - background-color: var(--color-bg-100); -} - .cursorTableBox { - display: block; + display: block !important; } + .tableIndex { width: 50px; } -.tableHoverBox { - display: none; - align-items: center; - position: absolute; - background-color: var(--color-bg); - top: 0; - right: 0; - bottom: 0; - - i { - font-size: 15px; - margin: 0px 2px; - } - i:hover { - color: var(--custom-primary-color); - } -} .monacoEditor { height: 300px; } -.tableItem { - width: 100%; - .f-lines(1); - cursor: pointer; - - &:hover .tableHoverBox { - display: flex; - } -} .monacoEditor { margin: -15px; -} +} \ No newline at end of file diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index fb02c7a7a..d61cdbb50 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -12,6 +12,7 @@ import { IManageResultData, ITableHeaderItem } from '@/typings'; import styles from './index.less'; import i18n from '@/i18n'; import { v4 as uuidv4 } from 'uuid'; +import TableBox from './TableBox'; interface IProps { className?: string; @@ -22,10 +23,38 @@ interface DataType { [key: string]: any; } +const handleTabs = (result: IManageResultData[]) => { + return (result || []).map((item, index) => { + return { + label: ( + <> + + {`${i18n('common.text.executionResult')}-${index + 1}`} + + ), + value: index, + }; + }); +}; + export default memo(function SearchResult({ className, manageResultDataList = [] }) { const [isUnfold, setIsUnfold] = useState(true); const [currentTab, setCurrentTab] = useState(0); const [resultDataList, setResultDataList] = useState([]); + const [tabs, setTabs] = useState([]); + + useEffect(() => { + setResultDataList(manageResultDataList); + setTabs(handleTabs(manageResultDataList)); + }, [manageResultDataList]); + + function onChange(index: string | number) { + setCurrentTab(index); + } const renderStatus = (text: string) => { return ( @@ -36,187 +65,59 @@ export default memo(function SearchResult({ className, manageResultDataL ); }; - function onChange(index: string | number) { - setCurrentTab(index); - } - - const tabs: IOption[] = useMemo(() => { - return manageResultDataList.map((item, index) => { - return { - label: ( - <> - - {`${i18n('common.text.executionResult')}-${index + 1}`} - - ), - value: index, - }; - }); - }, []); - function onEdit(type: 'add' | 'remove', value?: number | string) { if (type === 'remove') { if (currentTab === value) { setCurrentTab(0); } - // manageResultDataList = manageResultDataList.filter(t => t.uuid !== value) - manageResultDataList.splice(value as number, 1); - } - } - - return ( -
-
- -
-
- {manageResultDataList?.map((item, index) => { - if (item.success) { - return ( - - ); - } else { - return ; - } - })} -
-
- ); -}); - -interface ITableProps { - headerList: ITableHeaderItem[]; - dataList: string[][]; - className?: string; - data: IManageResultData; - key: number; -} - -interface IViewTableCellData { - name: string; - value: any; -} + const remainTabs = tabs.filter((t) => t.value !== value); + setTabs(remainTabs); -export function TableBox(props: ITableProps) { - const { headerList, dataList, className, data, key, ...rest } = props; - const [columns, setColumns] = useState(); - const [tableData, setTableData] = useState(); - const [viewTableCellData, setViewTableCellData] = useState(null); - - function viewTableCell(data: IViewTableCellData) { - setViewTableCellData(data); - } - - function copyTableCell(data: IViewTableCellData) { - navigator.clipboard.writeText(data?.value || viewTableCellData?.value); - message.success('复制成功'); + const dataList = resultDataList.filter((t) => t.uuid !== value); + // resultDataList.splice(value as number, 1); + setResultDataList(dataList); + } } - function handleCancel() { - setViewTableCellData(null); - } + const renderEmpty = () => { + return
暂无数据
; + }; - useEffect(() => { - if (!headerList?.length) { - return; + const renderTable = () => { + if (!tabs || !tabs.length) { + return renderEmpty(); + } + if (!resultDataList || !resultDataList.length) { + return renderEmpty(); } - const columns: any = headerList?.map((item: any, index) => { - const data = { - title: item.name, - dataIndex: item.name, - key: item.name, - type: item.dataType, - sorter: (a: any, b: any) => a[item.name] - b[item.name], - render: (value: any) => ( -
-
- - -
- {value} -
- ), - column: index === headerList.length - 1 ? undefined : 320, - fixed: index === 0 ? 'left' : undefined - }; - return data; - }); - setColumns(columns); - }, [headerList]); - useEffect(() => { - if (!columns?.length) return; - const tableData = dataList?.map((item, rowIndex) => { - const rowData: any = {}; - item.map((i: string | null, index: number) => { - const { dataType: type } = headerList[index] || {}; - // console.log('headerList[rowIndex]', headerList[rowIndex]); - if (type === TableDataType.DATETIME && i) { - rowData[columns[index].title] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); - } else if (i === null) { - rowData[columns[index].title] = '[null]'; - } else { - rowData[columns[index].title] = i; - } - }); - rowData.key = rowIndex; - return rowData; + return (resultDataList || []).map((item: any, index: number) => { + if (item.success) { + return ( + + ); + } else { + return ; + } }); - - setTableData(tableData); - }, [columns]); + }; return ( -
- {dataList !== null ? ( -
- ) : ( - - )} - - {/* { - - } */} - - } - > -
- +
+ {tabs.length ? ( +
+
- + ) : null} +
{renderTable()}
); -} +}); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 472d11751..971eec455 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -29,7 +29,7 @@ const WorkspaceRightItem = memo(function (props) { const { className, data, workspaceModel, isActive, dispatch } = props; const draggableRef = useRef(); const [appendValue, setAppendValue] = useState({ text: data.initDDL }); - const [resultData, setResultData] = useState(); + const [resultData, setResultData] = useState([]); const { doubleClickTreeNodeData, curTableList } = workspaceModel; const [showResult, setShowResult] = useState(false); @@ -39,7 +39,7 @@ const WorkspaceRightItem = memo(function (props) { } const { extraParams } = doubleClickTreeNodeData; const { tableName } = extraParams || {}; - const ddl = `SELECT * FROM ${tableName};`; + const ddl = `SELECT * FROM ${tableName};\n`; if (isActive) { setAppendValue({ text: ddl }); } @@ -72,7 +72,7 @@ const WorkspaceRightItem = memo(function (props) { />
-
{showResult && }
+
{}
); diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 318075c29..9758cddcf 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -20,4 +20,5 @@ export interface IManageResultData { sql: string; success: boolean; uuid?: string; + duration: number; } diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index d742f2e60..28d659770 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1392,7 +1392,7 @@ "@babel/parser" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.22.5": +"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.22.5", "@babel/traverse@^7.4.5": version "7.22.5" resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.22.5.tgz#44bd276690db6f4940fdb84e1cb4abd2f729ccd1" integrity sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ== @@ -1584,7 +1584,24 @@ resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@emotion/unitless@^0.7.5": +"@emotion/is-prop-valid@^1.1.0": + version "1.2.1" + resolved "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" + integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== + dependencies: + "@emotion/memoize" "^0.8.1" + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.npmmirror.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/stylis@^0.8.4": + version "0.8.5" + resolved "https://registry.npmmirror.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" + integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== + +"@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== @@ -2013,6 +2030,11 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@popperjs/core@^2.9.1": + version "2.11.8" + resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" + integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== + "@rc-component/color-picker@~1.2.0": version "1.2.0" resolved "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.2.0.tgz#964c86e85f0791703c7f1ec842e7476bcb41954d" @@ -2278,6 +2300,13 @@ "@types/node" "*" "@types/responselike" "^1.0.0" +"@types/classnames@^2.2.9": + version "2.3.1" + resolved "https://registry.npmmirror.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" + integrity sha512-zeOWb0JGBoVmlQoznvqXbE0tEC/HONsnoUNH19Hc96NFsTAwTXbTqb8FMYkru1F/iqp7a18Ws3nWJvtA1sHD1A== + dependencies: + classnames "*" + "@types/debug@^4.1.6": version "4.1.8" resolved "https://registry.npmmirror.com/@types/debug/-/debug-4.1.8.tgz#cef723a5d0a90990313faec2d1e22aee5eecb317" @@ -3039,6 +3068,18 @@ ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ali-react-table@^2.6.1: + version "2.6.1" + resolved "https://registry.npmmirror.com/ali-react-table/-/ali-react-table-2.6.1.tgz#ad6d07269943125bc71c98b81aa011413e70020c" + integrity sha512-YK/s+1fw7ckJDkm5Ln+DJXSXBMyZHlL6ixMSBt9iXroS8roplKuCVkDRIBDBtSv1bqe967BoAcG5MT06mPMQqA== + dependencies: + "@popperjs/core" "^2.9.1" + "@types/classnames" "^2.2.9" + classnames "^2.2.6" + resize-observer-polyfill "^1.5.1" + rxjs "^6.5.4" + styled-components "^3.4.10 || ^5.0.1" + ansi-html-community@^0.0.8: version "0.0.8" resolved "https://registry.npmmirror.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" @@ -3414,6 +3455,17 @@ babel-plugin-styled-components@2.1.1: lodash "^4.17.21" picomatch "^2.3.0" +"babel-plugin-styled-components@>= 1.12.0": + version "2.1.4" + resolved "https://registry.npmmirror.com/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz#9a1f37c7f32ef927b4b008b529feb4a2c82b1092" + integrity sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g== + dependencies: + "@babel/helper-annotate-as-pure" "^7.22.5" + "@babel/helper-module-imports" "^7.22.5" + "@babel/plugin-syntax-jsx" "^7.22.5" + lodash "^4.17.21" + picomatch "^2.3.1" + babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" resolved "https://registry.npmmirror.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" @@ -3826,16 +3878,16 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +classnames@*, classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: + version "2.3.2" + resolved "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" + integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== + classnames@2.3.1: version "2.3.1" resolved "https://registry.npmmirror.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== -classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2: - version "2.3.2" - resolved "https://registry.npmmirror.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924" - integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw== - clean-css@^5.2.2: version "5.3.2" resolved "https://registry.npmmirror.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224" @@ -4186,7 +4238,7 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" -css-to-react-native@^3.2.0: +css-to-react-native@^3.0.0, css-to-react-native@^3.2.0: version "3.2.0" resolved "https://registry.npmmirror.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32" integrity sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ== @@ -5545,7 +5597,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmmirror.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -8490,6 +8542,13 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" +rxjs@^6.5.4: + version "6.6.7" + resolved "https://registry.npmmirror.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + rxjs@^7.8.1: version "7.8.1" resolved "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" @@ -8966,6 +9025,22 @@ styled-components@6.0.0-rc.0: stylis "^4.1.4" tslib "^2.5.0" +"styled-components@^3.4.10 || ^5.0.1": + version "5.3.11" + resolved "https://registry.npmmirror.com/styled-components/-/styled-components-5.3.11.tgz#9fda7bf1108e39bf3f3e612fcc18170dedcd57a8" + integrity sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/traverse" "^7.4.5" + "@emotion/is-prop-valid" "^1.1.0" + "@emotion/stylis" "^0.8.4" + "@emotion/unitless" "^0.7.4" + babel-plugin-styled-components ">= 1.12.0" + css-to-react-native "^3.0.0" + hoist-non-react-statics "^3.0.0" + shallowequal "^1.1.0" + supports-color "^5.5.0" + stylelint-config-recommended@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz#7497372ae83ab7a6fffc18d7d7b424c6480ae15e" @@ -8997,7 +9072,7 @@ superjson@^1.10.0: dependencies: copy-anything "^3.0.2" -supports-color@^5.3.0: +supports-color@^5.3.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.npmmirror.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== @@ -9192,7 +9267,7 @@ tslib@2.3.0: resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg== -tslib@^1.8.1: +tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== From 9e3c77325cc75666b5b57ff9d7124d9f33e0e2ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Fri, 30 Jun 2023 21:11:01 +0800 Subject: [PATCH 0117/1069] =?UTF-8?q?feat:=E5=AF=BC=E5=87=BAddl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Console/MonacoEditor/index.tsx | 21 +++++-- .../Tree/TreeNodeRightClick/index.less | 5 ++ .../Tree/TreeNodeRightClick/index.tsx | 58 ++++++++++++------- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 38bb195e3..5d3129a29 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -6,10 +6,14 @@ import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; const { keywords: SQLKeys } = language; import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import styles from './index.less'; - export type IEditorIns = monaco.editor.IStandaloneCodeEditor; export type IEditorOptions = monaco.editor.IStandaloneEditorConstructionOptions; export type IEditorContentChangeEvent = monaco.editor.IModelContentChangedEvent; + +export type IAppendValue = { + text: any; + range?: IRangeType; +}; interface IProps { id: string | number; isActive?: boolean; @@ -18,6 +22,7 @@ interface IProps { options?: IEditorOptions; needDestroy?: boolean; addAction?: Array<{ id: string; label: string; action: (selectedText: string) => void }>; + appendValue?: IAppendValue; // onChange?: (v: string, e?: IEditorContentChangeEvent) => void; didMount?: (editor: IEditorIns) => any; onSave?: (value: string) => void; // 快捷键保存的回调 @@ -32,7 +37,7 @@ export interface IExportRefFunction { } function MonacoEditor(props: IProps, ref: ForwardedRef) { - const { id, className, language = 'sql', didMount, options, isActive, onSave, onExecute, defaultValue } = props; + const { id, className, language = 'sql', didMount, options, isActive, onSave, onExecute, defaultValue, appendValue } = props; const editorRef = useRef(); const [appTheme] = useTheme(); @@ -136,6 +141,11 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { setValue, })); + useEffect(() => { + console.log(appendValue) + appendMonacoValue(editorRef.current, appendValue?.text, appendValue?.range); + }, [appendValue]) + const setValue = (text: any, range?: IRangeType) => { appendMonacoValue(editorRef.current, text, range); }; @@ -264,7 +274,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // 'front' 开头 // 'cover' 覆盖掉原有的文字 // 自定义位置数组 new monaco.Range [] -export type IRangeType = 'end' | 'front' | 'cover' | any; +export type IRangeType = 'end' | 'front' | 'cover' | 'reset' | any; export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'end') => { if (!editor) { @@ -273,7 +283,9 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e const model = editor?.getModel && editor.getModel(editor); // 创建编辑操作,将当前文档内容替换为新内容 let newRange: IRangeType = range; - text = `${text}`; + if (range === 'reset') { + editor.setValue(text) + } switch (range) { case 'cover': newRange = model.getFullModelRange(); @@ -285,6 +297,7 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e const lastLine = editor.getModel().getLineCount(); const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); newRange = new monaco.Range(lastLine, lastLineLength, lastLine, lastLineLength); + text = `${text}\n`; break; default: break; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less index 55931146e..68bb5e2f3 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.less @@ -9,3 +9,8 @@ margin-left: 14px; } } + +.monacoEditorBox { + height: 500px; + margin: -15px; +} diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index d78f452a1..102083387 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -1,17 +1,12 @@ -import React, { memo, useContext, useMemo, useState } from 'react'; +import React, { memo, useContext, useMemo, useState, useRef } from 'react'; import i18n from '@/i18n'; import classnames from 'classnames'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import { MenuProps, message, Modal, Input, Dropdown, notification } from 'antd'; -import { ExclamationCircleFilled } from '@ant-design/icons'; -// import { Menu } from 'antd'; -// import Menu, { IMenu, MenuItem } from '@/components/Menu'; -// import { IOperationData } from '@/components/OperationTableModal'; import { TreeNodeType, DatabaseTypeCode } from '@/constants'; import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; import { ITreeNode } from '@/typings'; -// import { DatabaseContext } from '@/context/database'; import connectionServer from '@/service/connection'; import historyService from '@/service/history'; import mysqlServer from '@/service/sql'; @@ -20,7 +15,8 @@ import { dataSourceFormConfigs } from '@/components/CreateConnection/config/data import { IConnectionConfig } from '@/components/CreateConnection/config/types'; import { IWorkspaceModelType } from '@/models/workspace'; import EditDialog from '@/components/EditDialog'; -import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants' +import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; +import MonacoEditor, { IExportRefFunction, IRangeType } from '@/components/Console/MonacoEditor'; type MenuItem = Required['items'][number]; @@ -29,7 +25,7 @@ export type IProps = { setIsLoading: (value: boolean) => void; data: ITreeNode; dispatch: any; - workspaceModel: IWorkspaceModelType['state'] + workspaceModel: IWorkspaceModelType['state']; } export interface IOperationColumnConfigItem { @@ -40,7 +36,6 @@ export interface IOperationColumnConfigItem { function TreeNodeRightClick(props: IProps) { const { className, data, setIsLoading, dispatch, workspaceModel } = props; - // const { setCreateConsoleDialog, setOperationDataDialog, setNeedRefreshNodeTree, setEditDataSourceData } = useContext(DatabaseContext); const [verifyDialog, setVerifyDialog] = useState(); const [verifyTableName, setVerifyTableName] = useState(''); const [modalApi, modelDom] = Modal.useModal(); @@ -48,6 +43,8 @@ function TreeNodeRightClick(props: IProps) { const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType] const { getChildren, operationColumn } = treeNodeConfig; const { curWorkspaceParams } = workspaceModel; + const [monacoVerifyDialog, setMonacoVerifyDialog] = useState(false); + const [monacoDefaultValue, setMonacoDefaultValue] = useState(''); const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { return t.type === data.extraParams?.databaseType })! @@ -66,7 +63,13 @@ function TreeNodeRightClick(props: IProps) { text: '导出ddl', icon: '\ue613', handle: () => { - + mysqlServer.exportCreateTableSql({ + ...curWorkspaceParams, + tableName: data.name + } as any).then(res => { + setMonacoVerifyDialog(true); + setMonacoDefaultValue(res); + }) } } }, @@ -111,16 +114,6 @@ function TreeNodeRightClick(props: IProps) { icon: '\ue6a7', handle: () => { setVerifyDialog(true); - // modalApi.confirm({ - // title: i18n('common.tips.deleteTable'), - // icon: , - // content: `${i18n('common.text.tableName')}:${data.name}`, - // okText: i18n('common.button.delete'), - // okType: 'danger', - // cancelText: i18n('common.button.cancel'), - // onOk() { - // }, - // }); } } }, @@ -141,7 +134,7 @@ function TreeNodeRightClick(props: IProps) { text: data.pinned ? '取消置顶' : '置顶', icon: data.pinned ? '\ue61d' : '\ue627', handle: () => { - handelTop() + handelTop(); } } }, @@ -158,6 +151,9 @@ function TreeNodeRightClick(props: IProps) { payload: { ...curWorkspaceParams, extraParams: curWorkspaceParams, + }, + callback: () => { + message.success('操作成功') } }) }) @@ -268,6 +264,26 @@ function TreeNodeRightClick(props: IProps) { onCancel={(() => { setVerifyDialog(false) })}> { setVerifyTableName(e.target.value) }}>
+ {/* 这里后续肯定是要提出去的 */} + { + monacoVerifyDialog && + { setMonacoVerifyDialog(false) })}> +
+ +
+
+ } } From e09449a943d77e54dea1e2f5091d554b37c24dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Fri, 30 Jun 2023 21:24:08 +0800 Subject: [PATCH 0118/1069] Azure AI --- chat2db-client/src/blocks/Setting/index.tsx | 52 +++++++++++++++++++-- chat2db-client/src/i18n/en-us/setting.ts | 2 +- chat2db-client/src/i18n/zh-cn/setting.ts | 2 +- chat2db-client/src/service/config.ts | 3 ++ 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 8193c1718..ce77d6705 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -24,6 +24,7 @@ interface IProps { export enum AiSqlSourceType { OPENAI = 'OPENAI', + Azure = 'Azure', RESTAI = 'RESTAI', } @@ -123,8 +124,8 @@ export default memo(function Setting({ className, text }) { {text ? ( {text} ) : ( - - )} + + )} { @@ -215,7 +219,8 @@ export function SettingAI() { }} value={chatgptConfig.aiSqlSource} > - Open Ai + Open AI + Azure AI {i18n('setting.tab.custom')} @@ -271,6 +276,43 @@ export function SettingAI() { )} + {chatgptConfig.aiSqlSource === AiSqlSourceType.Azure && ( +
+
Api Key
+
+ { + setChatgptConfig({ ...chatgptConfig, azureApiKey: e.target.value }); + }} + /> +
+
Endpoint
+
+ { + setChatgptConfig({ ...chatgptConfig, azureEndpoint: e.target.value }); + }} + /> +
+
DeploymentId
+
+ { + setChatgptConfig({ + ...chatgptConfig, + azureDeploymentId: e.target.value, + }); + }} + /> +
+
+ )} {chatgptConfig.aiSqlSource === AiSqlSourceType.RESTAI && (
@@ -309,7 +351,7 @@ export function SettingAI() { )}
@@ -442,7 +484,7 @@ export function ProxyBody() {
diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 935e6a03f..32ef8bd3b 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -16,7 +16,7 @@ export default { 'setting.title.aiSource': 'Ai Source', 'setting.tab.custom': 'Custom', 'setting.label.serviceAddress': 'Service Address', - 'setting.button.use': 'Use', + 'setting.button.apply': 'Apply', 'setting.text.currentEnv': 'Current Env', 'setting.text.currentVersion': 'Current Version', 'setting.text.viewingUpdateLogs': 'Viewing Update Logs', diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 6447fb16d..6f3d9686b 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -16,7 +16,7 @@ export default { 'setting.title.aiSource': 'AI 来源', 'setting.tab.custom': '自定义', 'setting.label.serviceAddress': '服务地址', - 'setting.button.use': '应用', + 'setting.button.apply': '应用', 'setting.text.currentEnv': '当前环境', 'setting.text.currentVersion': '当前版本', 'setting.text.viewingUpdateLogs': '查看当前应用', diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index d18a94f2d..857f0df69 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -10,6 +10,9 @@ export interface IChatGPTConfig { apiHost: string, aiSqlSource: string; restAiStream: boolean; + azureEndpoint: string; + azureApiKey: string; + azureDeploymentId: string; } const getChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { errorLevel: false }); From 65cc6b7ba196dd3bc6a2a5fff4bd1f47b476fdd5 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 1 Jul 2023 10:23:26 +0800 Subject: [PATCH 0119/1069] Fix the exception when there is no permission at startup --- .../listener/ManageApplicationListener.java | 106 +++++++++--------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java index 20ad6e877..adc9a7343 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java @@ -41,7 +41,7 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { .execute(new TypeReference<>() {}); } catch (Exception e) { // 抛出异常 代表没有旧的启动 或者旧的不靠谱 - log.info("尝试访问旧的应用失败。本异常不重要,正常启动启动都会输出,请忽略。"+ e.getMessage()); + log.info("尝试访问旧的应用失败。本异常不重要,正常启动启动都会输出,请忽略。" + e.getMessage()); // 尝试杀死旧的进程 killOldIfNecessary(environment); @@ -60,59 +60,65 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { } private void killOldIfNecessary(String environment) { - ProcessHandle.allProcesses().forEach(process -> { - String command = process.info().command().orElse(null); - // 不是java应用 - boolean isJava = StringUtils.endsWithIgnoreCase(command, "java") || StringUtils.endsWithIgnoreCase(command, - "java.exe"); - if (!isJava) { - return; - } - String[] arguments = process.info().arguments().orElse(null); - // 没有参数 - if (arguments == null) { - return; - } - // 是否是dbhub - boolean isDbhub = false; - String environmentArgument = null; - for (String argument : arguments) { - if (StringUtils.equals("ali-dbhub-server-start.jar", argument)) { - isDbhub = true; + try { + ProcessHandle.allProcesses().forEach(process -> { + String command = process.info().command().orElse(null); + // 不是java应用 + boolean isJava = StringUtils.endsWithIgnoreCase(command, "java") || StringUtils.endsWithIgnoreCase( + command, + "java.exe"); + if (!isJava) { + return; + } + String[] arguments = process.info().arguments().orElse(null); + // 没有参数 + if (arguments == null) { + return; + } + // 是否是dbhub + boolean isDbhub = false; + String environmentArgument = null; + for (String argument : arguments) { + if (StringUtils.equals("chat2db-server-start.jar", argument)) { + isDbhub = true; + } + if (StringUtils.startsWith(argument, "-Dspring.profiles.active=")) { + environmentArgument = StringUtils.substringAfter(argument, "-Dspring.profiles.active="); + } + } + // 不是dbhub + if (!isDbhub) { + return; } - if (StringUtils.startsWith(argument, "-Dspring.profiles.active=")) { - environmentArgument = StringUtils.substringAfter(argument, "-Dspring.profiles.active="); + // 判断是否是正式环境 + if (StringUtils.equals(SystemEnvironmentEnum.RELEASE.getCode(), environment) && StringUtils.equals( + SystemEnvironmentEnum.RELEASE.getCode(), environmentArgument)) { + log.info("正式环境需要关闭进程"); + destroyProcess(process, command, arguments); + return; } - } - // 不是dbhub - if (!isDbhub) { - return; - } - // 判断是否是正式环境 - if (StringUtils.equals(SystemEnvironmentEnum.RELEASE.getCode(), environment) && StringUtils.equals( - SystemEnvironmentEnum.RELEASE.getCode(), environmentArgument)) { - log.info("正式环境需要关闭进程"); - destroyProcess(process, command, arguments); - return; - } - // 判断是否是测试环境 - if (StringUtils.equals(SystemEnvironmentEnum.TEST.getCode(), environment) && StringUtils.equals( - SystemEnvironmentEnum.TEST.getCode(), environmentArgument)) { - log.info("测试环境需要关闭进程"); - destroyProcess(process, command, arguments); - return; - } + // 判断是否是测试环境 + if (StringUtils.equals(SystemEnvironmentEnum.TEST.getCode(), environment) && StringUtils.equals( + SystemEnvironmentEnum.TEST.getCode(), environmentArgument)) { + log.info("测试环境需要关闭进程"); + destroyProcess(process, command, arguments); + return; + } + + // 判断是否是本地环境 + boolean devDestroy = StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environment) && ( + environmentArgument == null + || StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environmentArgument)); + if (devDestroy) { + log.info("本地环境需要关闭进程"); + destroyProcess(process, command, arguments); + } + }); + } catch (Throwable t) { + log.warn("尝试关闭多余的进程失败,不影响正常启动", t); + } - // 判断是否是本地环境 - boolean devDestroy = StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environment) && ( - environmentArgument == null - || StringUtils.equals(SystemEnvironmentEnum.DEV.getCode(), environmentArgument)); - if (devDestroy) { - log.info("本地环境需要关闭进程"); - destroyProcess(process, command, arguments); - } - }); } private void destroyProcess(ProcessHandle process, String command, String[] arguments) { From 98ab3dbbd289db8b06dde33acec587ebe827e074 Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 10:24:52 +0800 Subject: [PATCH 0120/1069] bugfix: pinController not work --- .../java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java index 690b3e1aa..a2b61dc9d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/PinServiceImpl.java @@ -38,6 +38,7 @@ public ActionResult pinTable(PinTableParam param) { @Override public ActionResult deletePinTable(PinTableParam param) { + param.setUserId(ContextUtils.getUserId()); LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(PinTableDO::getUserId, param.getUserId()); updateWrapper.eq(PinTableDO::getDataSourceId, param.getDataSourceId()); From bca33c8166a8693f2656ac4fec8a19a05480a19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 11:07:36 +0800 Subject: [PATCH 0121/1069] =?UTF-8?q?feat:=E5=9B=BD=E5=AE=B6=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Console/MonacoEditor/index.tsx | 8 ++++---- .../src/components/Console/index.tsx | 15 ++++++++------- chat2db-client/src/components/Tabs/index.less | 1 + chat2db-client/src/i18n/en-us/common.ts | 9 ++++++++- chat2db-client/src/i18n/en-us/workspace.ts | 6 ++++++ chat2db-client/src/i18n/zh-cn/common.ts | 12 ++++++++++-- chat2db-client/src/i18n/zh-cn/workspace.ts | 8 +++++++- .../Tree/TreeNodeRightClick/index.tsx | 19 +++++++++---------- 8 files changed, 53 insertions(+), 25 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 5d3129a29..78cf253d6 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -6,6 +6,7 @@ import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; const { keywords: SQLKeys } = language; import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import styles from './index.less'; +import { useUpdateEffect } from '@/hooks' export type IEditorIns = monaco.editor.IStandaloneCodeEditor; export type IEditorOptions = monaco.editor.IStandaloneEditorConstructionOptions; export type IEditorContentChangeEvent = monaco.editor.IModelContentChangedEvent; @@ -46,7 +47,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const editorIns = monaco.editor.create(document.getElementById(`monaco-editor-${id}`)!, { ...editorDefaultOptions, ...options, - value: defaultValue, + value: defaultValue || '', language: language, theme: appTheme.backgroundColor === ThemeType.Light ? EditorThemeType.Default : EditorThemeType.BlackTheme, }); @@ -141,8 +142,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { setValue, })); - useEffect(() => { - console.log(appendValue) + useUpdateEffect(() => { appendMonacoValue(editorRef.current, appendValue?.text, appendValue?.range); }, [appendValue]) @@ -297,7 +297,7 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e const lastLine = editor.getModel().getLineCount(); const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); newRange = new monaco.Range(lastLine, lastLineLength, lastLine, lastLineLength); - text = `${text}\n`; + text = `${text}`; break; default: break; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 0b55e9240..bacd1c10e 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -13,6 +13,7 @@ import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import Iconfont from '../Iconfont'; import { ITreeNode } from '@/typings'; import styles from './index.less'; +import i18n from '@/i18n'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -186,17 +187,17 @@ function Console(props: IProps) { () => [ { id: 'explainSQL', - label: '解释SQL', + label: i18n('common.text.explainSQL'), action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_EXPLAIN), }, { id: 'optimizeSQL', - label: '优化SQL', + label: i18n('common.text.optimizeSQL'), action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_OPTIMIZER), }, { id: 'changeSQL', - label: 'SQL转化', + label: i18n('common.text.conversionSQL'), action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_2_SQL), }, ], @@ -227,7 +228,7 @@ function Console(props: IProps) { onSave={saveConsole} onExecute={executeSQL} options={props.editorOptions} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> @@ -241,11 +242,11 @@ function Console(props: IProps) {
{hasSaveBtn && ( )}
@@ -256,7 +257,7 @@ function Console(props: IProps) { editorRef?.current?.setValue(format(contextTmp || ''), 'cover'); }} > - Format + {i18n('common.button.format')}
diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 4e948c488..33abfde11 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -37,6 +37,7 @@ width: 100px; margin: 0px 2px; cursor: pointer; + user-select: none; &:hover { .tab-focus(); .text { diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index a13baba78..e9a4c5d1f 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -20,7 +20,9 @@ export default { 'common.text.column': 'column', 'common.text.indexes': 'indexes', 'common.button.save': 'Save', + 'common.button.refresh': 'Refresh', 'common.button.execute': 'Run', + 'common.button.format': 'Format', 'common.message.successfulConfig': 'Successful configuration', 'common.text.successful': 'successful', 'common.text.failure': 'failure', @@ -30,5 +32,10 @@ export default { 'common.button.delete': 'Delete', 'common.text.executionResult': 'Result', 'common.tips.deleteTable': 'Are you sure delete this Table?', - 'common.text.tableName': 'Table Name' + 'common.text.tableName': 'Table Name', + 'common.text.operateSuccessfully': 'Operate Successfully', + 'common.text.successfullyDelete': 'Successfully Delete', + 'common.text.explainSQL': 'Explain SQL', + 'common.text.optimizeSQL': 'Optimize SQL', + 'common.text.conversionSQL': 'Conversion SQL', }; diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 71ec49d0e..d6f762598 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -1,4 +1,10 @@ export default { 'workspace.cascader.placeholder': 'Select Here', 'workspace.ai.input.placeholder': 'Enter your plain text statement here', + 'workspace.menu.exportDDL': 'Export DDL', + 'workspace.menu.deleteTable': 'Delete Table', + 'workspace.menu.pin': 'Pin', + 'workspace.menu.unPin': 'Un Pin', + '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 d3a037b8d..3a4c06c5d 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -20,7 +20,9 @@ export default { 'common.text.column': '列', 'common.text.indexes': '索引', 'common.button.save': '保存', - 'common.button.取消execute': '执行', + 'common.button.refresh': 'refresh', + 'common.button.execute': '执行', + 'common.button.format': '格式化', 'common.message.successfulConfig': '配置成功', 'common.text.successful': '成功', 'common.text.failure': '失败', @@ -30,5 +32,11 @@ export default { 'common.button.delete': '删除', 'common.text.executionResult': '执行结果', 'common.tips.deleteTable': '你确定好删除这张表吗?', - 'common.text.tableName': '表名称' + 'common.text.tableName': '表名称', + 'common.text.operateSuccessfully': '操作成功', + 'common.text.successfullyDelete': '删除成功', + 'common.text.explainSQL': '解释SQL', + 'common.text.optimizeSQL': '优化SQL', + 'common.text.conversionSQL': '转化SQL', + }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index ceed2934b..7cc5091ae 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -1,4 +1,10 @@ export default { 'workspace.cascader.placeholder': '请选择', - 'workspace.ai.input.placeholder': 'Enter your plain text statement here' + 'workspace.ai.input.placeholder': '在这里输入纯文本语句', + 'workspace.menu.exportDDL': '导出DDL', + 'workspace.menu.deleteTable': '删除表', + 'workspace.menu.pin': '置顶', + 'workspace.menu.unPin': '取消置顶', + 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', + 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', } \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 102083387..687e33d8c 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -51,7 +51,7 @@ function TreeNodeRightClick(props: IProps) { const OperationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { [OperationColumn.Refresh]: (data) => { return { - text: '刷新', + text: i18n('common.button.refresh'), icon: '\uec08', handle: () => { refresh(); @@ -60,7 +60,7 @@ function TreeNodeRightClick(props: IProps) { }, [OperationColumn.ExportDDL]: (data) => { return { - text: '导出ddl', + text: i18n('workspace.menu.exportDDL'), icon: '\ue613', handle: () => { mysqlServer.exportCreateTableSql({ @@ -104,13 +104,12 @@ function TreeNodeRightClick(props: IProps) { text: '新建查询', icon: '\ue619', handle: () => { - } } }, [OperationColumn.DeleteTable]: (data) => { return { - text: '删除表', + text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', handle: () => { setVerifyDialog(true); @@ -131,7 +130,7 @@ function TreeNodeRightClick(props: IProps) { }, [OperationColumn.Top]: (data) => { return { - text: data.pinned ? '取消置顶' : '置顶', + text: data.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: data.pinned ? '\ue61d' : '\ue627', handle: () => { handelTop(); @@ -153,7 +152,7 @@ function TreeNodeRightClick(props: IProps) { extraParams: curWorkspaceParams, }, callback: () => { - message.success('操作成功') + message.success(i18n('common.text.operateSuccessfully')) } }) }) @@ -182,7 +181,7 @@ function TreeNodeRightClick(props: IProps) { mysqlServer.deleteTable(p).then(res => { notificationApi.success( { - message: '删除成功', + message: i18n('common.text.successfullyDelete'), } ) dispatch({ @@ -199,7 +198,7 @@ function TreeNodeRightClick(props: IProps) { }) } else { - message.error('输入的表名与要删除的表名不一致,请再次确认') + message.error(i18n('workspace.tips.affirmDeleteTable')) } } @@ -257,12 +256,12 @@ function TreeNodeRightClick(props: IProps) { } { setVerifyDialog(false) })}> - { setVerifyTableName(e.target.value) }}> + { setVerifyTableName(e.target.value) }}> {/* 这里后续肯定是要提出去的 */} { From 86d36fe0e881210f71899ac816a32aa5397d0cf8 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 1 Jul 2023 11:13:20 +0800 Subject: [PATCH 0122/1069] Modify the date formatting logic --- .../server/start/test/druid/SqlUtilsTest.java | 12 ++++ .../java/ai/chat2db/spi/util/JdbcUtils.java | 59 ++++++++----------- 2 files changed, 38 insertions(+), 33 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest.java index 8bfce63dc..943c1863a 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest.java @@ -230,4 +230,16 @@ public void testlimit2() { log.info("解析sql:{}", sqlLimit); } + + @Test + public void test57() { + java.sql.Date date=new java.sql.Date(System.currentTimeMillis()); + + log.info("{}",date); + + java.sql.Timestamp ts=new java.sql.Timestamp(System.currentTimeMillis()); + + log.info("{}",ts); + + } } 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 a3b73fa94..36060f985 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,25 +1,30 @@ package ai.chat2db.spi.util; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Objects; + +import com.alibaba.druid.DbType; + import ai.chat2db.spi.config.DriverConfig; -import cn.hutool.core.date.DateUtil; import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.sql.SSHManager; -import com.alibaba.druid.DbType; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; -import java.sql.*; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Locale; -import java.util.Map; - /** * jdbc工具类 * @@ -28,18 +33,6 @@ @Slf4j public class JdbcUtils { - private static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; - private static final DateTimeFormatter DEFAULT_DATETIME_FORMAT = DateTimeFormatter - .ofPattern(DEFAULT_DATETIME_PATTERN, Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - - private static final DateTimeFormatter DEFAULT_DATE_FORMAT = DateTimeFormatter - .ofPattern("yyyy-MM-dd", Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - private static final DateTimeFormatter DEFAULT_DATETIME_TZ_FORMAT = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss.SSS Z", Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - /** * 获取德鲁伊的的数据库类型 * @@ -160,37 +153,37 @@ public static String getResultSetValue(ResultSet rs, int index) throws SQLExcept return "(CLOB " + clob.length() + ")"; } if (obj instanceof Timestamp timestamp) { - return DateUtil.format(timestamp, DEFAULT_DATETIME_FORMAT); + return Objects.toString(timestamp); } String className = obj.getClass().getName(); if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) { - return DateUtil.format(rs.getTimestamp(index), DEFAULT_DATETIME_TZ_FORMAT); + return Objects.toString(rs.getTimestamp(index)); } if (className.startsWith("oracle.sql.DATE")) { String metaDataClassName = rs.getMetaData().getColumnClassName(index); if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { - return DateUtil.format(rs.getTimestamp(index), DEFAULT_DATETIME_FORMAT); + return Objects.toString(rs.getTimestamp(index)); } else { - return DateUtil.format(rs.getDate(index), DEFAULT_DATETIME_FORMAT); + return Objects.toString(rs.getDate(index)); } } if (obj instanceof Date date) { if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) { - return DateUtil.format(rs.getDate(index), DEFAULT_DATETIME_FORMAT); + return Objects.toString(rs.getDate(index)); } - return DateUtil.format(date, DEFAULT_DATETIME_FORMAT); + return Objects.toString(date); } if (obj instanceof LocalDateTime localDateTime) { - return localDateTime.toString(); + return Objects.toString(localDateTime); } if (obj instanceof LocalDate localDate) { - return localDate.toString(); + return Objects.toString(localDate); } - if (obj instanceof Number) { - return obj.toString(); + if (obj instanceof Number num) { + return Objects.toString(num); } - return obj.toString(); + return Objects.toString(obj); } /** From 77a01b26c13177d6667513731fa6b2d2fcf62bc0 Mon Sep 17 00:00:00 2001 From: "jipengfei.jpf" Date: Sat, 1 Jul 2023 11:39:12 +0800 Subject: [PATCH 0123/1069] support drop table --- .../chat2db/plugin/mysql/MysqlDBManage.java | 5 ++ .../plugin/clickhouse/ClickHouseDBManage.java | 6 +- .../ai/chat2db/plugin/db2/DB2DBManage.java | 4 +- .../java/ai/chat2db/plugin/dm/DMDBManage.java | 4 +- .../java/ai/chat2db/plugin/h2/H2DBManage.java | 4 +- .../ai/chat2db/plugin/hive/HiveDBManage.java | 4 +- .../plugin/kingbase/KingBaseDBManage.java | 4 +- .../chat2db/plugin/mariadb/MariaDBManage.java | 4 +- .../web/api/controller/rdb/vo/TableVO.java | 4 +- .../ai/chat2db/spi/jdbc/DefaultDBManage.java | 58 +++++++++++++++++++ 10 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java index a4ab7fd80..35843b7b9 100644 --- a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java @@ -51,6 +51,11 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { + String sql = "DROP TABLE "+ format(tableName); + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + } + public static String format(String tableName) { + return "`" + tableName + "`"; } } diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java index 137b6db1f..d12cb5841 100644 --- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.clickhouse; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class ClickHouseDBManage implements DBManage { @Override @@ -39,6 +40,9 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "DROP TABLE IF EXISTS " + databaseName + "." + tableName; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } + + } diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java index c4184b63a..dcbd20fb0 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.db2; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class DB2DBManage implements DBManage { @Override @@ -40,6 +41,7 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "DROP TABLE " + tableName; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java index b34e63283..672a59c6b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.dm; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class DMDBManage implements DBManage { @Override @@ -40,6 +41,7 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "DROP TABLE IF EXISTS " +tableName; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java index 3dcec4923..c5555b0a0 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.h2; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class H2DBManage implements DBManage { @Override @@ -40,6 +41,7 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "DROP TABLE " +tableName; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java index 4e3a6cf95..0480cc2be 100644 --- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.hive; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class HiveDBManage implements DBManage { @Override @@ -40,6 +41,7 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "drop table if exists " +tableName; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java index e222a7c2b..65cb32971 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.kingbase; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class KingBaseDBManage implements DBManage { @Override @@ -40,6 +41,7 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "drop table if exists " +tableName; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java index 5c71290b4..47862c21a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java @@ -1,6 +1,7 @@ package ai.chat2db.plugin.mariadb; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; public class MariaDBManage implements DBManage { @Override @@ -40,6 +41,7 @@ public void modifySchema(String databaseName, String schemaName, String newSchem @Override public void dropTable(String databaseName, String schemaName, String tableName) { - + String sql = "DROP TABLE " +tableName ; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java index 45dfdc0b9..d4199fe37 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java @@ -32,6 +32,8 @@ public class TableVO { */ private List indexList; - + /** + * 是否已经被固定 + */ private boolean pinned; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java new file mode 100644 index 000000000..d21b64d11 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java @@ -0,0 +1,58 @@ +/** + * alibaba.com Inc. + * Copyright (c) 2004-2023 All Rights Reserved. + */ +package ai.chat2db.spi.jdbc; + +import java.sql.SQLException; + +import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.sql.SQLExecutor; + +/** + * @author jipengfei + * @version : DefaultDBManage.java + */ +public class DefaultDBManage implements DBManage { + + @Override + public void connectDatabase(String database) { + + } + + @Override + public void modifyDatabase(String databaseName, String newDatabaseName) { + + } + + @Override + public void createDatabase(String databaseName) { + + } + + @Override + public void dropDatabase(String databaseName) { + + } + + @Override + public void createSchema(String databaseName, String schemaName) { + + } + + @Override + public void dropSchema(String databaseName, String schemaName) { + + } + + @Override + public void modifySchema(String databaseName, String schemaName, String newSchemaName) { + + } + + @Override + public void dropTable(String databaseName, String schemaName, String tableName) { + String sql = "DROP TABLE "+ tableName ; + SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + } +} \ No newline at end of file From a4a8b80fd1ab5d6525be16aaea5d045e2df7a263 Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 14:05:02 +0800 Subject: [PATCH 0124/1069] add i18n --- .../web/api/controller/ai/ChatController.java | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index b10c1a832..b31ad8ec7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -208,7 +208,7 @@ public SseEmitter completions(ChatQueryRequest queryRequest, @RequestHeader Map< throw new ParamBusinessException("message"); } - return distributeAI(queryRequest.getMessage(), sseEmitter, uid); + return distributeAISql(queryRequest, sseEmitter, uid); } /** @@ -225,12 +225,30 @@ private SseEmitter distributeAI(String msg, SseEmitter sseEmitter, String uid) t return chatWithOpenAi(msg, sseEmitter, uid); case RESTAI : return chatWithRestAi(msg, sseEmitter); - case AZUREAI : - return chatWithAzureAi(msg, sseEmitter, uid); } return chatWithOpenAi(msg, sseEmitter, uid); } + /** + * distribute with different AI + * + * @return + */ + private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); + switch (Objects.requireNonNull(aiSqlSourceEnum)) { + case OPENAI : + return chatWithOpenAiSql(queryRequest, sseEmitter, uid); + case RESTAI : + return chatWithRestAi(queryRequest.getMessage(), sseEmitter); + case AZUREAI : + return chatWithAzureAi(queryRequest, sseEmitter, uid); + } + return chatWithOpenAiSql(queryRequest, sseEmitter, uid); + } + /** * 使用自定义AI接口进行聊天 * @@ -309,13 +327,19 @@ private SseEmitter chatWithOpenAi(String msg, SseEmitter sseEmitter, String uid) /** * chat with azure openai * - * @param msg + * @param queryRequest * @param sseEmitter * @param uid * @return * @throws IOException */ - private SseEmitter chatWithAzureAi(String msg, SseEmitter sseEmitter, String uid) throws IOException { + private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { + String prompt = buildPrompt(queryRequest); + if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) { + log.error("提示语超出最大长度:{},输入长度:{}, 请重新输入", MAX_PROMPT_LENGTH, + prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); + throw new ParamBusinessException(); + } String messageContext = (String)LocalCache.CACHE.get(uid); List messages = new ArrayList<>(); if (StrUtil.isNotBlank(messageContext)) { @@ -324,7 +348,7 @@ private SseEmitter chatWithAzureAi(String msg, SseEmitter sseEmitter, String uid messages = messages.subList(1, contextLength); } } - ChatMessage currentMessage = new ChatMessage(ChatRole.USER).setContent(msg); + ChatMessage currentMessage = new ChatMessage(ChatRole.USER).setContent(prompt); messages.add(currentMessage); sseEmitter.send(SseEmitter.event().id(uid).name("sseEmitter connected!!!!").data(LocalDateTime.now()).reconnectTime(3000)); From 9f0d4043ad2592b21c6e4ec46b4e0da1c59d1aa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 14:40:03 +0800 Subject: [PATCH 0125/1069] =?UTF-8?q?feat=EF=BC=9A=E6=89=93=E5=BC=80savedc?= =?UTF-8?q?onsole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/index.tsx | 12 +-- .../src/components/Console/index.tsx | 1 - chat2db-client/src/i18n/en-us/common.ts | 4 +- chat2db-client/src/i18n/en-us/setting.ts | 3 + chat2db-client/src/i18n/en-us/workspace.ts | 3 +- chat2db-client/src/i18n/zh-cn/common.ts | 6 +- chat2db-client/src/i18n/zh-cn/setting.ts | 3 + chat2db-client/src/i18n/zh-cn/workspace.ts | 1 + chat2db-client/src/models/workspace.ts | 30 +++++--- .../Tree/TreeNodeRightClick/index.tsx | 2 +- .../components/WorkspaceLeft/index.less | 10 ++- .../components/WorkspaceLeft/index.tsx | 77 +++++++++++++++++-- .../components/WorkspaceRight/index.tsx | 67 +++++++--------- .../components/WorkspaceRightItem/index.tsx | 16 +++- 14 files changed, 163 insertions(+), 72 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index ce77d6705..7b2ce9d46 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -24,7 +24,7 @@ interface IProps { export enum AiSqlSourceType { OPENAI = 'OPENAI', - Azure = 'Azure', + AZUREAI = 'AZUREAI', RESTAI = 'RESTAI', } @@ -220,7 +220,7 @@ export function SettingAI() { value={chatgptConfig.aiSqlSource} > Open AI - Azure AI + Azure AI {i18n('setting.tab.custom')} @@ -276,12 +276,12 @@ export function SettingAI() { )} - {chatgptConfig.aiSqlSource === AiSqlSourceType.Azure && ( + {chatgptConfig.aiSqlSource === AiSqlSourceType.AZUREAI && (
Api Key
{ setChatgptConfig({ ...chatgptConfig, azureApiKey: e.target.value }); @@ -291,7 +291,7 @@ export function SettingAI() {
Endpoint
{ setChatgptConfig({ ...chatgptConfig, azureEndpoint: e.target.value }); @@ -301,7 +301,7 @@ export function SettingAI() {
DeploymentId
{ setChatgptConfig({ diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index bacd1c10e..39b4185c6 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -172,7 +172,6 @@ function Console(props: IProps) { const saveConsole = (value?: string) => { // const a = editorRef.current?.getAllContent(); - let p: any = { id: executeParams.consoleId, status: ConsoleStatus.RELEASE, diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index e9a4c5d1f..5c154774d 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -20,6 +20,7 @@ export default { 'common.text.column': 'column', 'common.text.indexes': 'indexes', 'common.button.save': 'Save', + 'common.button.open': 'Open', 'common.button.refresh': 'Refresh', 'common.button.execute': 'Run', 'common.button.format': 'Format', @@ -33,9 +34,10 @@ export default { 'common.text.executionResult': 'Result', 'common.tips.deleteTable': 'Are you sure delete this Table?', 'common.text.tableName': 'Table Name', - 'common.text.operateSuccessfully': 'Operate Successfully', + 'common.text.submittedSuccessfully': 'Successfully submitted', 'common.text.successfullyDelete': 'Successfully Delete', 'common.text.explainSQL': 'Explain SQL', 'common.text.optimizeSQL': 'Optimize SQL', 'common.text.conversionSQL': 'Conversion SQL', + 'common.text.table': 'Table', }; diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 32ef8bd3b..d73faec03 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -27,4 +27,7 @@ export default { 'setting.placeholder.customUrl': 'URL of the REST interface of the AI', 'setting.placeholder.apiHost': 'This parameter is mandatory. The default value is https://api.openai.com/', 'setting.message.urlTestError': 'The interface test failed. Procedure', + 'setting.placeholder.azureOpenAIKey': 'Get Azure OpenAI key credential from the Azure Portal', + 'setting.placeholder.azureEndpoint': 'Get Azure OpenAI endpoint from the Azure Portal', + 'setting.placeholder.azureDeployment': 'Deployment id of the deployed model', } diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index d6f762598..17cf5aa82 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -1,10 +1,11 @@ export default { 'workspace.cascader.placeholder': 'Select Here', 'workspace.ai.input.placeholder': 'Enter your plain text statement here', + 'workspace.title.saved': 'Saved', 'workspace.menu.exportDDL': 'Export DDL', 'workspace.menu.deleteTable': 'Delete Table', 'workspace.menu.pin': 'Pin', - 'workspace.menu.unPin': 'Un Pin', + 'workspace.menu.unPin': 'Unpin', '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 3a4c06c5d..a752990fd 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -20,7 +20,8 @@ export default { 'common.text.column': '列', 'common.text.indexes': '索引', 'common.button.save': '保存', - 'common.button.refresh': 'refresh', + 'common.button.open': '打开', + 'common.button.refresh': '刷新', 'common.button.execute': '执行', 'common.button.format': '格式化', 'common.message.successfulConfig': '配置成功', @@ -33,10 +34,11 @@ export default { 'common.text.executionResult': '执行结果', 'common.tips.deleteTable': '你确定好删除这张表吗?', 'common.text.tableName': '表名称', - 'common.text.operateSuccessfully': '操作成功', + 'common.text.submittedSuccessfully': 'Successfully submitted', 'common.text.successfullyDelete': '删除成功', 'common.text.explainSQL': '解释SQL', 'common.text.optimizeSQL': '优化SQL', 'common.text.conversionSQL': '转化SQL', + 'common.text.table': '表', }; diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 6f3d9686b..47196f208 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -27,5 +27,8 @@ export default { 'setting.placeholder.customUrl': '选择自定义AI时必填,用于设置自定义AI的REST接口URL', 'setting.placeholder.apiHost': '非必填,默认值为 https://api.openai.com/', 'setting.message.urlTestError': '接口测试不通过', + 'setting.placeholder.azureOpenAIKey': '从Azure门户获取Azure OpenAI密钥凭证', + 'setting.placeholder.azureEndpoint': '从Azure门户获取Azure OpenA端口', + 'setting.placeholder.azureDeployment': '部署模型的部署id', } \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 7cc5091ae..db47fcfc3 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -1,6 +1,7 @@ export default { 'workspace.cascader.placeholder': '请选择', 'workspace.ai.input.placeholder': '在这里输入纯文本语句', + 'workspace.title.saved': '保存记录', 'workspace.menu.exportDDL': '导出DDL', 'workspace.menu.deleteTable': '删除表', 'workspace.menu.pin': '置顶', diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index b2a3d247f..e34e15639 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -22,7 +22,8 @@ export interface IState { // 双击树node节点 doubleClickTreeNodeData: ITreeNode | undefined; consoleList: IConsole[]; - curTableList: ITreeNode[] | undefined; + openConsoleList: IConsole[]; + curTableList: ITreeNode[]; } export interface IWorkspaceModelType { @@ -33,6 +34,7 @@ export interface IWorkspaceModelType { setCurWorkspaceParams: Reducer; setDoubleClickTreeNodeData: Reducer; //TS TODO: setConsoleList: Reducer; + setOpenConsoleList: Reducer; setCurTableList: Reducer; }; effects: { @@ -50,6 +52,7 @@ const WorkspaceModel: IWorkspaceModelType = { curWorkspaceParams: getCurrentWorkspaceDatabase(), doubleClickTreeNodeData: undefined, consoleList: [], + openConsoleList: [], curTableList: [], }, @@ -83,6 +86,14 @@ const WorkspaceModel: IWorkspaceModelType = { consoleList: payload, }; }, + + setOpenConsoleList(state, { payload }) { + return { + ...state, + openConsoleList: payload, + }; + }, + setCurTableList(state, { payload }) { return { ...state, @@ -99,22 +110,17 @@ const WorkspaceModel: IWorkspaceModelType = { payload: res, }); }, - *fetchGetSavedConsole({ payload }, { put }) { + *fetchGetSavedConsole({ payload, callback }, { put }) { const res = (yield historyService.getSavedConsoleList({ pageNo: 1, pageSize: 999, - status: ConsoleStatus.RELEASE, + ...payload })) as IPageResponse; - yield put({ - type: 'setConsoleList', - payload: res.data, - }); + if (callback && typeof callback === 'function') { + callback(res); + } }, - *fetchGetCurTableList({ payload, callback }, { put,call }) { - // yield put({ - // type: 'setCurTableList', - // payload: undefined, - // }); + *fetchGetCurTableList({ payload, callback }, { put, call }) { const res = (yield treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 687e33d8c..7c71eb78a 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -152,7 +152,7 @@ function TreeNodeRightClick(props: IProps) { extraParams: curWorkspaceParams, }, callback: () => { - message.success(i18n('common.text.operateSuccessfully')) + message.success(i18n('common.text.submittedSuccessfully')) } }) }) 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 32a399963..fd345bdea 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -97,12 +97,20 @@ } .saveItem { - cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; padding: 0px 10px; margin-bottom: 2px; line-height: 26px; border-radius: 2px; user-select: none; + cursor: pointer; + .moreButton { + flex-shrink: 0; + display: none; + transform: rotate(90deg); + } &:hover { background-color: var(--color-hover-bg); 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 a53679f0f..2e30ddfa0 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -2,13 +2,14 @@ import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'r import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Cascader, Divider, Input } from 'antd'; +import { Cascader, Divider, Input, Dropdown } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; +import historyServer from '@/service/history'; import Tree from '../Tree'; -import { TreeNodeType, ConsoleStatus } from '@/constants'; +import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateTreeNode, approximateList } from '@/utils'; @@ -220,7 +221,7 @@ const RenderTableBox = dvaModel(function (props: any) {
:
-
Table
+
{i18n('common.text.table')}
openSearch()}>
@@ -250,6 +251,12 @@ const RenderSaveBox = dvaModel(function (props: any) { status: ConsoleStatus.RELEASE, ...curWorkspaceParams, }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setConsoleList', + payload: res.data, + }) + } }); }, [curWorkspaceParams]); @@ -278,6 +285,32 @@ const RenderSaveBox = dvaModel(function (props: any) { setSearchedList(approximateList(consoleList, value,)) } + function openConsole(data: IConsole) { + let p: any = { + id: data.id, + tabOpened: ConsoleOpenedStatus.IS_OPEN + }; + historyServer.updateSavedConsole(p).then((res) => { + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + tabOpened: ConsoleOpenedStatus.IS_OPEN, + ...curWorkspaceParams + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: res.data, + }) + } + }) + }); + } + + function deleteSaved(data: IConsole) { + + } + return (
@@ -296,7 +329,7 @@ const RenderSaveBox = dvaModel(function (props: any) {
:
-
Saved
+
{i18n('workspace.title.saved')}
openSearch()}>
@@ -307,12 +340,42 @@ const RenderSaveBox = dvaModel(function (props: any) { {(searchedList || consoleList)?.map((t: IConsole) => { return ( -
+
+
+ { + openConsole(t) + } + }, + { + key: 'delete', + label: i18n('common.button.delete'), + onClick: () => { + deleteSaved(t) + }, + }, + ], + }} + > +
+ +
+
+ +
); })} -
-
+
+
); }); 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 476427007..fc0348659 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -8,7 +8,6 @@ import historyService from '@/service/history'; import Tabs from '@/components/Tabs'; import LoadingContent from '@/components/Loading/LoadingContent'; import WorkspaceRightItem from '../WorkspaceRightItem'; - import { IWorkspaceModelType } from '@/models/workspace'; interface IProps { @@ -19,10 +18,9 @@ interface IProps { const WorkspaceRight = memo(function (props) { const { className } = props; - const [consoleList, setConsoleList] = useState(); const [activeConsoleId, setActiveConsoleId] = useState(); const { workspaceModel, dispatch } = props; - const { databaseAndSchema, curWorkspaceParams, doubleClickTreeNodeData } = workspaceModel; + const { databaseAndSchema, curWorkspaceParams, doubleClickTreeNodeData, openConsoleList } = workspaceModel; useEffect(() => { getConsoleList(); @@ -30,13 +28,13 @@ const WorkspaceRight = memo(function (props) { useEffect(() => { // 这里只处理没有console的情况下 - if (!doubleClickTreeNodeData || consoleList?.length) { + if (!doubleClickTreeNodeData || openConsoleList?.length) { return } const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType, tableName } = extraParams || {}; - const ddl = `SELECT * FROM ${tableName};`; + const ddl = `SELECT * FROM ${tableName};\n`; const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); let p: any = { name: name, @@ -57,22 +55,22 @@ const WorkspaceRight = memo(function (props) { }, [doubleClickTreeNodeData]); useEffect(() => { - if (!consoleList?.length) { + if (!openConsoleList?.length) { setActiveConsoleId(undefined); } else if (!activeConsoleId) { - setActiveConsoleId(consoleList[0].id); + setActiveConsoleId(openConsoleList[0].id); } else { let flag = false; - consoleList.forEach(t => { + openConsoleList.forEach(t => { if (t.id === activeConsoleId) { flag = true } }) if (!flag) { - setActiveConsoleId(consoleList[consoleList.length - 1].id) + setActiveConsoleId(openConsoleList[openConsoleList.length - 1].id) } } - }, [consoleList]) + }, [openConsoleList]) function getConsoleList() { let p: any = { @@ -82,26 +80,16 @@ const WorkspaceRight = memo(function (props) { ...curWorkspaceParams, }; - historyService.getSavedConsoleList(p).then((res) => { - const newWindowList: IConsole[] = []; - res.data?.map((item, index) => { - if (item.connectable) { - newWindowList.push({ - id: item.id, - ddl: item.ddl, - name: item.name, - type: item.type, - status: item.status, - databaseName: item.databaseName, - dataSourceName: item.dataSourceName, - dataSourceId: item.dataSourceId, - schemaName: item.schemaName, - connectable: true - }); - } - }); - setConsoleList(newWindowList); - }); + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: p, + callback: (res: any) => { + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: res.data, + }) + } + }) } function onChange(key: number | string) { @@ -120,7 +108,7 @@ const WorkspaceRight = memo(function (props) { const addConsole = (params?: ICreateConsole) => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams let p = { - name: `new console${consoleList?.length}`, + name: `new console${openConsoleList?.length}`, ddl: '', dataSourceId: dataSourceId!, databaseName: databaseName!, @@ -138,13 +126,13 @@ const WorkspaceRight = memo(function (props) { const closeWindowTab = (key: number) => { let newActiveKey = activeConsoleId; let lastIndex = -1; - consoleList?.forEach((item, i) => { + openConsoleList?.forEach((item, i) => { if (item.id === key) { lastIndex = i - 1; } }); - const newPanes = consoleList?.filter((item) => item.id !== key) || []; + const newPanes = openConsoleList?.filter((item) => item.id !== key) || []; if (newPanes.length && newActiveKey === key) { if (lastIndex >= 0) { newActiveKey = newPanes[lastIndex].id; @@ -152,7 +140,10 @@ const WorkspaceRight = memo(function (props) { newActiveKey = newPanes[0].id; } } - setConsoleList(newPanes); + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: newPanes + }) setActiveConsoleId(newActiveKey); let p: any = { @@ -160,7 +151,7 @@ const WorkspaceRight = memo(function (props) { tabOpened: 'n', }; - const window = consoleList?.find((t) => t.id === key); + const window = openConsoleList?.find((t) => t.id === key); if (!window?.status) { return; } @@ -179,13 +170,13 @@ const WorkspaceRight = memo(function (props) { return (
- +
{ + tabs={(openConsoleList || [])?.map((t, i) => { return { label: t.name, value: t.id, @@ -193,7 +184,7 @@ const WorkspaceRight = memo(function (props) { })} />
- {consoleList?.map((t, index) => { + {openConsoleList?.map((t, index) => { return
(function (props) { const draggableRef = useRef(); const [appendValue, setAppendValue] = useState({ text: data.initDDL }); const [resultData, setResultData] = useState([]); - const { doubleClickTreeNodeData, curTableList } = workspaceModel; + const { doubleClickTreeNodeData, curTableList, curWorkspaceParams } = workspaceModel; const [showResult, setShowResult] = useState(false); useEffect(() => { @@ -66,6 +66,18 @@ const WorkspaceRightItem = memo(function (props) { onConsoleSave={() => { dispatch({ type: 'workspace/fetchGetSavedConsole', + payload: { + pageNo: 1, + pageSize: 999, + status: ConsoleStatus.RELEASE, + ...curWorkspaceParams, + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setConsoleList', + payload: res.data, + }) + } }); }} tables={curTableList || []} From c8c8198a21afd9033ed1044b638b19fb44ec3d7f Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 14:51:50 +0800 Subject: [PATCH 0126/1069] order by saved queries --- .../server/domain/api/param/OperationPageQueryParam.java | 5 +++++ .../server/domain/core/impl/OperationServiceImpl.java | 5 +++++ .../operation/saved/request/OperationQueryRequest.java | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java index 21e2385f5..c0da1b331 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java @@ -36,4 +36,9 @@ public class OperationPageQueryParam extends PageQueryParam { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * orderBy modify time desc + */ + private Boolean orderByDesc; } 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 cf5c0fbdb..f6b290ec3 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 @@ -105,6 +105,11 @@ public PageResult queryPage(OperationPageQueryParam param) { Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); page.setOptimizeCountSql(false); + if (param.getOrderByDesc()) { + queryWrapper.orderByDesc("gmt_modified"); + } else { + queryWrapper.orderByAsc("gmt_modified"); + } IPage iPage = operationSavedMapper.selectPage(page, queryWrapper); List userSavedDdlDOS = operationConverter.do2dto(iPage.getRecords()); if (CollectionUtils.isEmpty(userSavedDdlDOS)) { 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 1cf68a328..3f993ed74 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 @@ -36,4 +36,9 @@ public class OperationQueryRequest extends PageQueryRequest { * ddl语句状态:DRAFT/RELEASE */ private String status; + + /** + * orderBy modify time desc + */ + private Boolean orderByDesc; } From c5abcc2b6f1ba3514a3ebe634f6ff818ffe6c7db Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 14:58:49 +0800 Subject: [PATCH 0127/1069] order by saved queries --- .../server/domain/api/param/OperationPageQueryParam.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java index c0da1b331..470ac7d71 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java @@ -40,5 +40,5 @@ public class OperationPageQueryParam extends PageQueryParam { /** * orderBy modify time desc */ - private Boolean orderByDesc; + private boolean orderByDesc; } From f2b460f63b711f3c88c69a010ca0e72c1dd90e4b Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 14:59:43 +0800 Subject: [PATCH 0128/1069] order by saved queries --- .../chat2db/server/domain/core/impl/OperationServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f6b290ec3..8252afbdf 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 @@ -105,7 +105,7 @@ public PageResult queryPage(OperationPageQueryParam param) { Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); page.setOptimizeCountSql(false); - if (param.getOrderByDesc()) { + if (param.isOrderByDesc()) { queryWrapper.orderByDesc("gmt_modified"); } else { queryWrapper.orderByAsc("gmt_modified"); From f0bf220d0f27962da84ae08b2cbd33dca00bc014 Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 15:09:20 +0800 Subject: [PATCH 0129/1069] add azure ai --- .../ai/azure/client/AzureOpenAIClient.java | 12 ++++++------ .../web/api/controller/config/ConfigController.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java index 893c489b7..b2a3e1b93 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java @@ -54,7 +54,7 @@ private static AzureOpenAiStreamClient singleton() { } public static void refresh() { - String apikey = ""; + String key = ""; String apiEndpoint = ""; String deployId = "gpt-3.5-turbo"; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); @@ -64,15 +64,15 @@ public static void refresh() { } Config config = configService.find(AZURE_CHATGPT_API_KEY).getData(); if (config != null) { - apikey = config.getContent(); + key = config.getContent(); } Config deployConfig = configService.find(AZURE_CHATGPT_DEPLOYMENT_ID).getData(); - if (config != null) { + if (deployConfig != null) { deployId = deployConfig.getContent(); } - log.info("refresh azure openai apikey:{}", maskApiKey(apikey)); - OPEN_AI_CLIENT = new AzureOpenAiStreamClient(apiKey, apiEndpoint, deployId); - apiKey = apikey; + log.info("refresh azure openai apikey:{}", maskApiKey(key)); + OPEN_AI_CLIENT = new AzureOpenAiStreamClient(key, apiEndpoint, deployId); + apiKey = key; } private static String maskApiKey(String input) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index 002d60f36..279e2f451 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -131,7 +131,7 @@ private void saveAzureAIConfig(AISystemConfigRequest request) { SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID).content( request.getAzureDeploymentId()).build(); configService.createOrUpdate(modelParam); - RestAIClient.refresh(); + AzureOpenAIClient.refresh(); } @GetMapping("/system_config/{code}") From 6bc0ebca24fc6d2bb2deb305b0729b6073e197c9 Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 15:15:40 +0800 Subject: [PATCH 0130/1069] add azure ai --- .../api/controller/ai/azure/client/AzureOpenAIClient.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java index b2a3e1b93..0c841d317 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java @@ -8,6 +8,7 @@ import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; /** * @author jipengfei @@ -59,15 +60,15 @@ public static void refresh() { String deployId = "gpt-3.5-turbo"; ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config apiHostConfig = configService.find(AZURE_CHATGPT_ENDPOINT).getData(); - if (apiHostConfig != null) { + if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { apiEndpoint = apiHostConfig.getContent(); } Config config = configService.find(AZURE_CHATGPT_API_KEY).getData(); - if (config != null) { + if (config != null && StringUtils.isNotBlank(config.getContent())) { key = config.getContent(); } Config deployConfig = configService.find(AZURE_CHATGPT_DEPLOYMENT_ID).getData(); - if (deployConfig != null) { + if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { deployId = deployConfig.getContent(); } log.info("refresh azure openai apikey:{}", maskApiKey(key)); From 21cfa013b43d1f6129167ec5012d9e128de92faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 15:28:39 +0800 Subject: [PATCH 0131/1069] =?UTF-8?q?feat:=E5=88=A0=E9=99=A4console?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/index.tsx | 2 +- chat2db-client/src/constants/common.ts | 2 +- .../src/pages/main/connection/index.less | 4 +- .../src/pages/main/dashboard/index.less | 28 ++++++--- .../src/pages/main/dashboard/index.tsx | 17 ++--- .../components/WorkspaceLeft/index.less | 12 +++- .../components/WorkspaceLeft/index.tsx | 63 +++++++++++++++++-- .../components/WorkspaceRight/index.tsx | 2 +- .../components/WorkspaceRightItem/index.tsx | 3 +- chat2db-client/src/service/history.ts | 7 ++- 10 files changed, 109 insertions(+), 31 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 7b2ce9d46..d5e2d6715 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -203,7 +203,7 @@ export function SettingAI() { newChatgptConfig.apiHost = newChatgptConfig.apiHost + '/'; } configService.setChatGptSystemConfig(newChatgptConfig).then((res) => { - message.success(i18n('common.message.successfulConfig')); + message.success(i18n('common.text.submittedSuccessfully')); }); } diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 6e5491127..7b1ff0552 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -21,4 +21,4 @@ export enum ConsoleOpenedStatus { export enum ConsoleStatus { DRAFT = 'DRAFT', RELEASE = 'RELEASE', -} +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 29f292208..8c8c84db2 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -14,7 +14,9 @@ width: 220px; overflow: hidden; background-color: var(--color-bg-elevated); - border-left: 1px solid var(--color-border); + border: 1px solid var(--color-border-secondary); + border-top: 0px; + border-bottom: 0px; } .pageTitle { diff --git a/chat2db-client/src/pages/main/dashboard/index.less b/chat2db-client/src/pages/main/dashboard/index.less index 112be652e..3696cbdca 100644 --- a/chat2db-client/src/pages/main/dashboard/index.less +++ b/chat2db-client/src/pages/main/dashboard/index.less @@ -6,13 +6,23 @@ height: 100%; width: 100%; } +.dragBox { + width: 220px; + height: 100%; + overflow: hidden; + border: 1px solid var(--color-border-secondary); + border-top: 0px; + border-bottom: 0px; +} .boxLeft { - min-width: 200px; - max-width: 400px; + display: flex; + flex-direction: column; height: 100%; - padding-top: 20px; background-color: var(--color-bg-elevated); + padding: 10px 20px 0px; + box-sizing: border-box; + min-width: 200px; } .createDashboardBtn { @@ -53,10 +63,11 @@ justify-content: space-between; align-items: center; border-radius: var(--border-radius-l-g); - - .itemTitle{ + margin-bottom: 4px; + + .itemTitle { font-weight: 400; - &:hover{ + &:hover { color: var(--color-primary); } } @@ -98,7 +109,8 @@ margin-bottom: 24px; } -.boxRightContent {} +.boxRightContent { +} .boxRightContentRow { display: flex; @@ -111,4 +123,4 @@ &:last-child { margin-right: 0px; } -} \ No newline at end of file +} diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 7351c6ffa..0cc99812f 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useRef } from 'react'; import { Button, Dropdown, Form, Input, Modal, message } from 'antd'; import { connect, Dispatch } from 'umi'; import cs from 'classnames'; @@ -43,6 +43,7 @@ function Chart(props: IProps) { const [form] = Form.useForm(); // 创建一个表单实例 const [messageApi, contextHolder] = message.useMessage(); + const draggableRef = useRef(); useEffect(() => { // 获取列表数据 @@ -228,13 +229,15 @@ function Chart(props: IProps) { return ( <> - -
-
-
{i18n('dashboard.title')}
- setOpenAddDashboard(true)} /> + +
+
+
+
{i18n('dashboard.title')}
+ setOpenAddDashboard(true)} /> +
+ {renderLeft()}
- {renderLeft()}
{renderContent()}
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 fd345bdea..d869d2734 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -14,7 +14,7 @@ display: flex; justify-content: space-between; align-items: center; - margin-bottom: 40px; + margin-bottom: 34px; flex-shrink: 0; } @@ -81,7 +81,7 @@ .saveBoxList { margin: 0px -10px; - height: calc(26px * 3); + height: calc(26px * 3 + 6px); overflow-y: auto; } @@ -133,6 +133,14 @@ font-weight: bold; } .iconBox { + display: flex; + align-items: center; + } + .refreshIcon { + margin-right: 10px; + } + .refreshIcon, + .searchIcon { cursor: pointer; &:hover { color: var(--color-primary); 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 2e30ddfa0..f8f2ec7c1 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -203,6 +203,18 @@ const RenderTableBox = dvaModel(function (props: any) { setSearchedTableList(approximateTreeNode(curTableList, value)) } + function refreshTableList() { + if (curWorkspaceParams?.dataSourceId) { + dispatch({ + type: 'workspace/fetchGetCurTableList', + payload: { + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + } + }) + } + } + return (
@@ -222,8 +234,13 @@ const RenderTableBox = dvaModel(function (props: any) { :
{i18n('common.text.table')}
-
openSearch()}> - +
+
refreshTableList()}> + +
+
openSearch()}> + +
} @@ -308,7 +325,38 @@ const RenderSaveBox = dvaModel(function (props: any) { } function deleteSaved(data: IConsole) { - + let p: any = { + id: data.id, + }; + historyServer.deleteSavedConsole(p).then((res) => { + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + tabOpened: ConsoleOpenedStatus.IS_OPEN, + ...curWorkspaceParams + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: res.data, + }) + } + }) + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + status: ConsoleStatus.RELEASE, + orderByDesc: true, + ...curWorkspaceParams + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setConsoleList', + payload: res.data, + }) + } + }) + }); } return ( @@ -330,8 +378,13 @@ const RenderSaveBox = dvaModel(function (props: any) { :
{i18n('workspace.title.saved')}
-
openSearch()}> - +
+ {/*
refreshTableList()}> + +
*/} +
openSearch()}> + +
} 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 fc0348659..774d69759 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -156,7 +156,7 @@ const WorkspaceRight = memo(function (props) { return; } if (window!.status === 'DRAFT') { - historyService.deleteWindowTab({ id: window!.id }); + historyService.deleteSavedConsole({ id: window!.id }); } else { historyService.updateSavedConsole(p); } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index dc55ab967..7cd006d9d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -67,9 +67,8 @@ const WorkspaceRightItem = memo(function (props) { dispatch({ type: 'workspace/fetchGetSavedConsole', payload: { - pageNo: 1, - pageSize: 999, status: ConsoleStatus.RELEASE, + orderByDesc: true, ...curWorkspaceParams, }, callback: (res: any) => { diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index 687369e74..f080d1906 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -23,13 +23,14 @@ export interface IUpdateConsoleParams { const saveConsole = createRequest('/api/operation/saved/create', { method: 'post' }); -const getWindowTab = createRequest<{ id: number }, number>('/api/operation/saved/:id', { method: 'get' }); +// orderByDesc true 降序 +const getWindowTab = createRequest<{ id: number, orderByDesc: boolean }, number>('/api/operation/saved/:id', { method: 'get' }); const updateSavedConsole = createRequest & {id: number}, number>('/api/operation/saved/update', { method: 'post' }); const getSavedConsoleList = createRequest>('/api/operation/saved/list', {}); -const deleteWindowTab = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); +const deleteSavedConsole = createRequest<{ id: number }, string>('/api/operation/saved/:id', { method: 'delete' }); const createHistory = createRequest('/api/operation/log/create', { method: 'post' }); @@ -40,7 +41,7 @@ export default { updateSavedConsole, getHistoryList, saveConsole, - deleteWindowTab, + deleteSavedConsole, createHistory, getWindowTab } \ No newline at end of file From 4f7b46c46a5bdde2aea23230e35caaa81fc0dc55 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 15:41:35 +0800 Subject: [PATCH 0132/1069] feat: Optimize table --- chat2db-client/package.json | 1 + .../components/Console/ChatInput/index.less | 30 +++++- .../components/Console/ChatInput/index.tsx | 19 ++-- .../src/components/Console/index.tsx | 11 +- .../src/components/SearchResult/TableBox.less | 37 +++++-- .../src/components/SearchResult/TableBox.tsx | 101 ++++++++++++------ .../src/components/SearchResult/index.less | 2 +- .../src/components/SearchResult/index.tsx | 4 +- chat2db-client/src/components/Tabs/index.less | 4 +- chat2db-client/src/hooks/useTheme.ts | 26 ++--- chat2db-client/src/i18n/en-us/chat.ts | 7 +- chat2db-client/src/i18n/zh-cn/chat.ts | 3 +- chat2db-client/src/typings/database.ts | 2 +- chat2db-client/yarn.lock | 41 ++++++- 14 files changed, 203 insertions(+), 85 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 1882a9fe1..b687d9267 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -39,6 +39,7 @@ "react-monaco-editor": "^0.52.0", "react-sortablejs": "^6.1.4", "sql-formatter": "^12.2.1", + "styled-components": "^6.0.1", "umi": "^4.0.70", "umi-request": "^1.4.0", "uuid": "^9.0.0" diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 96fb98b7d..e4db2ef38 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -1,4 +1,4 @@ -.chat_wrapper { +.chatWrapper { display: flex; align-items: center; padding: 12px 20px; @@ -6,7 +6,21 @@ border-bottom: 1px solid var(--color-border-secondary); } -.chat_ai { +.chatShortcut { + + padding: 4px 8px; + color: #8a9099; + font-size: 12px; + line-height: 1; + white-space: nowrap; + background-color: #fffc; + border-radius: 4px; + border: 1px solid #d0d5d8; + + margin-right: 10px; +} + +.chatAi { width: 16px; height: 16px; margin-right: 14px; @@ -34,4 +48,16 @@ background-color: var(--color-bg-elevated); padding: 4px 12px; +} + +.aiSelectedTable{ + display: flex; + flex-direction: column; + max-width: 140px; +} + +.aiSelectedTableTips{ + padding-bottom: 4px; + border-bottom: 1px solid var(--color-border-secondary); + margin-bottom: 4px } \ No newline at end of file diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index d871891a4..142a0e3bb 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -4,6 +4,7 @@ import AIImg from '@/assets/img/ai.svg'; import { Checkbox, Dropdown, Input, Popover } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; +import { WarningOutlined } from '@ant-design/icons'; interface IProps { value?: string; @@ -16,16 +17,20 @@ interface IProps { function ChatInput(props: IProps) { const onPressEnter = (e: any) => { - if (e.target.value) { - props.onPressEnter && props.onPressEnter(e.target.value); + if (!e.target.value) { + return; } + props.onPressEnter && props.onPressEnter(e.target.value); }; const renderSelectTable = () => { const { tables, selectedTables, onSelectTables } = props; return tables && tables.length ? ( -
- +
+ + {/* */} + {i18n('chat.input.remain.tooltip')} + + {/*
⌘ + ↵
*/}
@@ -55,12 +61,13 @@ function ChatInput(props: IProps) { }; return ( -
- +
+ diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 39b4185c6..7e735ca5a 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -80,7 +80,14 @@ function Console(props: IProps) { } }, [appendValue]); - const tableListName = useMemo(() => (props.tables || []).map((t) => t.name), [props.tables]); + const tableListName = useMemo(() => { + const tableList = (props.tables || []).map((t) => t.name); + + // 默认选中前八个 + setSelectedTables(tableList.slice(0, 8)); + + return tableList; + }, [props.tables]); const handleAiChat = (content: string, promptType: IPromptType) => { const { dataSourceId, databaseName, schemaName } = executeParams; @@ -227,7 +234,7 @@ function Console(props: IProps) { onSave={saveConsole} onExecute={executeSQL} options={props.editorOptions} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index 4df520508..cc534412e 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -17,35 +17,49 @@ .statusBar { - position: absolute; + position: sticky; bottom: 0; - left: 0; - right: 0; + z-index: 30; padding: 4px 8px; font-size: 12px; display: flex; + justify-content: start; align-items: center; + border-top: 1px solid var(--color-border-secondary); background-color: var(--color-bg-elevated); } .tableItem { width: 100%; - .f-lines(1); + // .f-lines(1); cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + + // height: 100%; + max-height: 120px; + overflow-y: auto; + + &::-webkit-scrollbar { + display: none; + } &:hover .tableHoverBox { - display: flex; + // display: flex !important; + display: block; } } .tableHoverBox { + width: 40px; display: none; align-items: center; - position: absolute; + // position: absolute; background-color: var(--color-bg); - top: 0; - right: 0; - bottom: 0; + // top: 0; + // right: 0; + // bottom: 0; i { font-size: 15px; @@ -54,5 +68,8 @@ i:hover { color: var(--custom-primary-color); + } -} \ No newline at end of file +} + +.table {} \ No newline at end of file diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index b55ec7019..f556cab6b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -2,13 +2,17 @@ import React, { useEffect, useMemo, useState } from 'react'; import { TableDataType } from '@/constants/table'; import { IManageResultData, ITableHeaderItem } from '@/typings/database'; import { formatDate } from '@/utils/date'; -import { message, Modal, Table } from 'antd'; -import { applyTransforms, BaseTable, BaseTableProps, collectNodes, makeColumnResizeTransform } from 'ali-react-table'; +import { Button, message, Modal, Table } from 'antd'; +import antd from 'antd'; +import { BaseTable, ArtColumn, useTablePipeline, features } from 'ali-react-table'; import Iconfont from '../Iconfont'; import classnames from 'classnames'; import StateIndicator from '../StateIndicator'; import MonacoEditor from '../Console/MonacoEditor'; +import { useTheme } from '@/hooks/useTheme'; +import styled from 'styled-components'; import styles from './TableBox.less'; +import { ThemeType } from '@/constants'; interface ITableProps { className?: string; @@ -20,11 +24,30 @@ interface IViewTableCellData { name: string; value: any; } +// --bgcolor: #333; +// --header-bgcolor: #45494f; +const DarkSupportBaseTable: any = styled(BaseTable)` + &.dark { + --bgcolor: #131418; + --header-bgcolor: #0a0b0c; + --hover-bgcolor: #46484a; + --header-hover-bgcolor: #606164; + --highlight-bgcolor: #191a1b; + --header-highlight-bgcolor: #191a1b; + --color: #dadde1; + --header-color: #dadde1; + --lock-shadow: rgb(37 37 37 / 0.5) 0 0 6px 2px; + --border-color: #3c4045; + } +`; export default function TableBox(props: ITableProps) { const { className, data, key } = props; const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); + const [appTheme] = useTheme(); + + const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); function viewTableCell(data: IViewTableCellData) { setViewTableCellData(data); @@ -39,24 +62,28 @@ export default function TableBox(props: ITableProps) { setViewTableCellData(null); } - const columns = useMemo( + const columns: ArtColumn[] = useMemo( () => - (headerList || []).map((item: any) => ({ - title: item.name, - dataIndex: item.name, + (headerList || []).map((item, index) => ({ code: item.name, + name: item.name, key: item.name, - type: item.dataType, - sorter: (a: any, b: any) => a[item.name] - b[item.name], - render: (value: any) => ( -
-
- - + lock: index === 0, + width: 120, + // type: item.dataType, + // sorter: (a: any, b: any) => a[item.name] - b[item.name], + render: (value: any, row: any, rowIndex: number) => { + console.log('rowIndex', rowIndex); + return ( +
+
{value}
+
+ + +
- {value} -
- ), + ); + }, })), [headerList], ); @@ -71,11 +98,11 @@ export default function TableBox(props: ITableProps) { const { dataType: type } = headerList[index] || {}; // console.log('headerList[rowIndex]', headerList[rowIndex]); if (type === TableDataType.DATETIME && i) { - rowData[columns[index].title] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); + rowData[columns[index].name] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); } else if (i === null) { - rowData[columns[index].title] = '[null]'; + rowData[columns[index].name] = '[null]'; } else { - rowData[columns[index].title] = i; + rowData[columns[index].name] = i; } }); rowData.key = rowIndex; @@ -84,15 +111,32 @@ export default function TableBox(props: ITableProps) { } }, [dataList, columns]); - console.log('dataList', dataList); + const pipeline = useTablePipeline() + .input({ dataSource: tableData, columns }) + .use( + features.columnResize({ + fallbackSize: 120, + minSize: 60, + maxSize: 1080, + // handleBackground: '#ddd', + // handleHoverBackground: '#aaa', + // handleActiveBackground: '#89bff7', + }), + ); return (
{columns.length ? ( -
+ //
+ <> + +
{`结果:${description}. 耗时:${duration}ms`}
+ ) : ( )} -
{`结果:${description}. 耗时:${duration}ms`}
+ /> ); } -function pipeline(arg0: { - sizes: number; - onChangeSizes: React.Dispatch>; - appendExpander: boolean; - expanderVisibility: string; - disableUserSelectWhenResizing: boolean; - minSize: number; - maxSize: number; -}): import('ali-react-table').Transform<{ columns: any; dataSource: any }> { - throw new Error('Function not implemented.'); -} diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 4b3070241..cd85523af 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -7,7 +7,7 @@ } .tabs { - padding-top: 10px; + padding-top: 8px; border-bottom: 1px solid var(--color-border); } diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index d61cdbb50..775503d72 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -44,7 +44,7 @@ const handleTabs = (result: IManageResultData[]) => { export default memo(function SearchResult({ className, manageResultDataList = [] }) { const [isUnfold, setIsUnfold] = useState(true); const [currentTab, setCurrentTab] = useState(0); - const [resultDataList, setResultDataList] = useState([]); + const [resultDataList, setResultDataList] = useState([]); const [tabs, setTabs] = useState([]); useEffect(() => { @@ -91,7 +91,7 @@ export default memo(function SearchResult({ className, manageResultDataL return renderEmpty(); } - return (resultDataList || []).map((item: any, index: number) => { + return (resultDataList || []).map((item, index: number) => { if (item.success) { return ( diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 33abfde11..0cbe4c327 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -70,8 +70,8 @@ display: flex; align-items: center; padding: 0px 10px; - line-height: 30px; - height: 30px; + line-height: 20px; + height: 24px; width: 100px; margin: 0px 2px; cursor: pointer; diff --git a/chat2db-client/src/hooks/useTheme.ts b/chat2db-client/src/hooks/useTheme.ts index 5b1903ff2..50e441a1a 100644 --- a/chat2db-client/src/hooks/useTheme.ts +++ b/chat2db-client/src/hooks/useTheme.ts @@ -1,14 +1,9 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { addColorSchemeListener, colorSchemeListeners } from '@/layouts'; import { getOsTheme } from '@/utils'; import { ITheme } from '@/typings'; import { ThemeType, PrimaryColorType } from '@/constants'; -import { - getPrimaryColor, - getTheme, - setPrimaryColor, - setTheme, -} from '@/utils/localStorage'; +import { getPrimaryColor, getTheme, setPrimaryColor, setTheme } from '@/utils/localStorage'; const initialTheme = () => { let backgroundColor = getTheme() || ThemeType.Dark; @@ -24,12 +19,11 @@ const initialTheme = () => { }; }; -export function useTheme(): [ - T, - React.Dispatch>, -] { +export function useTheme(): [T, React.Dispatch>] { const [appTheme, setAppTheme] = useState(initialTheme()); - + + // const isDark = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); + useEffect(() => { const uuid = addColorSchemeListener(setAppTheme); return () => { @@ -37,14 +31,10 @@ export function useTheme(): [ }; }, []); - function handleAppThemeChange(theme: { - backgroundColor: ThemeType; - primaryColor: PrimaryColorType; - }) { + function handleAppThemeChange(theme: { backgroundColor: ThemeType; primaryColor: PrimaryColorType }) { if (theme.backgroundColor === ThemeType.FollowOs) { theme.backgroundColor = - window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches + window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeType.Dark : ThemeType.Light; } diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts index 8542eccf0..b1a03f1d1 100644 --- a/chat2db-client/src/i18n/en-us/chat.ts +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -1,3 +1,4 @@ -export default{ - 'chat.input.remain': '{1} remaining' -} \ No newline at end of file +export default { + 'chat.input.remain': '{1} remaining', + 'chat.input.remain.tooltip': 'Table schema added to prompt.', +}; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index bce077515..c26e5e3ba 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -1,3 +1,4 @@ export default{ - 'chat.input.remain': '剩余 {1} 次' + 'chat.input.remain': '剩余 {1} 次', + 'chat.input.remain.tooltip': '选中表的schema将会被添加到prompt中' } \ No newline at end of file diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 9758cddcf..263e7769e 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -9,7 +9,7 @@ export interface IDatabase { export interface ITableHeaderItem { dataType: TableDataType; - stringValue: string; + name: string; } export interface IManageResultData { diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 28d659770..198e86a51 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1584,7 +1584,7 @@ resolved "https://registry.npmmirror.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== -"@emotion/is-prop-valid@^1.1.0": +"@emotion/is-prop-valid@^1.1.0", "@emotion/is-prop-valid@^1.2.1": version "1.2.1" resolved "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc" integrity sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw== @@ -2481,6 +2481,11 @@ resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== +"@types/stylis@^4.0.2": + version "4.2.0" + resolved "https://registry.npmmirror.com/@types/stylis/-/stylis-4.2.0.tgz#199a3f473f0c3a6f6e4e1b17cdbc967f274bdc6b" + integrity sha512-n4sx2bqL0mW1tvDf/loQ+aMX7GQD3lc3fkCMC55VFNDu/vBOabO+LTIeXKM14xK0ppk5TUGcWRjiSpIlUpghKw== + "@types/use-sync-external-store@^0.0.3": version "0.0.3" resolved "https://registry.npmmirror.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz#b6725d5f4af24ace33b36fafd295136e75509f43" @@ -4277,7 +4282,7 @@ csso@^4.2.0: dependencies: css-tree "^1.1.2" -csstype@^3.0.10, csstype@^3.0.2: +csstype@^3.0.10, csstype@^3.0.2, csstype@^3.1.2: version "3.1.2" resolved "https://registry.npmmirror.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== @@ -7576,7 +7581,7 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.21, postcss@^8.4.7: +postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.7: version "8.4.24" resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.24.tgz#f714dba9b2284be3cc07dbd2fc57ee4dc972d2df" integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg== @@ -9041,6 +9046,31 @@ styled-components@6.0.0-rc.0: shallowequal "^1.1.0" supports-color "^5.5.0" +styled-components@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.0.1.tgz#f4db3195e5e510ff3cd0909b1a7b103f4440cbbc" + integrity sha512-6VAlf5A9KZJOnX54becRCLnBFSfeqP+q3raTCdPDPFg4HOy7MNlnWFqAq3sHlQVDdZh5jcsDwK06vTU3NMO6yQ== + dependencies: + "@babel/cli" "^7.21.0" + "@babel/core" "^7.21.0" + "@babel/helper-module-imports" "^7.18.6" + "@babel/plugin-external-helpers" "^7.18.6" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.20.7" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@babel/traverse" "^7.21.2" + "@emotion/is-prop-valid" "^1.2.1" + "@emotion/unitless" "^0.8.0" + "@types/stylis" "^4.0.2" + css-to-react-native "^3.2.0" + csstype "^3.1.2" + postcss "^8.4.23" + shallowequal "^1.1.0" + stylis "^4.3.0" + tslib "^2.5.0" + stylelint-config-recommended@^7.0.0: version "7.0.0" resolved "https://registry.npmmirror.com/stylelint-config-recommended/-/stylelint-config-recommended-7.0.0.tgz#7497372ae83ab7a6fffc18d7d7b424c6480ae15e" @@ -9058,6 +9088,11 @@ stylis@^4.0.13, stylis@^4.1.4: resolved "https://registry.npmmirror.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== +stylis@^4.3.0: + version "4.3.0" + resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" + integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== + sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" From 3a550c642b5986fc36588384d2569f015162640e Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 15:52:24 +0800 Subject: [PATCH 0133/1069] azure test with open ai --- .../server/domain/api/model/ChatGptConfig.java | 15 +++++++++++++++ .../api/controller/config/ConfigController.java | 6 ++++++ 2 files changed, 21 insertions(+) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ChatGptConfig.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ChatGptConfig.java index 583347e66..c35363366 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ChatGptConfig.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/ChatGptConfig.java @@ -47,4 +47,19 @@ public class ChatGptConfig { * 非必填,默认值为TRUE */ private Boolean restAiStream = Boolean.TRUE; + + /** + * Get Azure OpenAI key credential from the Azure Portal + */ + private String azureApiKey; + + /** + * Get Azure OpenAI endpoint from the Azure Portal + */ + private String azureEndpoint; + + /** + * deploymentId of the deployed model, default gpt-3.5-turbo + */ + private String azureDeploymentId; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index 279e2f451..473e73921 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -154,6 +154,9 @@ public DataResult getChatGptSystemConfig() { DataResult aiSqlSource = configService.find(RestAIClient.AI_SQL_SOURCE); DataResult restAiUrl = configService.find(RestAIClient.REST_AI_URL); DataResult restAiHttpMethod = configService.find(RestAIClient.REST_AI_STREAM_OUT); + DataResult azureApiKey = configService.find(AzureOpenAIClient.AZURE_CHATGPT_API_KEY); + DataResult azureEndpoint = configService.find(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT); + DataResult azureDeployId = configService.find(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID); ChatGptConfig config = new ChatGptConfig(); config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : null); config.setAiSqlSource(Objects.nonNull(aiSqlSource.getData()) ? aiSqlSource.getData().getContent() : null); @@ -163,6 +166,9 @@ public DataResult getChatGptSystemConfig() { config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : null); config.setHttpProxyHost(Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : null); config.setHttpProxyPort(Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : null); + config.setAzureApiKey(Objects.nonNull(azureApiKey.getData()) ? azureApiKey.getData().getContent() : null); + config.setAzureEndpoint(Objects.nonNull(azureEndpoint.getData()) ? azureEndpoint.getData().getContent() : null); + config.setAzureDeploymentId(Objects.nonNull(azureDeployId.getData()) ? azureDeployId.getData().getContent() : null); return DataResult.of(config); } } From 2b1ddbf7c58cf24dfcd8af2159e8154b599e4308 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 16:04:10 +0800 Subject: [PATCH 0134/1069] feat: table add sort --- .../src/components/SearchResult/TableBox.tsx | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index f556cab6b..d24379540 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -4,7 +4,7 @@ import { IManageResultData, ITableHeaderItem } from '@/typings/database'; import { formatDate } from '@/utils/date'; import { Button, message, Modal, Table } from 'antd'; import antd from 'antd'; -import { BaseTable, ArtColumn, useTablePipeline, features } from 'ali-react-table'; +import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; import Iconfont from '../Iconfont'; import classnames from 'classnames'; import StateIndicator from '../StateIndicator'; @@ -46,8 +46,25 @@ export default function TableBox(props: ITableProps) { const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); - const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); + // const [sorts, onChangeSorts] = useState([]); + + // useEffect(() => { + // const sorts: SortItem[] = (headerList || []).map((item) => ({ + // code: item.name, + // order: 'none', + // })); + // onChangeSorts(sorts); + // }, [headerList]); + + const defaultSorts: SortItem[] = useMemo( + () => + (headerList || []).map((item) => ({ + code: item.name, + order: 'none', + })), + [headerList], + ); function viewTableCell(data: IViewTableCellData) { setViewTableCellData(data); @@ -70,8 +87,6 @@ export default function TableBox(props: ITableProps) { key: item.name, lock: index === 0, width: 120, - // type: item.dataType, - // sorter: (a: any, b: any) => a[item.name] - b[item.name], render: (value: any, row: any, rowIndex: number) => { console.log('rowIndex', rowIndex); return ( @@ -84,6 +99,7 @@ export default function TableBox(props: ITableProps) { ); }, + features: { sortable: true }, })), [headerList], ); @@ -113,6 +129,14 @@ export default function TableBox(props: ITableProps) { const pipeline = useTablePipeline() .input({ dataSource: tableData, columns }) + .use( + features.sort({ + mode: 'single', + defaultSorts, + // sorts, + // onChangeSorts, + }), + ) .use( features.columnResize({ fallbackSize: 120, @@ -123,6 +147,7 @@ export default function TableBox(props: ITableProps) { // handleActiveBackground: '#89bff7', }), ); + return (
{columns.length ? ( From 4516db4538a409fb2f07ee05b607cff22d6eed9e Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 16:15:36 +0800 Subject: [PATCH 0135/1069] add i18n with chat --- .../chat2db/server/tools/common/util/I18nUtils.java | 11 +++++++++++ .../server/web/api/controller/ai/ChatController.java | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java index 8a9082c6b..dc21a1dee 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java @@ -9,6 +9,8 @@ import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; +import java.util.Locale; + /** * i18n utility * @@ -35,6 +37,15 @@ public static String getMessage(String messageCode, @Nullable Object[] args) { return messageSourceStatic.getMessage(messageCode, args, LocaleContextHolder.getLocale()); } + /** + * 是否是英文 + * + * @return + */ + public static Boolean isEn() { + return LocaleContextHolder.getLocale().equals(Locale.US); + } + @Override public void afterPropertiesSet() throws Exception { messageSourceStatic = messageSource; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index b31ad8ec7..e9fc3dd58 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -16,6 +16,7 @@ import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; import ai.chat2db.server.web.api.controller.ai.listener.AzureOpenAIEventSourceListener; import ai.chat2db.spi.model.TableColumn; @@ -501,6 +502,9 @@ private String buildPrompt(ChatQueryRequest queryRequest) { default: break; } + if (I18nUtils.isEn()) { + schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty); + } return schemaProperty; } } From a9ef530100056fbbc1a98754b7e7908874e54456 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 16:19:10 +0800 Subject: [PATCH 0136/1069] fix: fix number sort --- .../src/components/SearchResult/TableBox.tsx | 43 +++++++++++-------- chat2db-client/src/utils/sort.ts | 22 ++++++++++ 2 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 chat2db-client/src/utils/sort.ts diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index d24379540..6b767dca2 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -13,6 +13,7 @@ import { useTheme } from '@/hooks/useTheme'; import styled from 'styled-components'; import styles from './TableBox.less'; import { ThemeType } from '@/constants'; +import { compareStrings } from '@/utils/sort'; interface ITableProps { className?: string; @@ -81,26 +82,30 @@ export default function TableBox(props: ITableProps) { const columns: ArtColumn[] = useMemo( () => - (headerList || []).map((item, index) => ({ - code: item.name, - name: item.name, - key: item.name, - lock: index === 0, - width: 120, - render: (value: any, row: any, rowIndex: number) => { - console.log('rowIndex', rowIndex); - return ( -
-
{value}
-
- - + (headerList || []).map((item, index) => { + const { dataType, name } = item; + const isFirstLine = index === 0; + const isNumber = dataType === TableDataType.NUMERIC; + return { + code: name, + name: name, + key: name, + lock: isFirstLine, + width: 120, + render: (value: any, row: any, rowIndex: number) => { + return ( +
+
{value}
+
+ + +
-
- ); - }, - features: { sortable: true }, - })), + ); + }, + features: { sortable: isNumber ? compareStrings : true }, + }; + }), [headerList], ); diff --git a/chat2db-client/src/utils/sort.ts b/chat2db-client/src/utils/sort.ts new file mode 100644 index 000000000..13dd5ee52 --- /dev/null +++ b/chat2db-client/src/utils/sort.ts @@ -0,0 +1,22 @@ +/** + * 比较两个字符串数字的大小,包含大数情况 + * @param a + * @param b + * @returns + */ +export function compareStrings(a: string, b: string) { + // 比较字符串长度 + if (a.length !== b.length) { + return a.length - b.length; + } + + // 逐位比较字符的ASCII码值 + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) { + return a.charCodeAt(i) - b.charCodeAt(i); + } + } + + // 如果两个字符串完全相等,则返回0 + return 0; +} From c29d51d4dfc9cf01947a47cd1b6bf4c6aef29289 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 16:40:18 +0800 Subject: [PATCH 0137/1069] fix: fix table empty style --- chat2db-client/src/components/SearchResult/TableBox.less | 8 +++----- chat2db-client/src/components/SearchResult/TableBox.tsx | 2 +- chat2db-client/src/components/SearchResult/index.less | 6 +++++- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index cc534412e..4e6e271fe 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -11,10 +11,6 @@ background-color: var(--color-bg-100); } -.cursorTableBox { - display: block; -} - .statusBar { position: sticky; @@ -72,4 +68,6 @@ } } -.table {} \ No newline at end of file +.table { + --border-color: transparent; +} \ No newline at end of file diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index 6b767dca2..df534c52b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -38,7 +38,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` --color: #dadde1; --header-color: #dadde1; --lock-shadow: rgb(37 37 37 / 0.5) 0 0 6px 2px; - --border-color: #3c4045; + --border-color: transparent; } `; diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index cd85523af..4ae2f5e33 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -65,7 +65,11 @@ } .cursorTableBox { - display: block !important; + // display: block !important; + // height: 100%; + display: flex !important; + flex-direction: column; + justify-content: space-between; } From 9d662cc11ba15a2e2ef7740b738f2fe46d5f6e2c Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 16:48:29 +0800 Subject: [PATCH 0138/1069] fix: fix aichart enter issues --- chat2db-client/src/components/Console/ChatInput/index.tsx | 4 ++++ chat2db-client/src/components/SearchResult/TableBox.less | 2 +- chat2db-client/src/components/SearchResult/TableBox.tsx | 3 +++ chat2db-client/src/components/SearchResult/index.less | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 142a0e3bb..53f4ad249 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -20,6 +20,10 @@ function ChatInput(props: IProps) { if (!e.target.value) { return; } + if (e.nativeEvent.isComposing && e.key === 'Enter') { + e.preventDefault(); + return; + } props.onPressEnter && props.onPressEnter(e.target.value); }; diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index 4e6e271fe..1aeb6c9b9 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -8,7 +8,7 @@ bottom: 0; display: none; overflow: auto; - background-color: var(--color-bg-100); + background-color: var(--color-bg-elevated); } diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index df534c52b..e763c8bc1 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -159,6 +159,9 @@ export default function TableBox(props: ITableProps) { //
<> diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 4ae2f5e33..c6709b06c 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -70,6 +70,7 @@ display: flex !important; flex-direction: column; justify-content: space-between; + // background-color: var(--bgcolor); } From 753b4fa1851af40061752336fae6bc443f9771c8 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 17:01:31 +0800 Subject: [PATCH 0139/1069] fix: fix dashboard has not tablelist --- .vscode/settings.json | 1 + chat2db-client/src/models/workspace.ts | 14 +++++----- .../pages/main/dashboard/chart-item/index.tsx | 7 +++-- .../src/pages/main/dashboard/index.tsx | 28 +++++++++++-------- chat2db-client/src/service/ai.ts | 4 +++ 5 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 chat2db-client/src/service/ai.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 8a7400aef..3e3aeb54f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,6 +4,7 @@ "antd", "asar", "cascader", + "datasource", "Dserver", "Dspring", "echart", diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index e34e15639..3548eb275 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -14,7 +14,7 @@ export type ICurWorkspaceParams = { schemaName?: string; }; -export interface IState { +export interface IWorkspaceModelState { // 当前连接下的及联databaseAndSchema数据 databaseAndSchema: MetaSchemaVO | undefined; // 当前工作区所需的参数 @@ -28,14 +28,14 @@ export interface IState { export interface IWorkspaceModelType { namespace: 'workspace'; - state: IState; + state: IWorkspaceModelState; reducers: { - setDatabaseAndSchema: Reducer; - setCurWorkspaceParams: Reducer; + setDatabaseAndSchema: Reducer; + setCurWorkspaceParams: Reducer; setDoubleClickTreeNodeData: Reducer; //TS TODO: - setConsoleList: Reducer; - setOpenConsoleList: Reducer; - setCurTableList: Reducer; + setConsoleList: Reducer; + setOpenConsoleList: Reducer; + setCurTableList: Reducer; }; effects: { fetchDatabaseAndSchema: Effect; 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 335a12627..fb83d591e 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 } from '@/typings'; +import { IChartItem, IChartType, IConnectionDetails, ITreeNode } from '@/typings'; import React, { useEffect, useRef, useState } from 'react'; import styles from './index.less'; import addImage from '@/assets/img/add.svg'; @@ -54,6 +54,7 @@ interface IChartItemProps { isEditing?: boolean; canAddRowItem: boolean; connectionList: IConnectionDetails[]; + tableList: ITreeNode[]; onDelete?: (id: number) => void; addChartTop?: () => void; addChartBottom?: () => void; @@ -62,7 +63,7 @@ interface IChartItemProps { } function ChartItem(props: IChartItemProps) { - const { connectionList } = props; + const { connectionList, tableList } = props; const [cascaderOption, setCascaderOption] = useState([]); const [curConnection, setCurConnection] = useState(); const [chartData, setChartData] = useState({}); @@ -315,7 +316,6 @@ function ChartItem(props: IChartItemProps) { hasSaveBtn={false} value={chartData?.ddl} onExecuteSQL={(result: any, sql: string) => { - console.log('onExecuteSQL', result); let sqlData; if (result && result[0]) { sqlData = handleSQLResult2ChartData(result[0]); @@ -333,6 +333,7 @@ function ChartItem(props: IChartItemProps) { ? EditorThemeType.DashboardLightTheme : EditorThemeType.DashboardBlackTheme, }} + tables={tableList || []} /> ([]); const [curDashboard, setCurDashboard] = useState(); const [openAddDashboard, setOpenAddDashboard] = useState(false); @@ -217,6 +217,7 @@ function Chart(props: IProps) { addChartRight={() => onAddChart('right', rowIndex, colIndex)} onDelete={(id: number) => onDeleteChart(id, rowIndex, colIndex)} connectionList={connectionList || []} + tableList={curTableList || []} /> ))} @@ -288,6 +289,9 @@ function Chart(props: IProps) { ); } -export default connect(({ connection }: { connection: IConnectionModelState }) => ({ - connectionList: connection.connectionList, -}))(Chart); +export default connect( + ({ connection, workspace }: { connection: IConnectionModelState; workspace: IWorkspaceModelState }) => ({ + connectionList: connection.connectionList, + curTableList: workspace.curTableList, + }), +)(Chart); diff --git a/chat2db-client/src/service/ai.ts b/chat2db-client/src/service/ai.ts new file mode 100644 index 000000000..f196e79a6 --- /dev/null +++ b/chat2db-client/src/service/ai.ts @@ -0,0 +1,4 @@ +import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; +import createRequest from './base'; + +const get = createRequest<{ key: string }, void>('/client/remaininguses/'); From 04d397f2d3bd4d3f3a1faa3e7b6db140e6f3462d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 17:22:07 +0800 Subject: [PATCH 0140/1069] =?UTF-8?q?=E6=8A=A5=E8=A1=A8=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Console/MonacoEditor/index.tsx | 6 +- .../src/components/Console/index.tsx | 14 ++--- .../src/components/SearchResult/TableBox.less | 10 +++- .../src/components/SearchResult/TableBox.tsx | 21 ++++--- .../src/components/SearchResult/index.less | 7 +-- .../src/components/SearchResult/index.tsx | 60 ++++++++++++------- chat2db-client/src/components/Tabs/index.less | 15 +---- chat2db-client/src/components/Tabs/index.tsx | 1 + chat2db-client/src/i18n/en-us/common.ts | 3 + chat2db-client/src/i18n/zh-cn/common.ts | 3 + .../pages/main/dashboard/chart-item/index.tsx | 8 +-- .../Tree/TreeNodeRightClick/index.tsx | 2 +- .../components/WorkspaceRight/index.less | 5 ++ .../components/WorkspaceRight/index.tsx | 1 + .../components/WorkspaceRightItem/index.tsx | 5 +- 15 files changed, 95 insertions(+), 66 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 78cf253d6..33d891ba6 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -142,8 +142,10 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { setValue, })); - useUpdateEffect(() => { - appendMonacoValue(editorRef.current, appendValue?.text, appendValue?.range); + useEffect(() => { + if (appendValue) { + appendMonacoValue(editorRef.current, appendValue?.text, appendValue?.range); + } }, [appendValue]) const setValue = (text: any, range?: IRangeType) => { diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 7e735ca5a..a9e62fb94 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -59,7 +59,7 @@ interface IProps { }; editorOptions?: IEditorOptions; // onSQLContentChange: (v: string) => void; - onExecuteSQL: (result: any, sql?: string) => void; + onExecuteSQL: (result: any, sql: string, createHistoryParams) => void; onConsoleSave: () => void; } @@ -165,15 +165,12 @@ function Console(props: IProps) { sql: sqlContent, ...executeParams, }; - // props.onExecuteSQL?.(undefined); sqlServer.executeSql(p).then((res) => { - props.onExecuteSQL?.(res, sqlContent!); - // console.log(res) - let p: any = { + let createHistoryParams: any = { ...executeParams, ddl: sqlContent, }; - historyServer.createHistory(p); + props.onExecuteSQL?.(res, sqlContent!, createHistoryParams); }); }; @@ -182,9 +179,10 @@ function Console(props: IProps) { let p: any = { id: executeParams.consoleId, status: ConsoleStatus.RELEASE, + ddl: value }; historyServer.updateSavedConsole(p).then((res) => { - message.success('保存成功'); + message.success(i18n('common.tips.saveSuccessfully')); props.onConsoleSave && props.onConsoleSave(); }); }; @@ -234,7 +232,7 @@ function Console(props: IProps) { onSave={saveConsole} onExecute={executeSQL} options={props.editorOptions} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index cc534412e..4a1f09009 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -15,7 +15,6 @@ display: block; } - .statusBar { position: sticky; bottom: 0; @@ -68,8 +67,13 @@ i:hover { color: var(--custom-primary-color); - } } -.table {} \ No newline at end of file +.monacoEditor { + height: 400px; + margin: -15px; +} + +.table { +} diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index f556cab6b..3915015ae 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -13,11 +13,11 @@ import { useTheme } from '@/hooks/useTheme'; import styled from 'styled-components'; import styles from './TableBox.less'; import { ThemeType } from '@/constants'; +import i18n from '@/i18n'; interface ITableProps { className?: string; data: IManageResultData; - key: number; } interface IViewTableCellData { @@ -42,7 +42,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` `; export default function TableBox(props: ITableProps) { - const { className, data, key } = props; + const { className, data } = props; const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); @@ -55,7 +55,7 @@ export default function TableBox(props: ITableProps) { function copyTableCell(data: IViewTableCellData) { navigator.clipboard.writeText(data?.value || viewTableCellData?.value); - message.success('复制成功'); + message.success(i18n('common.button.copySuccessfully')); } function handleCancel() { @@ -73,7 +73,6 @@ export default function TableBox(props: ITableProps) { // type: item.dataType, // sorter: (a: any, b: any) => a[item.name] - b[item.name], render: (value: any, row: any, rowIndex: number) => { - console.log('rowIndex', rowIndex); return (
{value}
@@ -96,7 +95,6 @@ export default function TableBox(props: ITableProps) { const rowData: any = {}; item.map((i: string | null, index: number) => { const { dataType: type } = headerList[index] || {}; - // console.log('headerList[rowIndex]', headerList[rowIndex]); if (type === TableDataType.DATETIME && i) { rowData[columns[index].name] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); } else if (i === null) { @@ -145,18 +143,23 @@ export default function TableBox(props: ITableProps) { maskClosable={false} footer={ <> - {/* { + { - } */} + } } >
{ {`${i18n('common.text.executionResult')}-${index + 1}`} ), - value: index, + value: item.uuid!, }; }); }; export default memo(function SearchResult({ className, manageResultDataList = [] }) { const [isUnfold, setIsUnfold] = useState(true); - const [currentTab, setCurrentTab] = useState(0); + const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState([]); const [tabs, setTabs] = useState([]); useEffect(() => { - setResultDataList(manageResultDataList); - setTabs(handleTabs(manageResultDataList)); + if (!manageResultDataList.length) { + return + } + const newManageResultDataList = manageResultDataList.map(t => { + return { + ...t, + uuid: uuidv4() + } + }) + setCurrentTab(newManageResultDataList[0].uuid) + setResultDataList(newManageResultDataList); + setTabs(handleTabs(newManageResultDataList)); }, [manageResultDataList]); - function onChange(index: string | number) { - setCurrentTab(index); + function onChange(uuid: string | number) { + setCurrentTab(uuid); } const renderStatus = (text: string) => { @@ -67,40 +77,50 @@ export default memo(function SearchResult({ className, manageResultDataL function onEdit(type: 'add' | 'remove', value?: number | string) { if (type === 'remove') { - if (currentTab === value) { - setCurrentTab(0); - } - const remainTabs = tabs.filter((t) => t.value !== value); - setTabs(remainTabs); - const dataList = resultDataList.filter((t) => t.uuid !== value); - // resultDataList.splice(value as number, 1); setResultDataList(dataList); + if (currentTab === value) { + console.log(currentTab) + setCurrentTab(dataList[0].uuid); + console.log(dataList[0].uuid) + } } } + useEffect(() => { + console.log(currentTab) + }, [currentTab]) + + const renderEmpty = () => { return
暂无数据
; }; - const renderTable = () => { + const renderTable = useMemo(() => { + console.log('2', currentTab) if (!tabs || !tabs.length) { return renderEmpty(); } if (!resultDataList || !resultDataList.length) { return renderEmpty(); } - return (resultDataList || []).map((item, index: number) => { if (item.success) { return ( - + + + ); } else { - return ; + return + + ; } }); - }; + }, [currentTab]) return (
@@ -117,7 +137,7 @@ export default memo(function SearchResult({ className, manageResultDataL />
) : null} -
{renderTable()}
+
{renderTable}
); }); diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 0cbe4c327..bffcdea61 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -7,20 +7,11 @@ .tab-focus-line() { color: var(--color-primary); - &::after { - content: ''; - height: 1px; - position: absolute; - left: 0; - right: 0; - bottom: -1px; - background-color: var(--color-primary); - } + background-color: var(--color-bg-base); } .tab { display: flex; - padding: 18px 4px 0px 4px; } .tabList { @@ -71,10 +62,10 @@ align-items: center; padding: 0px 10px; line-height: 20px; - height: 24px; + height: 28px; width: 100px; - margin: 0px 2px; cursor: pointer; + border-right: 1px solid var(--color-border); &:hover { .tab-focus-line(); .text { diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 48ef080d3..af630358c 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -31,6 +31,7 @@ export default memo(function Tab(props) { useEffect(() => { setInternalActiveTab(activeTab); + console.log(activeTab) }, [activeTab]) useEffect(() => { diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 5c154774d..9dfaa918f 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -40,4 +40,7 @@ export default { 'common.text.optimizeSQL': 'Optimize SQL', 'common.text.conversionSQL': 'Conversion SQL', 'common.text.table': 'Table', + 'common.tips.saveSuccessfully': 'Save Successfully', + 'common.button.copy': 'Copy', + 'common.button.copySuccessfully': 'Copy Successfully', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index a752990fd..27e7a1351 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -40,5 +40,8 @@ export default { 'common.text.optimizeSQL': '优化SQL', 'common.text.conversionSQL': '转化SQL', 'common.text.table': '表', + 'common.tips.saveSuccessfully': '保存成功', + 'common.button.copy': '复制', + 'common.button.copySuccessfully': '复制成功', }; 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 335a12627..51085d36b 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -21,10 +21,10 @@ import { EditorThemeType, ThemeType } from '@/constants'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; - const mockData = headerList.reduce((acc, cur, index) => { + const mockData = headerList?.reduce((acc, cur, index) => { acc[cur.name] = { ...cur, - data: dataList.map((i: any) => i[index]), + data: dataList?.map((i: any) => i[index]), }; return acc; }, {}); @@ -194,7 +194,7 @@ function ChartItem(props: IChartItemProps) { }; await updateChart(params); notification.success({ - message: '保存成功', + message: i18n('common.tips.saveSuccessfully'), }); }; @@ -356,7 +356,7 @@ function ChartItem(props: IChartItemProps) { }} className={styles.dataSourceSelect} placeholder={i18n('dashboard.editor.cascader.placeholder')} - // style={{ width: '100%' }} + // style={{ width: '100%' }} />
diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 7c71eb78a..b2010b59e 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -67,8 +67,8 @@ function TreeNodeRightClick(props: IProps) { ...curWorkspaceParams, tableName: data.name } as any).then(res => { - setMonacoVerifyDialog(true); setMonacoDefaultValue(res); + setMonacoVerifyDialog(true); }) } } 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 cdff634d1..580687956 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -14,6 +14,11 @@ } } +.tabs { + padding: 18px 4px 0px 4px; + margin: 0px 4px; +} + .consoleBox { position: absolute; top: 48px; 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 774d69759..0d3d0b6f9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -173,6 +173,7 @@ const WorkspaceRight = memo(function (props) {
(function (props) { executeParams={{ ...data }} hasAiChat={true} hasAi2Lang={true} - onExecuteSQL={(res: any) => { + onExecuteSQL={(res: any, a: any, params: any) => { setResultData(res); setShowResult(true); + historyServer.createHistory(params); + }} onConsoleSave={() => { dispatch({ From c1058136f3e6fb74eab77d4248db28b878a40b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 19:07:16 +0800 Subject: [PATCH 0141/1069] =?UTF-8?q?feat:console=E8=A1=A8=E6=8F=90?= =?UTF-8?q?=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.ts | 1 - .../components/Console/MonacoEditor/index.tsx | 19 +++--- .../src/components/Console/index.tsx | 17 +++++- .../src/components/CreateConnection/index.tsx | 4 -- .../components/DraggableContainer/index.tsx | 1 - .../src/components/Iconfont/index.tsx | 8 +-- .../src/components/SearchResult/TableBox.tsx | 4 +- .../src/components/SearchResult/index.less | 7 +++ .../src/components/SearchResult/index.tsx | 12 +--- chat2db-client/src/components/Tabs/index.tsx | 5 +- chat2db-client/src/i18n/en-us/common.ts | 5 ++ chat2db-client/src/i18n/zh-cn/common.ts | 5 ++ .../workspace/components/Tree/treeConfig.tsx | 1 - .../components/WorkspaceLeft/index.less | 13 ++++ .../components/WorkspaceLeft/index.tsx | 59 ++++++++++++++++++- .../components/WorkspaceRightItem/index.less | 2 +- .../components/WorkspaceRightItem/index.tsx | 1 - 17 files changed, 124 insertions(+), 40 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 373d3c6bd..c8496e029 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -2,7 +2,6 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; import { getLang } from '@/utils/localStorage'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -console.log(process.env.UMI_ENV); const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 33d891ba6..0ef2a9da9 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -4,6 +4,7 @@ import { useTheme } from '@/hooks'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; const { keywords: SQLKeys } = language; +const { keywords } = language; import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import styles from './index.less'; import { useUpdateEffect } from '@/hooks' @@ -29,12 +30,18 @@ interface IProps { onSave?: (value: string) => void; // 快捷键保存的回调 defaultValue?: string; onExecute?: (value: string) => void; // 快捷键执行的回调 + tables: any[] } export interface IExportRefFunction { getCurrentSelectContent: () => string; getAllContent: () => string; setValue: (text: any, range?: IRangeType) => void; + handleRegisterTigger: (hintData: IHintData) => void; +} + +export interface IHintData { + [keys: string]: string[]; } function MonacoEditor(props: IProps, ref: ForwardedRef) { @@ -95,8 +102,6 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }, }); - handleRegisterTigger(); - createAction(editorIns); return () => { if (props.needDestroy) editorRef.current && editorRef.current.dispose(); @@ -137,6 +142,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // }, [props.onChange]); useImperativeHandle(ref, () => ({ + handleRegisterTigger, getCurrentSelectContent, getAllContent, setValue, @@ -173,10 +179,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { return value || ''; }; - const handleRegisterTigger = () => { - // SQL关键词、 数据库、 表 、列 - - const hintData = { table1: 'table1', table2: 'table2' }; + const handleRegisterTigger = (hintData: IHintData) => { // 获取 SQL 语法提示 const getSQLSuggest = () => { return SQLKeys.map((key: any) => ({ @@ -210,7 +213,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // detail: '
', // })); // }; - monaco.languages.registerCompletionItemProvider('sql', { + const editorHintExamples = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', ...SQLKeys], provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => { let suggestions: any = []; @@ -238,6 +241,8 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }; }, }); + + return editorHintExamples; }; const createAction = (editor: IEditorIns) => { diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index a9e62fb94..5893af55b 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -48,7 +48,6 @@ interface IProps { /** 是否有 */ hasSaveBtn?: boolean; value?: string; - tables: ITreeNode[]; executeParams: { databaseName?: string; dataSourceId?: number; @@ -57,10 +56,12 @@ interface IProps { schemaName?: string; consoleName?: string; }; + tableList?: ITreeNode[]; editorOptions?: IEditorOptions; // onSQLContentChange: (v: string) => void; onExecuteSQL: (result: any, sql: string, createHistoryParams) => void; onConsoleSave: () => void; + tables: any[] } function Console(props: IProps) { @@ -73,6 +74,7 @@ function Console(props: IProps) { const [aiContent, setAiContent] = useState(''); const [isAiDrawerOpen, setIsAiDrawerOpen] = useState(false); const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); + const monacoHint = useRef(); useEffect(() => { if (appendValue) { @@ -80,6 +82,17 @@ function Console(props: IProps) { } }, [appendValue]); + + useEffect(() => { + monacoHint.current?.dispose(); + const myEditorHintData: any = {}; + console.log(props.tables) + props.tables?.map((item: any) => { + myEditorHintData[item.name] = []; + }); + monacoHint.current = editorRef?.current?.handleRegisterTigger(myEditorHintData); + }, [props.tables]) + const tableListName = useMemo(() => { const tableList = (props.tables || []).map((t) => t.name); @@ -132,7 +145,6 @@ function Console(props: IProps) { chatResult.current += JSON.parse(message).content; } } catch (error) { - console.log('handleMessage', error); setIsLoading(false); } }; @@ -232,6 +244,7 @@ function Console(props: IProps) { onSave={saveConsole} onExecute={executeSQL} options={props.editorOptions} + tables={props.tables} // onChange={} /> {/* {modelConfig.content} */} diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 1ae6e47b9..cb2aa9a6e 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -366,10 +366,6 @@ function RenderForm(props: IRenderFormProps) { if (keyName === 'host' && !aliasChanged) { newData.alias = '@' + keyValue; } - // console.log({ - // ...dataObj, - // ...newData, - // }); form.setFieldsValue({ ...dataObj, ...newData, diff --git a/chat2db-client/src/components/DraggableContainer/index.tsx b/chat2db-client/src/components/DraggableContainer/index.tsx index c964743fd..403a8b84a 100644 --- a/chat2db-client/src/components/DraggableContainer/index.tsx +++ b/chat2db-client/src/components/DraggableContainer/index.tsx @@ -40,7 +40,6 @@ export default memo(function DraggableContainer({ DividerRef.current.onmousedown = (e) => { if (!volatileRef?.current) return; - console.log(volatileRef?.curren); e.preventDefault(); setDragging(true); diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index c16c28c19..f9847320f 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/a/font_3633546_uuxqox310h.woff2?t=1687943954391') format('woff2'), - url('//at.alicdn.com/t/a/font_3633546_uuxqox310h.woff?t=1687943954391') format('woff'), - url('//at.alicdn.com/t/a/font_3633546_uuxqox310h.ttf?t=1687943954391') format('truetype'); + src: url('//at.alicdn.com/t/a/font_3633546_cvx0po2baqd.woff2?t=1688205862419') format('woff2'), + url('//at.alicdn.com/t/a/font_3633546_cvx0po2baqd.woff?t=1688205862419') format('woff'), + url('//at.alicdn.com/t/a/font_3633546_cvx0po2baqd.ttf?t=1688205862419') format('truetype'); } ` let style = document.createElement("style"); @@ -24,7 +24,7 @@ export default class Iconfont extends PureComponent< { code: string; } & React.DetailedHTMLProps, HTMLElement> - > { +> { render() { return ( diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index f8fbce6d5..f929ecbf6 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -164,10 +164,10 @@ export default function TableBox(props: ITableProps) { className={classnames({ dark: isDarkTheme }, props.className, styles.table)} {...pipeline.getProps()} /> -
{`结果:${description}. 耗时:${duration}ms`}
+
{`${i18n('common.text.result')}:${description}. ${i18n('common.text.timeConsuming')}:${duration}ms`}
) : ( - + )} (function SearchResult({ className, manageResultDataL const dataList = resultDataList.filter((t) => t.uuid !== value); setResultDataList(dataList); if (currentTab === value) { - console.log(currentTab) - setCurrentTab(dataList[0].uuid); - console.log(dataList[0].uuid) + setCurrentTab(dataList[0]?.uuid); } } } - useEffect(() => { - console.log(currentTab) - }, [currentTab]) - - const renderEmpty = () => { - return
暂无数据
; + return
{i18n('common.text.noData')}
; }; const renderTable = useMemo(() => { - console.log('2', currentTab) if (!tabs || !tabs.length) { return renderEmpty(); } diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index af630358c..970abcfb3 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -31,7 +31,6 @@ export default memo(function Tab(props) { useEffect(() => { setInternalActiveTab(activeTab); - console.log(activeTab) }, [activeTab]) useEffect(() => { @@ -69,9 +68,9 @@ export default memo(function Tab(props) { { [styles.activeTab]: t.value === internalActiveTab && type !== 'line' }, ) } - onClick={changeTab.bind(null, t)} + > -
+
{t.label}
diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 9dfaa918f..c0af1e906 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -43,4 +43,9 @@ export default { 'common.tips.saveSuccessfully': 'Save Successfully', 'common.button.copy': 'Copy', 'common.button.copySuccessfully': 'Copy Successfully', + 'common.button.createConsole': 'Create Console', + 'common.text.successfulExecution': 'Successful Execution', + 'common.text.result': 'Result', + 'common.text.timeConsuming': 'Time Consuming', + 'common.text.noData': 'No Data', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 27e7a1351..2c6041d09 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -43,5 +43,10 @@ export default { 'common.tips.saveSuccessfully': '保存成功', 'common.button.copy': '复制', 'common.button.copySuccessfully': '复制成功', + 'common.button.createConsole': '新建控制台', + 'common.text.successfulExecution': '执行成功', + 'common.text.result': '结果', + 'common.text.timeConsuming': '耗时', + 'common.text.noData': '暂无数据', }; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index ee73dca49..9baeda6b2 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -259,7 +259,6 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { return new Promise((r: (value: ITreeNode[]) => void, j) => { mysqlServer.getKeyList(params).then(res => { - console.log(res) const tableList: ITreeNode[] = res?.map(item => { return { name: item.name, 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 d869d2734..1feffa3a1 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -151,3 +151,16 @@ height: 26px; } } + +.createButtonBox { + padding: 10px 0px 10px; +} +.createButton { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + i { + margin-right: 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 f8f2ec7c1..fe5c2b35a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -2,7 +2,7 @@ import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'r import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Cascader, Divider, Input, Dropdown } from 'antd'; +import { Cascader, Divider, Input, Dropdown, Button } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; @@ -10,12 +10,15 @@ import { IWorkspaceModelType } from '@/models/workspace'; import historyServer from '@/service/history'; import Tree from '../Tree'; import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; -import { IConsole, ITreeNode } from '@/typings'; +import { IConsole, ITreeNode, ICreateConsole } from '@/typings'; import styles from './index.less'; import { approximateTreeNode, approximateList } from '@/utils'; +import historyService from '@/service/history'; interface IProps { className?: string; + workspaceModel: IWorkspaceModelType['state'], + dispatch: any; } const dvaModel = connect( @@ -26,7 +29,51 @@ const dvaModel = connect( ); const WorkspaceLeft = memo(function (props) { - const { className } = props; + const { className, workspaceModel, dispatch } = props; + const { curWorkspaceParams } = workspaceModel; + + + function getConsoleList() { + let p: any = { + pageNo: 1, + pageSize: 999, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + ...curWorkspaceParams, + }; + + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: p, + callback: (res: any) => { + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: res.data, + }) + } + }) + } + + const addConsole = (params?: ICreateConsole) => { + const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams + let p = { + name: `new console`, + ddl: '', + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName: schemaName!, + type: databaseType, + status: ConsoleStatus.DRAFT, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + } + historyService.saveConsole(params || p).then(res => { + getConsoleList(); + }) + } + + function createConsole() { + addConsole() + } + return (
@@ -35,6 +82,12 @@ const WorkspaceLeft = memo(function (props) { +
+ +
); }); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less index b76828b8e..893cc60f1 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less @@ -9,7 +9,7 @@ } .boxRightConsole { - height: 50vh; + height: 56vh; overflow: hidden; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 0a7eda887..1cb999b83 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -85,7 +85,6 @@ const WorkspaceRightItem = memo(function (props) { tables={curTableList || []} />
-
{}
From 20bdb1db40aef4d82b1d27c23be0be917052268c Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 19:51:30 +0800 Subject: [PATCH 0142/1069] feat: improve setting --- .vscode/settings.json | 5 + .../src/blocks/Setting/About/index.less | 31 ++ .../src/blocks/Setting/About/index.tsx | 27 + .../src/blocks/Setting/AiSetting/index.less | 28 + .../src/blocks/Setting/AiSetting/index.tsx | 180 ++++++ .../src/blocks/Setting/BaseSetting/index.less | 81 +++ .../src/blocks/Setting/BaseSetting/index.tsx | 138 +++++ .../blocks/Setting/ProxySetting/index.less | 19 + .../src/blocks/Setting/ProxySetting/index.tsx | 49 ++ chat2db-client/src/blocks/Setting/index.less | 117 +--- chat2db-client/src/blocks/Setting/index.tsx | 521 +++--------------- .../components/Console/ChatInput/index.less | 8 +- .../components/Console/ChatInput/index.tsx | 19 +- .../src/components/Console/index.tsx | 7 +- chat2db-client/src/constants/social.ts | 1 + chat2db-client/src/models/ai.ts | 59 ++ chat2db-client/src/models/connection.ts | 5 +- .../pages/main/dashboard/chart-item/index.tsx | 7 +- .../src/pages/main/dashboard/index.tsx | 21 +- chat2db-client/src/service/ai.ts | 5 +- chat2db-client/src/service/config.ts | 34 +- chat2db-client/src/typings/ai.ts | 13 + 22 files changed, 789 insertions(+), 586 deletions(-) create mode 100644 chat2db-client/src/blocks/Setting/About/index.less create mode 100644 chat2db-client/src/blocks/Setting/About/index.tsx create mode 100644 chat2db-client/src/blocks/Setting/AiSetting/index.less create mode 100644 chat2db-client/src/blocks/Setting/AiSetting/index.tsx create mode 100644 chat2db-client/src/blocks/Setting/BaseSetting/index.less create mode 100644 chat2db-client/src/blocks/Setting/BaseSetting/index.tsx create mode 100644 chat2db-client/src/blocks/Setting/ProxySetting/index.less create mode 100644 chat2db-client/src/blocks/Setting/ProxySetting/index.tsx create mode 100644 chat2db-client/src/constants/social.ts create mode 100644 chat2db-client/src/models/ai.ts create mode 100644 chat2db-client/src/typings/ai.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 3e3aeb54f..6ca277f4a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,6 +3,7 @@ "ahooks", "antd", "asar", + "AZUREAI", "cascader", "datasource", "Dserver", @@ -13,9 +14,13 @@ "iconfont", "monaco", "nsis", + "OPENAI", "pgsql", + "remaininguses", + "RESTAI", "Sercurity", "sortablejs", + "wechat", "wireframe" ] } diff --git a/chat2db-client/src/blocks/Setting/About/index.less b/chat2db-client/src/blocks/Setting/About/index.less new file mode 100644 index 000000000..df6d7193e --- /dev/null +++ b/chat2db-client/src/blocks/Setting/About/index.less @@ -0,0 +1,31 @@ +.aboutUs { + display: flex; + flex-direction: column; + align-items: center; + + .brief { + display: flex; + flex-direction: column; + align-items: center; + + .appName { + font-size: 20px; + } + + .env, + .version { + margin: 4px 0px; + color: var(--color-text-45); + } + + .log { + margin-top: 4px; + color: var(--custom-primary-color); + cursor: pointer; + + &:hover { + text-decoration: underline; + } + } + } +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx new file mode 100644 index 000000000..a7db72382 --- /dev/null +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -0,0 +1,27 @@ +import BrandLogo from '@/components/BrandLogo'; +import { APP_NAME, GITHUB_URL } from '@/constants/appConfig'; +import i18n from '@/i18n'; +import React from 'react'; +import styles from './index.less'; + +// 关于我们 +export default function AboutUs() { + return ( +
+ +
+
{APP_NAME}
+
+ {i18n('setting.text.currentEnv')}:{__ENV} +
+
+ {i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build + {__BUILD_TIME__} +
+ + {i18n('setting.text.viewingUpdateLogs')} + +
+
+ ); +} diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.less b/chat2db-client/src/blocks/Setting/AiSetting/index.less new file mode 100644 index 000000000..d28368c05 --- /dev/null +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.less @@ -0,0 +1,28 @@ +.aiSqlSource { + display: flex; + margin-bottom: 20px; +} + +.aiSqlSourceTitle { + margin-right: 20px; +} + +.title { + font-size: 14px; + margin-bottom: 10px; + + i { + margin-left: 10px; + color: var(--color-primary); + } +} + +.content { + margin-bottom: 15px; +} + +.bottomButton { + display: flex; + justify-content: flex-end; + margin-top: 20px; +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx new file mode 100644 index 000000000..e0aa4e4d9 --- /dev/null +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -0,0 +1,180 @@ +import React, { useEffect, useState } from 'react'; +import configService, { IChatGPTConfig } from '@/service/config'; +import { AiSqlSourceType } from '@/typings/ai'; +import { Button, Input, message, Radio } from 'antd'; +import i18n from '@/i18n'; +import classnames from 'classnames'; +import { IAIState } from '@/models/ai'; +import styles from './index.less'; + +interface IProps { + handleUpdateAiConfig: (payload: IAIState['keyAndAiType']) => void; + chatGPTConfig?: IChatGPTConfig; +} +// openAI 的设置项 +export default function SettingAI(props: IProps) { + const { handleUpdateAiConfig } = props; + const [chatGPTConfig, setChatGPTConfig] = useState(); + + useEffect(() => { + setChatGPTConfig(props.chatGPTConfig); + }, [props.chatGPTConfig]); + + function changeChatGPTApiKey() { + const newChatGPTConfig = { ...chatGPTConfig }; + if (newChatGPTConfig.apiHost && !newChatGPTConfig.apiHost?.endsWith('/')) { + newChatGPTConfig.apiHost = newChatGPTConfig.apiHost + '/'; + } + configService.setChatGptSystemConfig(newChatGPTConfig).then((res) => { + message.success(i18n('common.text.submittedSuccessfully')); + }); + + handleUpdateAiConfig && + handleUpdateAiConfig({ + key: newChatGPTConfig.apiKey, + aiType: newChatGPTConfig.aiSqlSource, + }); + } + + return ( + <> +
+
{i18n('setting.title.aiSource')}:
+ { + setChatGPTConfig({ ...chatGPTConfig, aiSqlSource: e.target.value }); + }} + value={chatGPTConfig.aiSqlSource} + > + Open AI + Azure AI + {i18n('setting.tab.custom')} + +
+ {chatGPTConfig.aiSqlSource === AiSqlSourceType.OPENAI && ( +
+
Api Key
+
+ { + setChatGPTConfig({ ...chatGPTConfig, apiKey: e.target.value }); + }} + /> +
+
Api Host
+
+ { + setChatGPTConfig({ ...chatGPTConfig, apiHost: e.target.value }); + }} + /> +
+
HTTP Proxy Host
+
+ { + setChatGPTConfig({ + ...chatGPTConfig, + httpProxyHost: e.target.value, + }); + }} + /> +
+
HTTP Proxy Prot
+
+ { + setChatGPTConfig({ + ...chatGPTConfig, + httpProxyPort: e.target.value, + }); + }} + /> +
+
+ )} + {chatGPTConfig.aiSqlSource === AiSqlSourceType.AZUREAI && ( +
+
Api Key
+
+ { + setChatGPTConfig({ ...chatGPTConfig, azureApiKey: e.target.value }); + }} + /> +
+
Endpoint
+
+ { + setChatGPTConfig({ ...chatGPTConfig, azureEndpoint: e.target.value }); + }} + /> +
+
DeploymentId
+
+ { + setChatGPTConfig({ + ...chatGPTConfig, + azureDeploymentId: e.target.value, + }); + }} + /> +
+
+ )} + {chatGPTConfig.aiSqlSource === AiSqlSourceType.RESTAI && ( +
+
{i18n('setting.label.customAiUrl')}
+
+ { + setChatGPTConfig({ + ...chatGPTConfig, + restAiUrl: e.target.value, + }); + }} + /> +
+
{i18n('setting.label.isStreamOutput')}
+
+ { + setChatGPTConfig({ + ...chatGPTConfig, + restAiStream: e.target.value, + }); + }} + value={chatGPTConfig.restAiStream} + > + {i18n('common.text.is')} + {i18n('common.text.no')} + +
+
+ )} +
+ +
+ + ); +} diff --git a/chat2db-client/src/blocks/Setting/BaseSetting/index.less b/chat2db-client/src/blocks/Setting/BaseSetting/index.less new file mode 100644 index 000000000..e1aa2455f --- /dev/null +++ b/chat2db-client/src/blocks/Setting/BaseSetting/index.less @@ -0,0 +1,81 @@ +.title { + font-size: 14px; + margin-bottom: 10px; + + i { + margin-left: 10px; + color: var(--color-primary); + } +} + + +.backgroundList { + display: flex; + margin: 0px 0px 20px -7px; + + .themeItemBox { + display: flex; + flex-direction: column; + align-items: center; + margin: 0px 7px; + + .themeImg { + margin-bottom: 6px; + width: 64px; + height: 48px; + overflow: hidden; + border-radius: 2px; + background-size: cover; + background-repeat: no-repeat; + cursor: pointer; + } + + .themeName { + text-align: center; + font-size: 12px; + } + + .current { + box-sizing: border-box; + border: 2px solid var(--color-primary); + } + } + + filter: brightness(var(--filter-brightness)); +} + +.langBox { + margin-bottom: 20px; +} + +.primaryColorList { + display: flex; + + .themeColorItem { + margin-right: 20px; + display: flex; + flex-direction: column; + align-items: center; + } + + .colorLump { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border: 2px solid transparent; + border-radius: 50%; + cursor: pointer; + border: 1px solid var(--color-bg-hover); + } + + i { + color: #fff; + } + + .colorName { + margin-top: 6px; + font-size: 12px; + } +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx b/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx new file mode 100644 index 000000000..ad6987221 --- /dev/null +++ b/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx @@ -0,0 +1,138 @@ +import { LangType, ThemeType } from '@/constants'; +import i18n, { currentLang } from '@/i18n'; +import React, { useState } from 'react'; +import classnames from 'classnames'; +import themeDarkImg from '@/assets/img/theme-dark.png'; +import themeLightImg from '@/assets/img/theme-light.png'; +import themeAutoImg from '@/assets/img/theme-auto.png'; +import { Radio, Select } from 'antd'; +import Iconfont from '@/components/Iconfont'; +import { setLang as setLangLocalStorage } from '@/utils/localStorage'; +import { useTheme } from '@/hooks/useTheme'; + +import styles from './index.less'; + +const { Option } = Select; + +const themeList = [ + { + code: ThemeType.Dark, + name: i18n('setting.text.dark'), + img: themeDarkImg, + }, + { + code: ThemeType.Light, + name: i18n('setting.text.light'), + img: themeLightImg, + }, + { + code: ThemeType.FollowOs, + name: i18n('setting.text.followOS'), + img: themeAutoImg, + }, + // { + // code: 'eyeshield', + // name: '护眼', + // img: 'https://img.alicdn.com/imgextra/i1/O1CN01KGCqY21uJpuFjEQW2_!!6000000006017-2-tps-181-135.png' + // }, +]; + +const colorList = [ + { + code: 'polar-blue', + name: i18n('setting.label.blue'), + color: '#1a90ff', + }, + { + code: 'polar-green', + name: i18n('setting.label.green'), + color: '#1d3712', + }, + { + code: 'golden-purple', + name: i18n('setting.label.violet'), + color: '#301c4d', + }, + // { + // code: 'sunset-orange', + // name: '日暮', + // color: "#593815" + // }, +]; + +// baseBody 基础设置 +export default function BaseSetting() { + const [lang, setLang] = useState(currentLang); + const [currentTheme, setCurrentTheme] = useState(localStorage.getItem('theme')); + const [currentPrimaryColor, setCurrentPrimaryColor] = useState(localStorage.getItem('primary-color')); + const [appTheme, setAppTheme] = useTheme(); + + const changePrimaryColor = (item: any) => { + const html = document.documentElement; + html.setAttribute('primary-color', item.code); + localStorage.setItem('primary-color', item.code); + setCurrentPrimaryColor(item.code); + setAppTheme({ + ...appTheme, + primaryColor: item.code, + }); + }; + + function changeLang(e: any) { + setLangLocalStorage(e.target.value); + location.reload(); + } + + function handleChangeTheme(theme: ThemeType) { + setAppTheme({ + ...appTheme, + backgroundColor: theme, + }); + setCurrentTheme(theme); + } + + return ( + <> +
{i18n('setting.title.backgroundColor')}
+
    + {themeList.map((t) => { + return ( +
    +
    +
    {t.name}
    +
    + ); + })} +
+
{i18n('setting.title.language')}
+
+ + 简体中文 + English + +
+
{i18n('setting.title.themeColor')}
+
    + {colorList.map((item) => { + return ( +
    +
    + {currentPrimaryColor == item.code && } +
    +
    {item.name}
    +
    + ); + })} +
+ + ); +} diff --git a/chat2db-client/src/blocks/Setting/ProxySetting/index.less b/chat2db-client/src/blocks/Setting/ProxySetting/index.less new file mode 100644 index 000000000..0490c9504 --- /dev/null +++ b/chat2db-client/src/blocks/Setting/ProxySetting/index.less @@ -0,0 +1,19 @@ +.title { + font-size: 14px; + margin-bottom: 10px; + + i { + margin-left: 10px; + color: var(--color-primary); + } +} + +.content { + margin-bottom: 15px; +} + +.bottomButton { + display: flex; + justify-content: flex-end; + margin-top: 20px; +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx b/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx new file mode 100644 index 000000000..13403b569 --- /dev/null +++ b/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx @@ -0,0 +1,49 @@ +import React, { useState } from 'react'; +import i18n from '@/i18n'; +import { Button, Input, message } from 'antd'; +import classnames from 'classnames'; +import styles from './index.less'; +// 代理设置 +export default function ProxyBody() { + const [apiPrefix, setApiPrefix] = useState(window._BaseURL); + + function updateApi(e: any) { + setApiPrefix(e.target.value); + } + + function affirmUpdateApi() { + if (!apiPrefix) { + return; + } + try { + const xhr = new XMLHttpRequest(); + xhr.withCredentials = true; + xhr.open('GET', `${apiPrefix}/api/system/get-version-a`); + xhr.onload = function () { + if (xhr.status === 200) { + localStorage.setItem('_BaseURL', apiPrefix); + location.reload(); + } else { + message.error(i18n('setting.message.urlTestError')); + } + }; + xhr.send(); + } catch { + message.error(i18n('setting.message.urlTestError')); + } + } + + return ( + <> +
{i18n('setting.label.serviceAddress')}
+
+ +
+
+ +
+ + ); +} diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index 4dadd756f..330064791 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -8,39 +8,9 @@ color: var(--custom-color-icon); } -.backgroundList { - display: flex; - margin: 0px 0px 20px -7px; - - .themeItemBox { - display: flex; - flex-direction: column; - align-items: center; - margin: 0px 7px; - .themeImg { - margin-bottom: 6px; - width: 64px; - height: 48px; - overflow: hidden; - border-radius: 2px; - background-size: cover; - background-repeat: no-repeat; - cursor: pointer; - } - .themeName { - text-align: center; - font-size: 12px; - } - .current { - box-sizing: border-box; - border: 2px solid var(--color-primary); - } - } - filter: brightness(var(--filter-brightness)); -} - .setText { color: var(--custom-primary-color); + &:hover { text-decoration: underline; } @@ -49,6 +19,7 @@ .title { font-size: 14px; margin-bottom: 10px; + i { margin-left: 10px; color: var(--color-primary); @@ -59,39 +30,6 @@ margin-bottom: 15px; } -.langBox { - margin-bottom: 20px; -} - -.primaryColorList { - display: flex; - .themeColorItem { - margin-right: 20px; - display: flex; - flex-direction: column; - align-items: center; - } - - .colorLump { - display: inline-flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - border: 2px solid transparent; - border-radius: 50%; - cursor: pointer; - border: 1px solid var(--color-bg-hover); - } - i { - color: #fff; - } - .colorName { - margin-top: 6px; - font-size: 12px; - } -} - .modalBox { display: flex; margin: -16px; @@ -106,6 +44,7 @@ padding: 20px; position: sticky; top: 0; + .menusTitle { font-size: 16px; padding-bottom: 10px; @@ -119,11 +58,13 @@ display: flex; justify-content: flex-start; align-items: center; + i { font-size: 16px; margin-right: 10px; } } + .activeMenu { background-color: var(--color-hover-bg); } @@ -138,50 +79,4 @@ font-size: 16px; padding-bottom: 20px; } -} - -.bottomButton { - display: flex; - justify-content: flex-end; - margin-top: 20px; -} - -.aboutUs { - display: flex; - flex-direction: column; - align-items: center; - .brief { - display: flex; - flex-direction: column; - align-items: center; - .appName { - font-size: 20px; - } - .env, - .version { - margin: 4px 0px; - color: var(--color-text-45); - } - .log { - margin-top: 4px; - color: var(--custom-primary-color); - cursor: pointer; - &:hover { - text-decoration: underline; - } - } - } -} - -.brandLogo { - margin-bottom: 15px; -} - -.aiSqlSource { - display: flex; - margin-bottom: 20px; -} - -.aiSqlSourceTitle { - margin-right: 20px; -} +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index d5e2d6715..6027c993b 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -1,106 +1,42 @@ -import React, { memo, useEffect, useState } from 'react'; -import styles from './index.less'; +import React, { useEffect, useMemo, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; -import { Modal, Radio, Input, message, Select, Button } from 'antd'; +import { Modal } from 'antd'; +import i18n from '@/i18n'; +import BaseSetting from './BaseSetting'; +import AISetting from './AiSetting'; +import ProxySetting from './ProxySetting'; +import About from './About'; +import { connect } from 'umi'; +import { IAIState } from '@/models/ai'; +import styles from './index.less'; import configService, { IChatGPTConfig } from '@/service/config'; -import BrandLogo from '@/components/BrandLogo'; -import themeDarkImg from '@/assets/img/theme-dark.png'; -import themeLightImg from '@/assets/img/theme-light.png'; -import themeAutoImg from '@/assets/img/theme-auto.png'; -import { getOsTheme } from '@/utils'; -import i18n, { currentLang } from '@/i18n'; -import { ThemeType, LangType, APP_NAME, GITHUB_URL } from '@/constants'; -import { useTheme } from '@/hooks'; -import { setLang as setLangLocalStorage } from '@/utils/localStorage' - -const { Option } = Select; - - +import { AiSqlSourceType } from '@/typings/ai'; interface IProps { className?: string; text?: string; + dispatch: Function; } -export enum AiSqlSourceType { - OPENAI = 'OPENAI', - AZUREAI = 'AZUREAI', - RESTAI = 'RESTAI', -} - -const colorList = [ - { - code: 'polar-blue', - name: i18n('setting.label.blue'), - color: '#1a90ff', - }, - { - code: 'polar-green', - name: i18n('setting.label.green'), - color: '#1d3712', - }, - { - code: 'golden-purple', - name: i18n('setting.label.violet'), - color: '#301c4d', - }, - // { - // code: 'sunset-orange', - // name: '日暮', - // color: "#593815" - // }, -]; - -const themeList = [ - { - code: ThemeType.Dark, - name: i18n('setting.text.dark'), - img: themeDarkImg, - }, - { - code: ThemeType.Light, - name: i18n('setting.text.light'), - img: themeLightImg, - }, - { - code: ThemeType.FollowOs, - name: i18n('setting.text.followOS'), - img: themeAutoImg, - }, - // { - // code: 'eyeshield', - // name: '护眼', - // img: 'https://img.alicdn.com/imgextra/i1/O1CN01KGCqY21uJpuFjEQW2_!!6000000006017-2-tps-181-135.png' - // }, -]; - -const menusList = [ - { - label: i18n('setting.nav.basic'), - icon: '\ue795', - body: , - }, - { - label: i18n('setting.nav.customAi'), - icon: '\ue646', - body: , - }, - { - label: i18n('setting.nav.proxy'), - icon: '\ue63f', - body: , - }, - { - label: i18n('setting.nav.aboutUs'), - icon: '\ue60c', - body: , - }, -]; - -export default memo(function Setting({ className, text }) { +function Setting(props: IProps) { + const { className, text, dispatch } = props; const [isModalVisible, setIsModalVisible] = useState(false); - const [currentMenu, setCurrentMenu] = useState(menusList[0]); - const [appTheme, setAppTheme] = useTheme(); + const [chatGPTConfig, setChatGPTConfig] = useState(); + + const [currentMenu, setCurrentMenu] = useState(0); + useEffect(() => { + configService.getChatGptSystemConfig().then((res: IChatGPTConfig) => { + handleUpdateAiConfig({ + key: res.apiKey, + aiType: res.aiSqlSource, + }); + setChatGPTConfig({ + ...res, + restAiStream: res.restAiStream || true, + aiSqlSource: res.aiSqlSource || AiSqlSourceType.OPENAI, + }); + }); + }, []); const showModal = () => { setIsModalVisible(true); @@ -118,6 +54,41 @@ export default memo(function Setting({ className, text }) { setCurrentMenu(t); } + const handleUpdateAiConfig = (payload: IAIState['keyAndAiType']) => { + dispatch({ + type: 'ai/setKeyAndAiType', + payload, + }); + dispatch({ + type: 'ai/fetchRemainingUse', + payload: { + key: payload.key, + }, + }); + }; + const menusList = [ + { + label: i18n('setting.nav.basic'), + icon: '\ue795', + body: , + }, + { + label: i18n('setting.nav.customAi'), + icon: '\ue646', + body: , + }, + { + label: i18n('setting.nav.proxy'), + icon: '\ue63f', + body: , + }, + { + label: i18n('setting.nav.aboutUs'), + icon: '\ue60c', + body: , + }, + ]; + return ( <>
@@ -137,16 +108,14 @@ export default memo(function Setting({ className, text }) { >
-
- {i18n('setting.title.setting')} -
+
{i18n('setting.title.setting')}
{menusList.map((t, index) => { return (
@@ -156,359 +125,15 @@ export default memo(function Setting({ className, text }) { })}
-
- {currentMenu.label} -
- {currentMenu.body} +
{menusList[currentMenu].label}
+ {menusList[currentMenu].body}
); -}); - -// openAI 的设置项 -export function SettingAI() { - const [chatgptConfig, setChatgptConfig] = useState({ - apiKey: '', - httpProxyHost: '', - httpProxyPort: '', - restAiUrl: '', - apiHost: '', - restAiStream: true, - aiSqlSource: '', - azureApiKey: '', - azureEndpoint: '', - azureDeploymentId: '' - }); - - useEffect(() => { - configService.getChatGptSystemConfig().then((res: any) => { - setChatgptConfig({ - ...res, - restAiStream: res.restAiStream || true, - aiSqlSource: res.aiSqlSource || AiSqlSourceType.OPENAI, - }); - }); - }, []); - - function changeChatgptApiKey() { - // if (!chatgptKey) { - // message.error('请输入ChatGPT-apiKey') - // return - // } - // apiHost的最后必须为/ - const newChatgptConfig = { ...chatgptConfig }; - if (newChatgptConfig.apiHost && !newChatgptConfig.apiHost?.endsWith('/')) { - newChatgptConfig.apiHost = newChatgptConfig.apiHost + '/'; - } - configService.setChatGptSystemConfig(newChatgptConfig).then((res) => { - message.success(i18n('common.text.submittedSuccessfully')); - }); - } - - return ( - <> -
-
- {i18n('setting.title.aiSource')}: -
- { - setChatgptConfig({ ...chatgptConfig, aiSqlSource: e.target.value }); - }} - value={chatgptConfig.aiSqlSource} - > - Open AI - Azure AI - - {i18n('setting.tab.custom')} - - -
- {chatgptConfig.aiSqlSource === AiSqlSourceType.OPENAI && ( -
-
Api Key
-
- { - setChatgptConfig({ ...chatgptConfig, apiKey: e.target.value }); - }} - /> -
-
Api Host
-
- { - setChatgptConfig({ ...chatgptConfig, apiHost: e.target.value }); - }} - /> -
-
HTTP Proxy Host
-
- { - setChatgptConfig({ - ...chatgptConfig, - httpProxyHost: e.target.value, - }); - }} - /> -
-
HTTP Proxy Prot
-
- { - setChatgptConfig({ - ...chatgptConfig, - httpProxyPort: e.target.value, - }); - }} - /> -
-
- )} - {chatgptConfig.aiSqlSource === AiSqlSourceType.AZUREAI && ( -
-
Api Key
-
- { - setChatgptConfig({ ...chatgptConfig, azureApiKey: e.target.value }); - }} - /> -
-
Endpoint
-
- { - setChatgptConfig({ ...chatgptConfig, azureEndpoint: e.target.value }); - }} - /> -
-
DeploymentId
-
- { - setChatgptConfig({ - ...chatgptConfig, - azureDeploymentId: e.target.value, - }); - }} - /> -
-
- )} - {chatgptConfig.aiSqlSource === AiSqlSourceType.RESTAI && ( -
-
- {i18n('setting.label.customAiUrl')} -
-
- { - setChatgptConfig({ - ...chatgptConfig, - restAiUrl: e.target.value, - }); - }} - /> -
-
- {i18n('setting.label.isStreamOutput')} -
-
- { - setChatgptConfig({ - ...chatgptConfig, - restAiStream: e.target.value, - }); - }} - value={chatgptConfig.restAiStream} - > - {i18n('common.text.is')} - {i18n('common.text.no')} - -
-
- )} -
- -
- - ); } -// baseBody 基础设置 -export function BaseBody() { - const [lang, setLang] = useState(currentLang); - const [currentTheme, setCurrentTheme] = useState( - localStorage.getItem('theme'), - ); - const [currentPrimaryColor, setCurrentPrimaryColor] = useState( - localStorage.getItem('primary-color'), - ); - const [appTheme, setAppTheme] = useTheme(); - - const changePrimaryColor = (item: any) => { - const html = document.documentElement; - html.setAttribute('primary-color', item.code); - localStorage.setItem('primary-color', item.code); - setCurrentPrimaryColor(item.code); - setAppTheme({ - ...appTheme, - primaryColor: item.code, - }); - }; - - function changeLang(e: any) { - setLangLocalStorage(e.target.value) - location.reload(); - } - - function handleChangeTheme(theme: ThemeType) { - setAppTheme({ - ...appTheme, - backgroundColor: theme, - }); - setCurrentTheme(theme); - } - - return ( - <> -
- {i18n('setting.title.backgroundColor')} -
-
    - {themeList.map((t) => { - return ( -
    -
    -
    {t.name}
    -
    - ); - })} -
-
{i18n('setting.title.language')}
-
- - 简体中文 - English - -
-
{i18n('setting.title.themeColor')}
-
    - {colorList.map((item) => { - return ( -
    -
    - {currentPrimaryColor == item.code && ( - - )} -
    -
    {item.name}
    -
    - ); - })} -
- - ); -} - -// 代理设置 -export function ProxyBody() { - const [apiPrefix, setApiPrefix] = useState(window._BaseURL); - - function updateApi(e: any) { - setApiPrefix(e.target.value); - } - - function affirmUpdateApi() { - if (!apiPrefix) { - return; - } - try { - const xhr = new XMLHttpRequest(); - xhr.withCredentials = true; - xhr.open('GET', `${apiPrefix}/api/system/get-version-a`); - xhr.onload = function () { - if (xhr.status === 200) { - localStorage.setItem('_BaseURL', apiPrefix); - location.reload(); - } else { - message.error(i18n('setting.message.urlTestError')); - } - }; - xhr.send(); - } catch { - message.error(i18n('setting.message.urlTestError')); - } - } - - return ( - <> -
{i18n('setting.label.serviceAddress')}
-
- -
-
- -
- - ); -} - -// 关于我们 -function AboutUs() { - return ( -
- -
-
{APP_NAME}
-
- {i18n('setting.text.currentEnv')}:{__ENV} -
-
- {i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build - {__BUILD_TIME__} -
- - {i18n('setting.text.viewingUpdateLogs')} - -
-
- ); -} +export default connect(({ ai }: { ai: IAIState }) => ({ + remainingUse: ai.remainingUse, +}))(Setting); diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index e4db2ef38..90a4bb596 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -48,15 +48,19 @@ background-color: var(--color-bg-elevated); padding: 4px 12px; + &:hover { + cursor: pointer; + } + } -.aiSelectedTable{ +.aiSelectedTable { display: flex; flex-direction: column; max-width: 140px; } -.aiSelectedTableTips{ +.aiSelectedTableTips { padding-bottom: 4px; border-bottom: 1px solid var(--color-border-secondary); margin-bottom: 4px diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 53f4ad249..8c6a37609 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -1,16 +1,19 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; -import { Checkbox, Dropdown, Input, Popover } from 'antd'; +import { Checkbox, Dropdown, Input, Modal, Popover } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; +import { IRemainingUse } from '@/typings/ai'; +import { WECHAT_MP_URL } from '@/constants/social'; interface IProps { value?: string; result?: string; tables?: string[]; selectedTables?: string[]; + remainingUse: IRemainingUse; onPressEnter: (value: string) => void; onSelectTables?: (tables: string[]) => void; } @@ -50,7 +53,7 @@ function ChatInput(props: IProps) { }; const renderSuffix = () => { - const remainCnt = 10; + const remainCnt = props.remainingUse || '-'; return (
{/*
⌘ + ↵
*/} @@ -59,7 +62,17 @@ function ChatInput(props: IProps) {
-
{i18n('chat.input.remain', remainCnt)}
+
{ + Modal.confirm({ + title: 'wechat', + content: , + }); + }} + > + {i18n('chat.input.remain', remainCnt)} +
); }; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 5893af55b..25f22080f 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { connect } from 'umi'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; -import { Button, Spin, message, notification, Drawer, Modal } from 'antd'; +import { Button, Spin, message, Drawer } from 'antd'; import ChatInput from './ChatInput'; import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; import { format } from 'sql-formatter'; @@ -14,6 +14,7 @@ import Iconfont from '../Iconfont'; import { ITreeNode } from '@/typings'; import styles from './index.less'; import i18n from '@/i18n'; +import { IRemainingUse } from '@/typings/ai'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -58,6 +59,7 @@ interface IProps { }; tableList?: ITreeNode[]; editorOptions?: IEditorOptions; + remainingUse: IRemainingUse; // onSQLContentChange: (v: string) => void; onExecuteSQL: (result: any, sql: string, createHistoryParams) => void; onConsoleSave: () => void; @@ -65,7 +67,7 @@ interface IProps { } function Console(props: IProps) { - const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value } = props; + const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value, remainingUse } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -226,6 +228,7 @@ function Console(props: IProps) { {hasAiChat && ( { diff --git a/chat2db-client/src/constants/social.ts b/chat2db-client/src/constants/social.ts new file mode 100644 index 000000000..e44168542 --- /dev/null +++ b/chat2db-client/src/constants/social.ts @@ -0,0 +1 @@ +export const WECHAT_MP_URL = 'https://oss-chat2db.alibaba.com/static/wechat.webp'; diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts new file mode 100644 index 000000000..85a3258c9 --- /dev/null +++ b/chat2db-client/src/models/ai.ts @@ -0,0 +1,59 @@ +import aiService from '@/service/ai'; +import { AiSqlSourceType, IRemainingUse } from '@/typings/ai'; +import { Effect, Reducer } from 'umi'; + +export interface IAIState { + keyAndAiType: { + key: string; + aiType: AiSqlSourceType; + }; + remainingUse?: IRemainingUse; +} + +export interface IAIModelType { + namespace: 'ai'; + state: IAIState; + reducers: { + setRemainUse: Reducer; + setKeyAndAiType: Reducer; + }; + effects: { + fetchRemainingUse: Effect; + }; +} + +const AIModel: IAIModelType = { + namespace: 'ai', + state: { + remainingUse: undefined, + keyAndAiType: { + key: '', + aiType: AiSqlSourceType.CHAT2DB, + }, + }, + reducers: { + setKeyAndAiType(state, { payload }) { + return { + ...state, + keyAndAiType: payload, + }; + }, + setRemainUse(state, { payload }) { + return { + ...state, + remainingUse: payload, + }; + }, + }, + effects: { + *fetchRemainingUse({ payload }, { put }) { + const res = (yield aiService.getRemainingUse(payload)) as IRemainingUse; + yield put({ + type: 'setRemainUse', + payload: res, + }); + }, + }, +}; + +export default AIModel; diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index c3ae21315..1302ebcda 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -25,7 +25,6 @@ export interface IConnectionModelType { }; } -// const ConnectionModel:ConnectionModelType = { const ConnectionModel: IConnectionModelType = { namespace: 'connection', state: { @@ -43,10 +42,9 @@ const ConnectionModel: IConnectionModelType = { // 设置当前选着的Connection setCurConnection(state, { payload }) { - localStorage.setItem('cur-connection',JSON.stringify(payload)) + localStorage.setItem('cur-connection', JSON.stringify(payload)); return { ...state, curConnection: payload }; }, - }, effects: { @@ -57,7 +55,6 @@ const ConnectionModel: IConnectionModelType = { payload: res.data, }); }, - }, }; 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 96a587add..2c1f33153 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -18,6 +18,7 @@ import { handleDatabaseAndSchema } from '@/utils/database'; import i18n from '@/i18n'; import { useTheme } from '@/hooks'; import { EditorThemeType, ThemeType } from '@/constants'; +import { IRemainingUse } from '@/typings/ai'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; @@ -55,6 +56,7 @@ interface IChartItemProps { canAddRowItem: boolean; connectionList: IConnectionDetails[]; tableList: ITreeNode[]; + remainingUse: IRemainingUse; onDelete?: (id: number) => void; addChartTop?: () => void; addChartBottom?: () => void; @@ -63,7 +65,7 @@ interface IChartItemProps { } function ChartItem(props: IChartItemProps) { - const { connectionList, tableList } = props; + const { connectionList, tableList, remainingUse } = props; const [cascaderOption, setCascaderOption] = useState([]); const [curConnection, setCurConnection] = useState(); const [chartData, setChartData] = useState({}); @@ -111,7 +113,7 @@ function ChartItem(props: IChartItemProps) { useEffect(() => { handleChartConfigChange(); - }, [chartData.sqlData]) + }, [chartData.sqlData]); const queryDatabaseAndSchemaList = async (dataSourceId: number) => { const res = await sqlService.getDatabaseSchemaList({ dataSourceId }); @@ -334,6 +336,7 @@ function ChartItem(props: IChartItemProps) { : EditorThemeType.DashboardBlackTheme, }} tables={tableList || []} + remainingUse={remainingUse} /> ([]); const [curDashboard, setCurDashboard] = useState(); const [openAddDashboard, setOpenAddDashboard] = useState(false); @@ -218,6 +221,7 @@ function Chart(props: IProps) { onDelete={(id: number) => onDeleteChart(id, rowIndex, colIndex)} connectionList={connectionList || []} tableList={curTableList || []} + remainingUse={remainingUse} />
))} @@ -228,6 +232,10 @@ function Chart(props: IProps) { ); }; + const updateAiRemainUse = () => { + + }; + return ( <> @@ -290,8 +298,17 @@ function Chart(props: IProps) { } export default connect( - ({ connection, workspace }: { connection: IConnectionModelState; workspace: IWorkspaceModelState }) => ({ + ({ + connection, + workspace, + ai, + }: { + connection: IConnectionModelState; + workspace: IWorkspaceModelState; + ai: IAIState; + }) => ({ connectionList: connection.connectionList, curTableList: workspace.curTableList, + remainingUse: ai.remainingUse, }), )(Chart); diff --git a/chat2db-client/src/service/ai.ts b/chat2db-client/src/service/ai.ts index f196e79a6..08ae1f443 100644 --- a/chat2db-client/src/service/ai.ts +++ b/chat2db-client/src/service/ai.ts @@ -1,4 +1,7 @@ import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; +import { IRemainingUse } from '@/typings/ai'; import createRequest from './base'; -const get = createRequest<{ key: string }, void>('/client/remaininguses/'); +const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/'); + +export default { getRemainingUse }; diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index 857f0df69..1ef40955d 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -1,26 +1,38 @@ -import createRequest from "./base"; -const getSystemConfig = createRequest<{code:string}, {code:string,content:string}>('/api/config/system_config/:code', { errorLevel: false }); -const setSystemConfig = createRequest<{ code: string, content: string }, void>('/api/config/system_config', { errorLevel: 'toast', method: 'post' }); +import { AiSqlSourceType } from '@/typings/ai'; +import createRequest from './base'; +const getSystemConfig = createRequest<{ code: string }, { code: string; content: string }>( + '/api/config/system_config/:code', + { errorLevel: false }, +); +const setSystemConfig = createRequest<{ code: string; content: string }, void>('/api/config/system_config', { + errorLevel: 'toast', + method: 'post', +}); export interface IChatGPTConfig { apiKey: string; httpProxyHost: string; httpProxyPort: string; - restAiUrl: string, - apiHost: string, - aiSqlSource: string; + restAiUrl: string; + apiHost: string; + aiSqlSource: AiSqlSourceType; restAiStream: boolean; azureEndpoint: string; azureApiKey: string; azureDeploymentId: string; } -const getChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { errorLevel: false }); +const getChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { + errorLevel: false, +}); -const setChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { errorLevel: 'toast', method: 'post' }); +const setChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { + errorLevel: 'toast', + method: 'post', +}); -export default { +export default { getSystemConfig, setSystemConfig, getChatGptSystemConfig, - setChatGptSystemConfig -} \ No newline at end of file + setChatGptSystemConfig, +}; diff --git a/chat2db-client/src/typings/ai.ts b/chat2db-client/src/typings/ai.ts new file mode 100644 index 000000000..6c01e5187 --- /dev/null +++ b/chat2db-client/src/typings/ai.ts @@ -0,0 +1,13 @@ +export enum AiSqlSourceType { + CHAT2DB='CHAT2DB', + OPENAI = 'OPENAI', + AZUREAI = 'AZUREAI', + RESTAI = 'RESTAI', +} + +export interface IRemainingUse { + key: string; + wechatMpUrl: string; + expiry: number; + remainingUses: number; +} From 112ddc0a7af1e0d83ece2c85b7141c5b77938041 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 19:54:06 +0800 Subject: [PATCH 0143/1069] fix: fix dashboard bug --- chat2db-client/src/pages/main/dashboard/chart-item/index.tsx | 1 + 1 file changed, 1 insertion(+) 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 2c1f33153..9e18e89b0 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -199,6 +199,7 @@ function ChartItem(props: IChartItemProps) { notification.success({ message: i18n('common.tips.saveSuccessfully'), }); + setIsEditing(false) }; const handleChartConfigChange = () => { From 676aa6954867ff6068f6b68bfa3ee9d4f07116db Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 1 Jul 2023 20:18:56 +0800 Subject: [PATCH 0144/1069] test release --- .github/workflows/release.yml | 53 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 17b2c207a..fae531b89 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,20 +88,24 @@ jobs: - name: Copy Jre for Windows if: ${{ runner.os == 'Windows' }} run: | - cp -r "${{ env.JAVA_HOME }}" ali-dbhub-client/electron/jre + 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: | - cp -r $JAVA_HOME ali-dbhub-client/electron/jre - chmod -R 777 ali-dbhub-client/electron/jre/ + 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 @@ -118,6 +122,15 @@ jobs: npm install npm run build:desktop cp -r dist ../ali-dbhub-server/ali-dbhub-server-start/src/main/resources/static/front + + + # 构建静态文件信息 + - name: Yarn install & build & copy + run: | + cd chat2db-client + yarn install + yarn run build:web:prod --appVersion=${{ steps.chat2db_version.outputs.substring }} + cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 - name: Build Java @@ -126,24 +139,23 @@ jobs: # 复制服务端java 到指定位置 - name: Copy App run: | - mkdir ali-dbhub-client/electron/app - cp ali-dbhub-server/ali-dbhub-server-start/target/ali-dbhub-server-start.jar ali-dbhub-client/electron/app/ + cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/static/ - # 安装electron - - name: Npm install electron + - name: Prepare Build Electron run: | - cd ali-dbhub-client/electron - cp -r ../dist ./ + cd chat2db-client + yarn run build:web:desktop --appVersion=${{ steps.chat2db_version.outputs.substring }} # windows - name: Build/release Electron app for Windows if: ${{ runner.os == 'Windows' }} uses: samuelmeuli/action-electron-builder@v1 with: - package_root: "ali-dbhub-client/electron" + 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.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --win --x64" release: true @@ -152,28 +164,31 @@ jobs: if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} uses: samuelmeuli/action-electron-builder@v1 with: - package_root: "ali-dbhub-client/electron" + 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.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --mac --x64" release: true + # amd64 notarization - name: Notarization amd64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} run: | - xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file ali-dbhub-client/electron-build/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg + xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg - # macos amd64 + # 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: "ali-dbhub-client/electron" + 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.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --mac --arm64" release: true @@ -181,29 +196,29 @@ jobs: - name: Notarization arm64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} run: | - xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file ali-dbhub-client/electron-build/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg + xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg # 准备要需要的数据 Windows - name: Prepare upload for Windows if: runner.os == 'Windows' run: | mkdir oss_temp_file - cp -r ali-dbhub-client/electron-build/*Setup*.exe ./oss_temp_file + cp -r chat2db-client/release/*Setup*.exe ./oss_temp_file # 准备要需要的数据 MacOS amd64 - name: Prepare upload for MacOS amd64 if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} run: | mkdir oss_temp_file - cp ali-dbhub-client/electron/app/ali-dbhub-server-start.jar ./oss_temp_file - cp -r ali-dbhub-client/electron-build/*.dmg ./oss_temp_file + cp chat2db-client/static/chat2db-server-start.jar ./oss_temp_file + cp -r chat2db-client/release/*.dmg ./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 ali-dbhub-client/electron-build/*.dmg ./oss_temp_file + cp -r chat2db-client/release/*.dmg ./oss_temp_file # 把文件上传到OSS 方便下载 - name: Set up oss utils From 4bd835673b668acee995949ca8bfb677739172b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 20:25:10 +0800 Subject: [PATCH 0145/1069] =?UTF-8?q?feat:outside=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.ts | 6 +- .../src/components/TestVersion/index.less | 19 +++++ .../src/components/TestVersion/index.tsx | 85 +++++++++++++++++++ chat2db-client/src/layouts/index.tsx | 4 +- .../pages/main/dashboard/chart-item/index.tsx | 6 +- chat2db-client/src/pages/main/index.tsx | 12 +-- .../Tree/TreeNodeRightClick/index.tsx | 11 +-- chat2db-client/src/service/base.ts | 47 ++++++---- chat2db-client/src/service/outside.ts | 8 ++ chat2db-client/src/typings/common.ts | 24 ++++++ 10 files changed, 189 insertions(+), 33 deletions(-) create mode 100644 chat2db-client/src/components/TestVersion/index.less create mode 100644 chat2db-client/src/components/TestVersion/index.tsx create mode 100644 chat2db-client/src/service/outside.ts diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index c8496e029..dd2a27398 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -13,9 +13,9 @@ const chainWebpack = (config: any, { webpack }: any) => { export default defineConfig({ title: 'Chat2DB', - // history: { - // type: 'hash', - // }, + history: { + type: 'hash', + }, base: '/', publicPath: '/', hash: false, diff --git a/chat2db-client/src/components/TestVersion/index.less b/chat2db-client/src/components/TestVersion/index.less new file mode 100644 index 000000000..fd016288a --- /dev/null +++ b/chat2db-client/src/components/TestVersion/index.less @@ -0,0 +1,19 @@ +@import '../../styles/var.less'; + +.updateHint { + display: flex; + justify-content: space-between; +} + +.later { + cursor: pointer; +} + +.go { + cursor: pointer; + margin-left: 20px; + color: var(--custom-primary-color); + &:hover { + text-decoration: underline; + } +} diff --git a/chat2db-client/src/components/TestVersion/index.tsx b/chat2db-client/src/components/TestVersion/index.tsx new file mode 100644 index 000000000..069a139d3 --- /dev/null +++ b/chat2db-client/src/components/TestVersion/index.tsx @@ -0,0 +1,85 @@ +import React, { memo, useEffect } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { notification, Space, Button } from 'antd'; +import outSideService from '@/service/outside' + +interface IProps { + className?: string; +} + +export default memo(function TestVersion(props) { + const { className } = props; + const [notificationApi, notificationDom] = notification.useNotification(); + + useEffect(() => { + getVersions(); + }, []) + + const close = () => { }; + + function getVersions() { + try { + const time = +(localStorage.getItem('update-hint-time') || 0); + const nowTime = new Date().getTime(); + if (time < nowTime) { + outSideService.checkVersion().then(res => { + localStorage.setItem('app-gateway-params', JSON.stringify(res)) + openNotification(res); + const time = new Date().getTime() + 2 * 60 * 60 * 1000; + localStorage.setItem('update-hint-time', time.toString()) + }) + } + } + catch { + + } + } + + function updateHint() { + notificationApi.destroy(); + const time = new Date().getTime() + 24 * 60 * 60 * 1000; + localStorage.setItem('update-hint-time', time.toString()); + } + + + function go(responseText: any) { + window.open(responseText.downloadLink) + notificationApi.destroy(); + } + + const openNotification = (responseText: any) => { + console.log(responseText) + try { + if (responseText.version !== '1.0.11') { + const key = `open${Date.now()}`; + + const btn = ( + + + + + ); + + notificationApi.open({ + message: '更新提醒', + description: `监测到最新版本 v${responseText.version}`, + btn, + key, + onClose: close, + }); + } + } + catch { + + } + }; + return <> + {notificationDom} + + +}) diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index bdd857dde..7110040f7 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useLayoutEffect } from 'react'; import { Outlet } from 'umi'; -import { ConfigProvider, theme } from 'antd'; +import { ConfigProvider, theme, notification } from 'antd'; import { useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig } from '@/theme'; +import { IVersionResponse } from '@/typings'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; @@ -21,6 +22,7 @@ declare global { _BUILD_TIME: string; _BaseURL: string; _AppThemePack: { [key in string]: string }; + _appGatewayParams: IVersionResponse; } const __APP_VERSION__: string; const __BUILD_TIME__: string; 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 9e18e89b0..d691cdfa2 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -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, MenuProps, notification, Select } from 'antd'; +import { Button, Cascader, Dropdown, Form, MenuProps, message, notification, Select } from 'antd'; import { deleteChart, getChartById, updateChart } from '@/service/dashboard'; import { data } from '../../../../../mock/sqlResult.json'; import Console from '@/components/Console'; @@ -196,10 +196,8 @@ function ChartItem(props: IChartItemProps) { schema: JSON.stringify(form.getFieldsValue(true)), }; await updateChart(params); - notification.success({ - message: i18n('common.tips.saveSuccessfully'), - }); setIsEditing(false) + message.success(i18n('common.tips.saveSuccessfully')) }; const handleChartConfigChange = () => { diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index eb8bc3a8c..17c82004a 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -9,7 +9,8 @@ import BrandLogo from '@/components/BrandLogo'; import { IMainPageType } from '@/models/mainPage'; import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; -import { findObjListValue } from '@/utils' +import { findObjListValue } from '@/utils'; +import TestVersion from '@/components/TestVersion'; import DataSource from './connection'; import Workspace from './workspace'; @@ -19,6 +20,7 @@ import Chat from './chat'; import styles from './index.less'; import { INavItem } from '@/typings/main'; const navConfig: INavItem[] = [ + { key: 'workspace', icon: '\ue616', @@ -45,7 +47,7 @@ const navConfig: INavItem[] = [ }, ]; -const initPageIndex = navConfig.findIndex(t => `/${t.key}` === window.location.pathname); +const initPageIndex = navConfig.findIndex(t => `#/${t.key}` === window.location.hash); interface IProps { mainModel: IMainPageType['state']; @@ -58,7 +60,7 @@ function MainPage(props: IProps) { const { mainModel, workspaceModel, connectionModel, dispatch } = props; const { curPage } = mainModel; const { curConnection } = connectionModel; - const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > 0 ? initPageIndex : 0]); + const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > -1 ? initPageIndex : 2]); useEffect(() => { // activeNav 发生变化,同步到全局状态管理 @@ -72,7 +74,7 @@ function MainPage(props: IProps) { // } // activeNav 变化 同步地址栏变化 // change url,but no page refresh - window.history.pushState({}, "", activeNav.key); + window.history.pushState({}, "", `/#/${activeNav.key}`); }, [activeNav]) useEffect(() => { @@ -102,7 +104,6 @@ function MainPage(props: IProps) { } } - return (
@@ -140,6 +141,7 @@ function MainPage(props: IProps) { ); })}
+
); } diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index b2010b59e..1ce799aa4 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -179,11 +179,12 @@ function TreeNodeRightClick(props: IProps) { tableName: data.name, } mysqlServer.deleteTable(p).then(res => { - notificationApi.success( - { - message: i18n('common.text.successfullyDelete'), - } - ) + // notificationApi.success( + // { + // message: i18n('common.text.successfullyDelete'), + // } + // ) + message.success(i18n('common.text.successfullyDelete')) dispatch({ type: 'workspace/fetchGetCurTableList', payload: { diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 9226fa5fe..5d6df8670 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -8,6 +8,8 @@ export interface IOptions { mock?: boolean; errorLevel?: 'toast' | 'prompt' | 'critical' | false; delayTime?: number | true; + outside?: true; + } // TODO: @@ -52,19 +54,31 @@ const baseURL = window._BaseURL = baseURL; +const appGatewayParams = localStorage.getItem('app-gateway-params'); + +// appGateway 的基本信息 +if (appGatewayParams) { + window._appGatewayParams = JSON.parse(appGatewayParams); +} else { + window._appGatewayParams = {}; +} + +const outsideUrlPrefix = window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway/' + + const errorHandler = (error: ResponseError, errorLevel: IErrorLevel) => { const { response } = error; if (!response) return; const errorText = codeMessage[response.status] || response.statusText; const { status } = response; if (errorLevel === 'toast') { - notification.open({ - type: 'error', - message: status, - description: errorText, - placement: 'topRight', - }); - // message.error(`${status}: ${errorText}`); + // notification.open({ + // type: 'error', + // message: status, + // description: errorText, + // placement: 'topRight', + // }); + message.error(`${status}: ${errorText}`); } }; @@ -111,13 +125,14 @@ request.interceptors.response.use(async (response, options) => { }); export default function createRequest

(url: string, options?: IOptions) { - const { method = 'get', mock = false, errorLevel = 'toast', delayTime } = options || {}; + const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside } = options || {}; // 是否需要mock const _baseURL = mock ? mockUrl : baseURL; return function (params: P) { // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; + const _url = url.replace(/:(.+?)\b/, (_, name: string) => { const value = params[name]; paramsInUrl.push(name); @@ -147,18 +162,20 @@ export default function createRequest

(url: string, options?: I break; } - request[method](`${_baseURL}${_url}`, { [dataName]: params }) + const eventualUrl = outside ? `${outsideUrlPrefix}${_url}` : `${_baseURL}${_url}`; + + request[method](eventualUrl, { [dataName]: params }) .then((res) => { if (!res) return; const { success, errorCode, errorMessage, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { - notification.open({ - type: 'error', - message: errorCode, - description: errorMessage, - }); - // message.error(`${errorCode}: ${errorMessage}`); + // notification.open({ + // type: 'error', + // message: errorCode, + // description: errorMessage, + // }); + message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; diff --git a/chat2db-client/src/service/outside.ts b/chat2db-client/src/service/outside.ts new file mode 100644 index 000000000..79ad31517 --- /dev/null +++ b/chat2db-client/src/service/outside.ts @@ -0,0 +1,8 @@ +import createRequest from "./base"; +import {IVersionResponse} from '@/typings' + +const checkVersion = createRequest('/client/version/check/v2', { errorLevel: false, outside: true }); + +export default { + checkVersion, +} \ No newline at end of file diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 1fcc12fba..943f71df6 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -43,3 +43,27 @@ export interface IUniversalTableParams { schemaName?: string; tableName?: string; } + +/** + * 版本返回 + * VersionResponse + */ + export interface IVersionResponse { + /** + * 基础链接 + * 类似于:http://test.sqlgpt.cn/gateway + */ + baseUrl?: string; + /** + * 下载链接 + */ + downloadLink?: string; + /** + * 版本 + */ + version?: string; + /** + * 微信公众号名字 + */ + wechatMpName?: string; +} From 016842271861eb9ceff739b9bf32d332032af8fb Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 1 Jul 2023 20:26:32 +0800 Subject: [PATCH 0146/1069] test release --- .github/workflows/release.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fae531b89..01d2260ed 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,14 +115,6 @@ jobs: distribution: "temurin" cache: "maven" - # 构建静态文件信息 - - name: Npm install & build & copy - run: | - cd ali-dbhub-client - npm install - npm run build:desktop - cp -r dist ../ali-dbhub-server/ali-dbhub-server-start/src/main/resources/static/front - # 构建静态文件信息 - name: Yarn install & build & copy @@ -134,7 +126,7 @@ jobs: # 编译服务端java版本 - name: Build Java - run: mvn clean package -B '-Dmaven.test.skip=true' -f ali-dbhub-server/pom.xml + run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml # 复制服务端java 到指定位置 - name: Copy App @@ -255,7 +247,7 @@ jobs: content: | { "title": "MacOS-amd64-release-构建完成通知", - "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/ali-dbhub-server-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/ali-dbhub-server-start.jar) " + "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-start.jar) " } # 构建完成通知 From b69abd258e1cb02f29972909dbe11d5cb240d138 Mon Sep 17 00:00:00 2001 From: moji Date: Sat, 1 Jul 2023 20:55:00 +0800 Subject: [PATCH 0147/1069] add demo --- ...235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" new file mode 100644 index 000000000..fb02bd85c --- /dev/null +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" @@ -0,0 +1,2 @@ +INSERT INTO DATA_SOURCE (GMT_CREATE, GMT_MODIFIED, ALIAS, URL, USER_NAME, PASSWORD, TYPE, USER_ID, HOST, PORT, SSH,JDBC) +VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'DEMO@db.sqlgpt.cn', 'jdbc:mysql://db.sqlgpt.cn:3306/DEMO', 'demo', 'demo', 'MYSQL', 0, 'db.sqlgpt.cn', '3306', '{"use":false}', '8.0'); From 2d6cd8de3588f212c33682c713673bc1ae5bb270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 21:01:58 +0800 Subject: [PATCH 0148/1069] =?UTF-8?q?feat:=E5=90=AF=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/blocks/Setting/AiSetting/index.tsx | 8 +-- chat2db-client/src/blocks/Setting/index.less | 5 +- .../src/components/TestVersion/index.tsx | 15 +++-- chat2db-client/src/i18n/en-us/common.ts | 7 +++ chat2db-client/src/i18n/zh-cn/common.ts | 7 +++ chat2db-client/src/layouts/index.less | 26 ++++++++ chat2db-client/src/layouts/index.tsx | 61 ++++++++++++++++++- 7 files changed, 114 insertions(+), 15 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index e0aa4e4d9..11dc6e155 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -44,14 +44,14 @@ export default function SettingAI(props: IProps) { onChange={(e) => { setChatGPTConfig({ ...chatGPTConfig, aiSqlSource: e.target.value }); }} - value={chatGPTConfig.aiSqlSource} + value={chatGPTConfig?.aiSqlSource} > Open AI Azure AI {i18n('setting.tab.custom')}

- {chatGPTConfig.aiSqlSource === AiSqlSourceType.OPENAI && ( + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.OPENAI && (
Api Key
@@ -101,7 +101,7 @@ export default function SettingAI(props: IProps) {
)} - {chatGPTConfig.aiSqlSource === AiSqlSourceType.AZUREAI && ( + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.AZUREAI && (
Api Key
@@ -138,7 +138,7 @@ export default function SettingAI(props: IProps) {
)} - {chatGPTConfig.aiSqlSource === AiSqlSourceType.RESTAI && ( + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.RESTAI && (
{i18n('setting.label.customAiUrl')}
diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index 330064791..2d538b0bc 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -9,7 +9,8 @@ } .setText { - color: var(--custom-primary-color); + color: var(--custom-primary); + font-size: 16px; &:hover { text-decoration: underline; @@ -79,4 +80,4 @@ font-size: 16px; padding-bottom: 20px; } -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/TestVersion/index.tsx b/chat2db-client/src/components/TestVersion/index.tsx index 069a139d3..26f520124 100644 --- a/chat2db-client/src/components/TestVersion/index.tsx +++ b/chat2db-client/src/components/TestVersion/index.tsx @@ -2,7 +2,8 @@ import React, { memo, useEffect } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { notification, Space, Button } from 'antd'; -import outSideService from '@/service/outside' +import outSideService from '@/service/outside'; +import i18n from '@/i18n'; interface IProps { className?: string; @@ -51,23 +52,21 @@ export default memo(function TestVersion(props) { const openNotification = (responseText: any) => { console.log(responseText) try { - if (responseText.version !== '1.0.11') { + if (responseText.version !== '1.1.0') { const key = `open${Date.now()}`; - const btn = ( ); - notificationApi.open({ - message: '更新提醒', - description: `监测到最新版本 v${responseText.version}`, + message: i18n('common.text.updateReminder'), + description: `${'common.text.detectionLatestVersion'} v${responseText.version}`, btn, key, onClose: close, diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index c0af1e906..7df3890dc 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -48,4 +48,11 @@ export default { 'common.text.result': 'Result', 'common.text.timeConsuming': 'Time Consuming', 'common.text.noData': 'No Data', + 'common.text.remindMeLater': 'Remind Me Later', + 'common.text.goToUpdate': 'Go To Update', + 'common.text.updateReminder': 'Update Reminder', + 'common.text.detectionLatestVersion': 'The latest version is monitored', + 'common.text.setting': 'Setting', + 'common.text.tryToRestart': 'Try To Restart', + 'common.text.contactUs': 'Contact Us', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 2c6041d09..ac1816758 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -48,5 +48,12 @@ export default { 'common.text.result': '结果', 'common.text.timeConsuming': '耗时', 'common.text.noData': '暂无数据', + 'common.text.remindMeLater': '稍后提醒我', + 'common.text.goToUpdate': '前往更新', + 'common.text.updateReminder': '更新提醒', + 'common.text.detectionLatestVersion': '监测到最新版本', + 'common.text.setting': '设置', + 'common.text.tryToRestart': '尝试重新启动', + 'common.text.contactUs': '联系我们', }; diff --git a/chat2db-client/src/layouts/index.less b/chat2db-client/src/layouts/index.less index 395cd1310..2eb5969d4 100644 --- a/chat2db-client/src/layouts/index.less +++ b/chat2db-client/src/layouts/index.less @@ -15,3 +15,29 @@ 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); +} diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 7110040f7..f153c5f3a 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -5,6 +5,10 @@ import { useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig } from '@/theme'; import { IVersionResponse } from '@/typings'; +import classnames from 'classnames'; +import LoadingLiquid from '@/components/Loading/LoadingLiquid'; +import Setting from '@/blocks/Setting'; +import miscService from '@/service/misc'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; @@ -14,6 +18,7 @@ import { ThemeType, PrimaryColorType, LangType } from '@/constants/'; import { InjectThemeVar } from '@/theme'; import styles from './index.less'; import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorage'; +import i18n from '@/i18n'; declare global { interface Window { @@ -56,10 +61,15 @@ export default function Layout() { ); } +/** 重启次数 */ +const restartCount = 200; + function AppContainer() { const { token } = useToken(); const [initEnd, setInitEnd] = useState(false); const [appTheme, setAppTheme] = useTheme(); + const [startSchedule, setStartSchedule] = useState(0); // 0 初始状态 1 服务启动中 2 启动成功 + const [serviceFail, setServiceFail] = useState(false); useEffect(() => { let date = new Date('2030-12-30 12:30:00').toUTCString(); @@ -119,11 +129,60 @@ function AppContainer() { } } + useEffect(() => { + detectionService(); + }, []); + + function detectionService() { + setServiceFail(false); + let flag = 0; + const time = setInterval(() => { + miscService.testService().then(() => { + clearInterval(time); + setStartSchedule(2); + flag++; + }).catch(error => { + setStartSchedule(1); + flag++; + }); + if (flag > restartCount) { + setServiceFail(true); + clearInterval(time); + } + }, 1000); + } + return (
{initEnd && (
- + {/* 待启动状态 */} + {startSchedule === 0 &&
} + {/* 服务启动中 */} + {startSchedule === 1 &&
+
+ { + !serviceFail &&
+ +
+ } +
+ +
+ {serviceFail && ( + <> +
+ {i18n('common.text.contactUs')}-github:github +
+
+ {i18n('common.text.tryToRestart')} +
+ + )} +
+
} + {/* 服务启动完成 */} + {startSchedule === 2 && }
)}
From 93cc586e25bce648f789b21378689306199745c4 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 1 Jul 2023 21:09:58 +0800 Subject: [PATCH 0149/1069] test release --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 01d2260ed..0e0618758 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -247,7 +247,7 @@ jobs: content: | { "title": "MacOS-amd64-release-构建完成通知", - "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-start.jar) " + "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar) " } # 构建完成通知 From f9a75367e4b85dfbccfe4914cef29b238c0329d2 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 21:15:30 +0800 Subject: [PATCH 0150/1069] feat: add remaining --- chat2db-client/.umirc.ts | 6 +- .../src/blocks/Setting/AiSetting/index.tsx | 9 +- chat2db-client/src/blocks/Setting/index.tsx | 15 +++- .../components/Console/ChatInput/index.tsx | 17 ++-- .../src/components/Console/index.tsx | 49 ++++++++--- chat2db-client/src/i18n/en-us/chat.ts | 2 + chat2db-client/src/i18n/zh-cn/chat.ts | 7 +- .../components/WorkspaceRight/index.tsx | 84 ++++++++++--------- .../components/WorkspaceRightItem/index.tsx | 20 ++--- chat2db-client/src/service/ai.ts | 2 +- chat2db-client/src/service/base.ts | 2 +- .../azure/client/AzureOpenAiStreamClient.java | 3 + 12 files changed, 134 insertions(+), 82 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index dd2a27398..1cd98fb6c 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -34,7 +34,11 @@ export default defineConfig({ proxy: { '/api': { target: 'http://127.0.0.1:10821', - changeOrigin: true + changeOrigin: true, + }, + '/client/remaininguses/': { + target: 'http://127.0.0.1:1889', + changeOrigin: true, }, }, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 11dc6e155..0c74ba7e3 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -9,12 +9,17 @@ import styles from './index.less'; interface IProps { handleUpdateAiConfig: (payload: IAIState['keyAndAiType']) => void; - chatGPTConfig?: IChatGPTConfig; + chatGPTConfig: IChatGPTConfig; } + // openAI 的设置项 export default function SettingAI(props: IProps) { const { handleUpdateAiConfig } = props; - const [chatGPTConfig, setChatGPTConfig] = useState(); + const [chatGPTConfig, setChatGPTConfig] = useState(props?.chatGPTConfig); + + if (!chatGPTConfig) { + return null; + } useEffect(() => { setChatGPTConfig(props.chatGPTConfig); diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 6027c993b..b0845aca2 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -17,11 +17,22 @@ interface IProps { text?: string; dispatch: Function; } - +const initChatGPTConfig = { + apiKey: '', + httpProxyHost: '', + httpProxyPort: '', + restAiUrl: '', + apiHost: '', + restAiStream: true, + aiSqlSource: '', + azureApiKey: '', + azureEndpoint: '', + azureDeploymentId: '', +}; function Setting(props: IProps) { const { className, text, dispatch } = props; const [isModalVisible, setIsModalVisible] = useState(false); - const [chatGPTConfig, setChatGPTConfig] = useState(); + const [chatGPTConfig, setChatGPTConfig] = useState(initChatGPTConfig); const [currentMenu, setCurrentMenu] = useState(0); useEffect(() => { diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 8c6a37609..740c9d8b8 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -13,9 +13,10 @@ interface IProps { result?: string; tables?: string[]; selectedTables?: string[]; - remainingUse: IRemainingUse; + remainingUse?: IRemainingUse; onPressEnter: (value: string) => void; onSelectTables?: (tables: string[]) => void; + onClickRemainBtn: Function; } function ChatInput(props: IProps) { @@ -53,7 +54,7 @@ function ChatInput(props: IProps) { }; const renderSuffix = () => { - const remainCnt = props.remainingUse || '-'; + const remainCnt = props?.remainingUse?.remainingUses || '-'; return (
{/*
⌘ + ↵
*/} @@ -62,15 +63,9 @@ function ChatInput(props: IProps) {
-
{ - Modal.confirm({ - title: 'wechat', - content: , - }); - }} - > +
{ + props.onClickRemainBtn && props.onClickRemainBtn(); + }}> {i18n('chat.input.remain', remainCnt)}
diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 25f22080f..fc81003b0 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import { connect } from 'umi'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; -import { Button, Spin, message, Drawer } from 'antd'; +import { Button, Spin, message, Drawer, Modal } from 'antd'; import ChatInput from './ChatInput'; import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; import { format } from 'sql-formatter'; @@ -15,6 +15,8 @@ import { ITreeNode } from '@/typings'; import styles from './index.less'; import i18n from '@/i18n'; import { IRemainingUse } from '@/typings/ai'; +import { IAIState } from '@/models/ai'; +import { WECHAT_MP_URL } from '@/constants/social'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -59,15 +61,17 @@ interface IProps { }; tableList?: ITreeNode[]; editorOptions?: IEditorOptions; - remainingUse: IRemainingUse; + aiModel: IAIState; + dispatch: Function; + // remainingUse: IAIState['remainingUse']; // onSQLContentChange: (v: string) => void; onExecuteSQL: (result: any, sql: string, createHistoryParams) => void; onConsoleSave: () => void; - tables: any[] + tables: any[]; } function Console(props: IProps) { - const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value, remainingUse } = props; + const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value, aiModel, dispatch } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -84,16 +88,15 @@ function Console(props: IProps) { } }, [appendValue]); - useEffect(() => { monacoHint.current?.dispose(); const myEditorHintData: any = {}; - console.log(props.tables) + console.log(props.tables); props.tables?.map((item: any) => { myEditorHintData[item.name] = []; }); monacoHint.current = editorRef?.current?.handleRegisterTigger(myEditorHintData); - }, [props.tables]) + }, [props.tables]); const tableListName = useMemo(() => { const tableList = (props.tables || []).map((t) => t.name); @@ -105,6 +108,18 @@ function Console(props: IProps) { }, [props.tables]); const handleAiChat = (content: string, promptType: IPromptType) => { + if (!aiModel.remainingUse?.remainingUses) { + popUpPrompts(); + return; + } + + dispatch({ + type: 'ai/fetchRemainingUse', + payload: { + key: aiModel?.remainingUse?.key, + }, + }); + const { dataSourceId, databaseName, schemaName } = executeParams; const isNL2SQL = promptType === IPromptType.NL_2_SQL; if (isNL2SQL) { @@ -193,7 +208,7 @@ function Console(props: IProps) { let p: any = { id: executeParams.consoleId, status: ConsoleStatus.RELEASE, - ddl: value + ddl: value, }; historyServer.updateSavedConsole(p).then((res) => { message.success(i18n('common.tips.saveSuccessfully')); @@ -222,18 +237,27 @@ function Console(props: IProps) { [], ); + const popUpPrompts = () => { + Modal.info({ + title: i18n('chat.input.remain.dialog.tips'), + content: , + }); + }; return (
{hasAiChat && ( { setSelectedTables(tables); }} + onClickRemainBtn={() => { + popUpPrompts(); + }} /> )} {/*
{chatContent.current}
*/} @@ -248,7 +272,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> @@ -284,4 +308,7 @@ function Console(props: IProps) { ); } -export default Console; +const dvaModel = connect(({ ai }: { ai: IAIState }) => ({ + aiModel: ai, +})); +export default dvaModel(Console); diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts index b1a03f1d1..efcf1d4f0 100644 --- a/chat2db-client/src/i18n/en-us/chat.ts +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -1,4 +1,6 @@ export default { 'chat.input.remain': '{1} remaining', 'chat.input.remain.tooltip': 'Table schema added to prompt.', + 'chat.input.remain.dialog.tips': + 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index c26e5e3ba..fba0602a2 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -1,4 +1,5 @@ -export default{ +export default { 'chat.input.remain': '剩余 {1} 次', - 'chat.input.remain.tooltip': '选中表的schema将会被添加到prompt中' -} \ No newline at end of file + 'chat.input.remain.tooltip': '选中表的schema将会被添加到prompt中', + 'chat.input.remain.dialog.tips': '关注公众号,发送“推广”获取更多体验次数', +}; 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 0d3d0b6f9..204e1ce2d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -1,5 +1,5 @@ import React, { memo, useRef, useEffect, useState } from 'react'; -import { connect } from 'umi' +import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode } from '@/constants'; @@ -8,18 +8,19 @@ import historyService from '@/service/history'; import Tabs from '@/components/Tabs'; import LoadingContent from '@/components/Loading/LoadingContent'; import WorkspaceRightItem from '../WorkspaceRightItem'; -import { IWorkspaceModelType } from '@/models/workspace'; +import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; +import { IAIState } from '@/models/ai'; interface IProps { className?: string; - workspaceModel: IWorkspaceModelType['state']; + workspaceModel: IWorkspaceModelState; + aiModel: IAIState; dispatch: any; } const WorkspaceRight = memo(function (props) { - const { className } = props; const [activeConsoleId, setActiveConsoleId] = useState(); - const { workspaceModel, dispatch } = props; + const { className, aiModel, workspaceModel, dispatch } = props; const { databaseAndSchema, curWorkspaceParams, doubleClickTreeNodeData, openConsoleList } = workspaceModel; useEffect(() => { @@ -29,7 +30,7 @@ const WorkspaceRight = memo(function (props) { useEffect(() => { // 这里只处理没有console的情况下 if (!doubleClickTreeNodeData || openConsoleList?.length) { - return + return; } const { extraParams } = doubleClickTreeNodeData; @@ -50,7 +51,7 @@ const WorkspaceRight = memo(function (props) { addConsole(p); dispatch({ type: 'workspace/setDoubleClickTreeNodeData', - payload: '' + payload: '', }); }, [doubleClickTreeNodeData]); @@ -61,16 +62,16 @@ const WorkspaceRight = memo(function (props) { setActiveConsoleId(openConsoleList[0].id); } else { let flag = false; - openConsoleList.forEach(t => { + openConsoleList.forEach((t) => { if (t.id === activeConsoleId) { - flag = true + flag = true; } - }) + }); if (!flag) { - setActiveConsoleId(openConsoleList[openConsoleList.length - 1].id) + setActiveConsoleId(openConsoleList[openConsoleList.length - 1].id); } } - }, [openConsoleList]) + }, [openConsoleList]); function getConsoleList() { let p: any = { @@ -87,9 +88,9 @@ const WorkspaceRight = memo(function (props) { dispatch({ type: 'workspace/setOpenConsoleList', payload: res.data, - }) - } - }) + }); + }, + }); } function onChange(key: number | string) { @@ -106,7 +107,7 @@ const WorkspaceRight = memo(function (props) { }; const addConsole = (params?: ICreateConsole) => { - const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams + const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; let p = { name: `new console${openConsoleList?.length}`, ddl: '', @@ -116,12 +117,12 @@ const WorkspaceRight = memo(function (props) { type: databaseType, status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, - } - historyService.saveConsole(params || p).then(res => { + }; + historyService.saveConsole(params || p).then((res) => { setActiveConsoleId(res); getConsoleList(); - }) - } + }); + }; const closeWindowTab = (key: number) => { let newActiveKey = activeConsoleId; @@ -142,8 +143,8 @@ const WorkspaceRight = memo(function (props) { } dispatch({ type: 'workspace/setOpenConsoleList', - payload: newPanes - }) + payload: newPanes, + }); setActiveConsoleId(newActiveKey); let p: any = { @@ -163,9 +164,7 @@ const WorkspaceRight = memo(function (props) { }; function render() { - return
- Chat2DB -
+ return
Chat2DB
; } return ( @@ -186,11 +185,14 @@ const WorkspaceRight = memo(function (props) { />
{openConsoleList?.map((t, index) => { - return
- + (function (props) { schemaName: curWorkspaceParams?.schemaName!, consoleId: t.id, consoleName: t.name, - } - } - /> -
+ }} + workspaceModel={workspaceModel} + aiModel={aiModel} + dispatch={dispatch} + /> +
+ ); })}
); -}) +}); -const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace -})) +const dvaModel = connect(({ workspace, ai }: { workspace: IWorkspaceModelType; ai: IAIState }) => ({ + workspaceModel: workspace, + aiModel: ai, +})); -export default dvaModel(WorkspaceRight) +export default dvaModel(WorkspaceRight); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 1cb999b83..d9060b10a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -7,13 +7,15 @@ import Console, { IAppendValue } from '@/components/Console'; import SearchResult from '@/components/SearchResult'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { IManageResultData } from '@/typings'; -import { IWorkspaceModelType } from '@/models/workspace'; +import { IWorkspaceModelState } from '@/models/workspace'; import historyServer from '@/service/history'; +import { IAIState } from '@/models/ai'; interface IProps { className?: string; isActive: boolean; - workspaceModel: IWorkspaceModelType['state']; + workspaceModel: IWorkspaceModelState; + aiModel: IAIState; dispatch: any; data: { databaseName: string; @@ -27,7 +29,7 @@ interface IProps { } const WorkspaceRightItem = memo(function (props) { - const { className, data, workspaceModel, isActive, dispatch } = props; + const { className, data, workspaceModel, aiModel, isActive, dispatch } = props; const draggableRef = useRef(); const [appendValue, setAppendValue] = useState({ text: data.initDDL }); const [resultData, setResultData] = useState([]); @@ -64,7 +66,6 @@ const WorkspaceRightItem = memo(function (props) { setResultData(res); setShowResult(true); historyServer.createHistory(params); - }} onConsoleSave={() => { dispatch({ @@ -78,11 +79,12 @@ const WorkspaceRightItem = memo(function (props) { dispatch({ type: 'workspace/setConsoleList', payload: res.data, - }) - } + }); + }, }); }} tables={curTableList || []} + remainingUse={aiModel.remainingUse} />
{}
@@ -91,8 +93,4 @@ const WorkspaceRightItem = memo(function (props) { ); }); -const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace, -})); - -export default dvaModel(WorkspaceRightItem); +export default WorkspaceRightItem; diff --git a/chat2db-client/src/service/ai.ts b/chat2db-client/src/service/ai.ts index 08ae1f443..82a7fe583 100644 --- a/chat2db-client/src/service/ai.ts +++ b/chat2db-client/src/service/ai.ts @@ -2,6 +2,6 @@ import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; import { IRemainingUse } from '@/typings/ai'; import createRequest from './base'; -const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/'); +const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/:key'); export default { getRemainingUse }; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 5d6df8670..4d56eedd1 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -128,7 +128,7 @@ export default function createRequest

(url: string, options?: I const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside } = options || {}; // 是否需要mock - const _baseURL = mock ? mockUrl : baseURL; + let _baseURL = mock ? mockUrl : baseURL; return function (params: P) { // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; 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 1c48ec707..426582d42 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 @@ -44,6 +44,9 @@ public class AzureOpenAiStreamClient { */ public AzureOpenAiStreamClient(String apiKey, String endpoint, String deployId) { this.deployId = deployId; + if (StringUtils.isBlank(apiKey)) { + return; + } this.client = new OpenAIClientBuilder() .credential(new AzureKeyCredential(apiKey)) .endpoint(endpoint) From e334eb3b97d34cafe0954d27a3e5ee031b4900c3 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 1 Jul 2023 21:18:04 +0800 Subject: [PATCH 0151/1069] feat: fix serve --- chat2db-client/src/service/ai.ts | 2 +- chat2db-client/src/service/outside.ts | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/chat2db-client/src/service/ai.ts b/chat2db-client/src/service/ai.ts index 82a7fe583..165606cb2 100644 --- a/chat2db-client/src/service/ai.ts +++ b/chat2db-client/src/service/ai.ts @@ -2,6 +2,6 @@ import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; import { IRemainingUse } from '@/typings/ai'; import createRequest from './base'; -const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/:key'); +const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/:key', { outside: true }); export default { getRemainingUse }; diff --git a/chat2db-client/src/service/outside.ts b/chat2db-client/src/service/outside.ts index 79ad31517..9062cc4d4 100644 --- a/chat2db-client/src/service/outside.ts +++ b/chat2db-client/src/service/outside.ts @@ -1,8 +1,11 @@ -import createRequest from "./base"; -import {IVersionResponse} from '@/typings' +import createRequest from './base'; +import { IVersionResponse } from '@/typings'; -const checkVersion = createRequest('/client/version/check/v2', { errorLevel: false, outside: true }); +const checkVersion = createRequest('/client/version/check/v2', { + errorLevel: false, + outside: true, +}); -export default { +export default { checkVersion, -} \ No newline at end of file +}; From 8490ca4ab8937b4be3fc5cd1904e56a0b11b8eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=95=E8=B4=BA=E5=96=9C?= Date: Sat, 1 Jul 2023 21:21:08 +0800 Subject: [PATCH 0152/1069] =?UTF-8?q?fix:=E6=A1=8C=E9=9D=A2=E7=AB=AF?= =?UTF-8?q?=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.desktop.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index aafa2beba..716ac6092 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -1,7 +1,7 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -const UMI_PublicPath = process.env.UMI_PublicPath || './'; +// const UMI_PublicPath = process.env.UMI_PublicPath || './'; const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: UMI_PublicPath, + publicPath: './', chainWebpack, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], define: { From 4d8951b8fbf4928894ec7c43d7b50934ad17a1cf Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 1 Jul 2023 22:27:54 +0800 Subject: [PATCH 0153/1069] fix:github --- .github/workflows/release.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fae531b89..0f003a2b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,21 +115,12 @@ jobs: distribution: "temurin" cache: "maven" - # 构建静态文件信息 - - name: Npm install & build & copy - run: | - cd ali-dbhub-client - npm install - npm run build:desktop - cp -r dist ../ali-dbhub-server/ali-dbhub-server-start/src/main/resources/static/front - - # 构建静态文件信息 - name: Yarn install & build & copy run: | cd chat2db-client yarn install - yarn run build:web:prod --appVersion=${{ steps.chat2db_version.outputs.substring }} + yarn run build:desktop --appVersion=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 From 8449e30783c028eba82915da01fe4b5904ce667c Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 1 Jul 2023 22:55:36 +0800 Subject: [PATCH 0154/1069] =?UTF-8?q?fix:=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 6 +++--- chat2db-client/package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f003a2b9..1df69df58 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,12 +120,12 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:desktop --appVersion=${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:desktop --appVersion=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 - name: Build Java - run: mvn clean package -B '-Dmaven.test.skip=true' -f ali-dbhub-server/pom.xml + run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml # 复制服务端java 到指定位置 - name: Copy App @@ -246,7 +246,7 @@ jobs: content: | { "title": "MacOS-amd64-release-构建完成通知", - "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/ali-dbhub-server-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/ali-dbhub-server-start.jar) " + "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar) " } # 构建完成通知 diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 1882a9fe1..a90a06ebc 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -15,7 +15,7 @@ "build:main": "cross-env NODE_ENV=development electron-builder", "build:main:prod": "cross-env NODE_ENV=production electron-builder", "build:web": "umi build", - "build:web:desktop": "cross-env UMI_ENV=desktop APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", + "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", "postinstall": "umi setup", "lint": "umi lint", From 319d66f951e4c204373bf1b51b2cf1673ef55202 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 1 Jul 2023 23:19:10 +0800 Subject: [PATCH 0155/1069] =?UTF-8?q?fix:=E6=89=93=E5=8C=85=E7=99=BD?= =?UTF-8?q?=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release_test.yml | 2 +- chat2db-client/.umirc.prod.ts | 2 +- .../src/blocks/Setting/AiSetting/index.tsx | 15 +++++++++++++++ chat2db-client/src/i18n/en-us/setting.ts | 1 + chat2db-client/src/i18n/zh-cn/setting.ts | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index de263efc1..060ce0d39 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -106,7 +106,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --appVersion=1.0.${{ github.run_id }} --appPort=10822 + yarn run build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index a0d3b6d75..9e59a2739 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -1,7 +1,7 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -const UMI_PublicPath = process.env.UMI_PublicPath || './static/front/'; +const UMI_PublicPath = process.env.UMI_PublicPath || './'; const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 0c74ba7e3..f4ab941be 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -51,11 +51,26 @@ export default function SettingAI(props: IProps) { }} value={chatGPTConfig?.aiSqlSource} > + Chat2DB AI Open AI Azure AI {i18n('setting.tab.custom')} + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.OPENAI && ( +

+
Api Key
+
+ { + setChatGPTConfig({ ...chatGPTConfig, apiKey: e.target.value }); + }} + /> +
+
+ )} {chatGPTConfig?.aiSqlSource === AiSqlSourceType.OPENAI && (
Api Key
diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index d73faec03..9a0a22779 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -24,6 +24,7 @@ export default { 'setting.label.customAiUrl': 'User-defined interface Url', 'setting.placeholder.httpsProxy': 'Not required. Set HTTP proxy {1} when requesting OPENAI interface.', 'setting.placeholder.apiKey': 'OpenAI official website to view the APIKEY', + 'setting.placeholder.chat2dbApiKey': '使用Chat2DB提供的APIKEY', 'setting.placeholder.customUrl': 'URL of the REST interface of the AI', 'setting.placeholder.apiHost': 'This parameter is mandatory. The default value is https://api.openai.com/', 'setting.message.urlTestError': 'The interface test failed. Procedure', diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 47196f208..36ecebc18 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -24,6 +24,7 @@ export default { 'setting.label.customAiUrl': '自定义接口Url', 'setting.placeholder.httpsProxy': '非必填,用于设置请求OPENAI接口时的HTTP代理{1}', 'setting.placeholder.apiKey': '使用OpenAi接口时必填,可前往OpenAI官网查看APIKEY', + 'setting.placeholder.chat2dbApiKey': '使用Chat2DB提供的APIKEY', 'setting.placeholder.customUrl': '选择自定义AI时必填,用于设置自定义AI的REST接口URL', 'setting.placeholder.apiHost': '非必填,默认值为 https://api.openai.com/', 'setting.message.urlTestError': '接口测试不通过', From 717dffa4d6d3f3940e2ab4113e479c63623e79af Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 1 Jul 2023 23:25:08 +0800 Subject: [PATCH 0156/1069] fix: fix menu --- chat2db-client/src/main/index.js | 8 +- chat2db-client/src/main/menu.js | 179 ++++++++++++++++++------------- 2 files changed, 105 insertions(+), 82 deletions(-) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index ac5d0c96f..a898a0aa2 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -2,7 +2,8 @@ const { app, BrowserWindow, Menu, shell, net, ipcMain, dialog } = require('elect const path = require('path'); const os = require('os'); const fs = require('fs'); -const menuBar = require('./menu'); +const registerAppMenu = require('./menu'); +const i18n = require('./i18n'); const { loadMainResource } = require('./utils'); let mainWindow = null; @@ -46,6 +47,7 @@ function createWindow() { app.on('ready', () => { createWindow(); + registerAppMenu(); app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) { @@ -82,7 +84,3 @@ ipcMain.handle('get-product-name', (event) => { const { name } = path.parse(exePath); return name; }); - -module.exports = { - mainWindow, -}; diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index 54d049ac2..2fae0bc21 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -1,82 +1,107 @@ const i18n = require('./i18n'); -const { shell } = require('electron'); -const { mainWindow } = require('./index'); - -const menuBar = [ - { - label: 'Chat2DB', - submenu: [ - { - label: '关于Chat2DB', - }, - { type: 'separator' }, - { - label: '刷新', - accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'Ctrl+R', - }, - { - label: '检查更新', - }, - { type: 'separator' }, - { - label: '退出', - }, - ], - }, - { - label: i18n('menu.edit'), - submenu: [ - { label: '撤销', role: 'undo' }, - { label: '重做', role: 'redo' }, - { type: 'separator' }, - { label: '剪切', role: 'cut' }, - { label: '复制', role: 'copy' }, - { label: '粘贴', role: 'paste' }, - { label: '全选', role: 'selectAll' }, - ], - }, - { - label: '帮助', - submenu: [ - { - label: '打开日志', - accelerator: process.platform === 'darwin' ? 'Cmd+Shift+L' : 'Ctrl+Shift+L', - click() { - const fileName = '.chat2db/logs/application.log'; - const url = path.join(os.homedir(), fileName); - shell.openPath(url).then((str) => console.log('err:', str)); +const { shell, app, dialog, BrowserWindow, Menu } = require('electron'); +const os = require('os'); +const path = require('path'); +const registerAppMenu = () => { + const menuBar = [ + { + label: 'Chat2DB', + submenu: [ + { + label: '关于Chat2DB', + click() { + dialog.showMessageBox({ + title: '关于Chat2DB', + message: `关于Chat2DB v${app.getVersion()}`, + detail: + '一款由阿里巴巴开源免费的多数据库客户端工具,支持windows、mac本地安装,也支持服务器端部署,web网页访问。', + icon: './logo/icon.png', + }); + }, + }, + { type: 'separator' }, + { + label: '刷新', + accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'Ctrl+R', + click() { + const focusedWindow = BrowserWindow.getFocusedWindow(); + if (focusedWindow) { + focusedWindow.reload(); + } + }, + }, + // { + // label: '检查更新', + // }, + { type: 'separator' }, + { + label: '退出', + click() { + // 退出程序 + app.quit(); + }, + }, + ], + }, + { + // label: i18n('menu.edit'), + label: '编辑', + submenu: [ + { label: '撤销', role: 'undo' }, + { label: '重做', role: 'redo' }, + { type: 'separator' }, + { label: '剪切', role: 'cut' }, + { label: '复制', role: 'copy' }, + { label: '粘贴', role: 'paste' }, + { label: '全选', role: 'selectAll' }, + ], + }, + { + label: '帮助', + submenu: [ + { + label: '打开日志', + accelerator: process.platform === 'darwin' ? 'Cmd+Shift+L' : 'Ctrl+Shift+L', + click() { + const fileName = '.chat2db/logs/application.log'; + const url = path.join(os.homedir(), fileName); + shell.openPath(url).then((str) => console.log('err:', str)); + }, }, - }, - { - label: '打开控制台', - accelerator: process.platform === 'darwin' ? 'Cmd+Shift+I' : 'Ctrl+Shift+I', - click() { - mainWindow && mainWindow.toggleDevTools(); + { + label: '打开控制台', + accelerator: process.platform === 'darwin' ? 'Cmd+Shift+I' : 'Ctrl+Shift+I', + click() { + const focusedWindow = BrowserWindow.getFocusedWindow(); + focusedWindow && focusedWindow.toggleDevTools(); + }, }, - }, - { - label: '访问官网', - click() { - const url = 'https://chat2db.opensource.alibaba.com/'; - shell.openExternal(url); + { + label: '访问官网', + click() { + const url = 'https://chat2db.opensource.alibaba.com/'; + shell.openExternal(url); + }, }, - }, - // { - // label: '关于', - // role: 'about', // about (关于),此值只针对 Mac OS X 系统 - // // 点击事件 role 属性能识别时 点击事件无效 - // click: () => { - // var aboutWin = new BrowserWindow({ - // width: 300, - // height: 200, - // parent: win, - // modal: true, - // }); - // aboutWin.loadFile('about.html'); - // }, - // }, - ], - }, -]; + // { + // label: '关于', + // role: 'about', // about (关于),此值只针对 Mac OS X 系统 + // // 点击事件 role 属性能识别时 点击事件无效 + // click: () => { + // var aboutWin = new BrowserWindow({ + // width: 300, + // height: 200, + // parent: win, + // modal: true, + // }); + // aboutWin.loadFile('about.html'); + // }, + // }, + ], + }, + ]; + // console.log('registerAppMenu', registerAppMenu); + Menu.setApplicationMenu(Menu.buildFromTemplate(menuBar)); +}; -module.exports = menuBar; +module.exports = registerAppMenu; From 5428ff2846163e3271d96ce029c98ebeb6477af9 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 1 Jul 2023 23:35:11 +0800 Subject: [PATCH 0157/1069] annotation code --- chat2db-client/src/components/Console/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index fc81003b0..632b1e66b 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -108,10 +108,10 @@ function Console(props: IProps) { }, [props.tables]); const handleAiChat = (content: string, promptType: IPromptType) => { - if (!aiModel.remainingUse?.remainingUses) { - popUpPrompts(); - return; - } + // if (!aiModel.remainingUse?.remainingUses) { + // popUpPrompts(); + // return; + // } dispatch({ type: 'ai/fetchRemainingUse', From f62ccc441fa863f8c8f6c6db66c71447c41a7529 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 1 Jul 2023 23:50:36 +0800 Subject: [PATCH 0158/1069] =?UTF-8?q?fix:=E5=88=B7=E6=96=B0=E7=99=BD?= =?UTF-8?q?=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.prod.ts | 4 ++-- chat2db-client/src/pages/main/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 9e59a2739..a3200ad4c 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -1,7 +1,7 @@ import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); -const UMI_PublicPath = process.env.UMI_PublicPath || './'; +// const UMI_PublicPath = process.env.UMI_PublicPath || './static/front/'; const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: UMI_PublicPath, + publicPath: './', chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 17c82004a..9db61c61c 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -74,7 +74,7 @@ function MainPage(props: IProps) { // } // activeNav 变化 同步地址栏变化 // change url,but no page refresh - window.history.pushState({}, "", `/#/${activeNav.key}`); + window.history.pushState({}, "", `${location.href}/#/${activeNav.key}`); }, [activeNav]) useEffect(() => { From e17f250307b3c3c1c20e584df8d7f5c64540bcc7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 00:11:55 +0800 Subject: [PATCH 0159/1069] =?UTF-8?q?fix:=E5=88=B7=E6=96=B0=E7=99=BD?= =?UTF-8?q?=E5=B1=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/pages/main/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 9db61c61c..480411bbf 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -74,7 +74,7 @@ function MainPage(props: IProps) { // } // activeNav 变化 同步地址栏变化 // change url,but no page refresh - window.history.pushState({}, "", `${location.href}/#/${activeNav.key}`); + window.history.pushState({}, "", `${location.pathname}/#/${activeNav.key}`); }, [activeNav]) useEffect(() => { From 61dfc7a84e6f6f757f6e1a8843f488ff6af5047f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 00:30:18 +0800 Subject: [PATCH 0160/1069] =?UTF-8?q?fix:=E5=88=B7=E6=96=B0=E7=99=BD?= =?UTF-8?q?=E5=B1=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/main/index.js | 2 +- chat2db-client/src/pages/main/index.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index a898a0aa2..d68786b8f 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -74,7 +74,7 @@ app.on('before-quit', (event) => { response.on('data', (res) => { let data = JSON.parse(res.toString()); }); - response.on('end', () => {}); + response.on('end', () => { }); }); request.end(); }); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 480411bbf..f9ce09b50 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -11,6 +11,7 @@ import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; import { findObjListValue } from '@/utils'; import TestVersion from '@/components/TestVersion'; +const path = require('path'); import DataSource from './connection'; import Workspace from './workspace'; @@ -61,6 +62,7 @@ function MainPage(props: IProps) { const { curPage } = mainModel; const { curConnection } = connectionModel; const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > -1 ? initPageIndex : 2]); + console.log() useEffect(() => { // activeNav 发生变化,同步到全局状态管理 @@ -74,7 +76,8 @@ function MainPage(props: IProps) { // } // activeNav 变化 同步地址栏变化 // change url,but no page refresh - window.history.pushState({}, "", `${location.pathname}/#/${activeNav.key}`); + window.history.pushState({}, "", path.join(__dirname, `/#/${activeNav.key}`)); + console.log(path.join(__dirname, `/#/1`)) }, [activeNav]) useEffect(() => { From 3da4e1395b338f19c155979f021354d552eabff2 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 09:46:36 +0800 Subject: [PATCH 0161/1069] =?UTF-8?q?fix:=E5=A4=96=E9=83=A8=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=8B=BC=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/AiSetting/index.tsx | 2 +- chat2db-client/src/layouts/index.tsx | 3 +++ chat2db-client/src/pages/main/index.tsx | 4 +--- chat2db-client/src/service/base.ts | 6 ++++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index f4ab941be..864fbe37d 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -57,7 +57,7 @@ export default function SettingAI(props: IProps) { {i18n('setting.tab.custom')}
- {chatGPTConfig?.aiSqlSource === AiSqlSourceType.OPENAI && ( + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DB && (
Api Key
diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index f153c5f3a..f12ff011c 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -139,6 +139,9 @@ function AppContainer() { const time = setInterval(() => { miscService.testService().then(() => { clearInterval(time); + if (__ENV === 'desktop') { + window.location.href = 'http://127.0.0.1:10824/' + } setStartSchedule(2); flag++; }).catch(error => { diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index f9ce09b50..72e0a482e 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -11,7 +11,6 @@ import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; import { findObjListValue } from '@/utils'; import TestVersion from '@/components/TestVersion'; -const path = require('path'); import DataSource from './connection'; import Workspace from './workspace'; @@ -76,8 +75,7 @@ function MainPage(props: IProps) { // } // activeNav 变化 同步地址栏变化 // change url,but no page refresh - window.history.pushState({}, "", path.join(__dirname, `/#/${activeNav.key}`)); - console.log(path.join(__dirname, `/#/1`)) + window.history.pushState({}, "", `/#/${activeNav.key}`); }, [activeNav]) useEffect(() => { diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 4d56eedd1..a01cd0e03 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,6 +1,8 @@ import { extend, ResponseError } from 'umi-request'; import { message, notification } from 'antd'; import { getLang } from '@/utils/localStorage'; +const path = require('path'); + export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; export interface IOptions { @@ -63,7 +65,7 @@ if (appGatewayParams) { window._appGatewayParams = {}; } -const outsideUrlPrefix = window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway/' +const outsideUrlPrefix = window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway/' const errorHandler = (error: ResponseError, errorLevel: IErrorLevel) => { @@ -162,7 +164,7 @@ export default function createRequest

(url: string, options?: I break; } - const eventualUrl = outside ? `${outsideUrlPrefix}${_url}` : `${_baseURL}${_url}`; + const eventualUrl = outside ? path.join(outsideUrlPrefix, _url) : `${_baseURL}${_url}`; request[method](eventualUrl, { [dataName]: params }) .then((res) => { From 5533e4a129ce612ca795d514289118ec815e896b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 10:00:57 +0800 Subject: [PATCH 0162/1069] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=AE=BF=E6=A0=B9=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.desktop.ts | 2 +- chat2db-client/.umirc.prod.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index 716ac6092..6aee70d4d 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: './', + publicPath: './static/front/', chainWebpack, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], define: { diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index a3200ad4c..6ac580b59 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: './', + publicPath: './static/front/', chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, From ab112b3dd156938172bd068c466c56a6c4ea8bc2 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 10:35:59 +0800 Subject: [PATCH 0163/1069] =?UTF-8?q?=E7=99=BD=E5=B1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.desktop.ts | 2 +- chat2db-client/.umirc.prod.ts | 2 +- chat2db-client/src/layouts/index.tsx | 6 +++--- chat2db-client/src/pages/document.ejs | 9 ++++++--- chat2db-client/src/pages/main/index.tsx | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.desktop.ts index 6aee70d4d..716ac6092 100644 --- a/chat2db-client/.umirc.desktop.ts +++ b/chat2db-client/.umirc.desktop.ts @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: './static/front/', + publicPath: './', chainWebpack, headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], define: { diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 6ac580b59..a3200ad4c 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: './static/front/', + publicPath: './', chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index f12ff011c..7e6ecf893 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -139,9 +139,9 @@ function AppContainer() { const time = setInterval(() => { miscService.testService().then(() => { clearInterval(time); - if (__ENV === 'desktop') { - window.location.href = 'http://127.0.0.1:10824/' - } + // if (__ENV === 'desktop') { + // window.location.href = 'http://127.0.0.1:10824/' + // } setStartSchedule(2); flag++; }).catch(error => { diff --git a/chat2db-client/src/pages/document.ejs b/chat2db-client/src/pages/document.ejs index 51f19c403..556032059 100644 --- a/chat2db-client/src/pages/document.ejs +++ b/chat2db-client/src/pages/document.ejs @@ -5,12 +5,15 @@ Chat2DB - - + + - + + diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 72e0a482e..63ccd9969 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -75,7 +75,7 @@ function MainPage(props: IProps) { // } // activeNav 变化 同步地址栏变化 // change url,but no page refresh - window.history.pushState({}, "", `/#/${activeNav.key}`); + // window.history.pushState({}, "", `/#/${activeNav.key}`); }, [activeNav]) useEffect(() => { From 0a7ad5b5a706b697929631b599a97b9a61c255e8 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 10:44:08 +0800 Subject: [PATCH 0164/1069] update readme and changelog --- CHANGELOG.md | 10 ++++++++++ README.md | 30 +++++++++++++++--------------- README_CN.md | 28 +++++++++++++++------------- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2e5e039..fe5329549 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.0.0 +* 🎁 SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 +* 🎁 数据源连接和管理进阶为专注模式的全新体验设计与实现 +* 🎁 AI对话SQL升级为极简模式的全新交互设计与实现 +* 🎁 客户端AI体验重大升级,响应更多用户的诉求 +* 🎁 集成更多AI模型 +* 🎁 客户端双语支持 +* SQL查询基础功能优化 +* Issue问题修复 + # 1.0.11 * fixed: SQL有特殊字符时AI功能无法正常使用 [Issue #291](https://github.com/alibaba/Chat2DB/issues/291) * 增减版本信息检测 diff --git a/README.md b/README.md index 4d7f88794..e1c33bf9a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,9 @@ Languages: English | [中文](README_CN.md) - 🛡 Front-end development using Electron, providing a solution that integrates Windows, Mac, Linux clients, and web versions - 🎁 Support environment isolation, online, and daily data permission separation +## DEMO + + ## ⏬ Download and Install @@ -74,13 +77,13 @@ Languages: English | [中文](README_CN.md) ## 🌰 Demo ### Create data source - + ### Data source management - + ### SQL console - + ### AI intelligent assistant - + ## 🔥 AI Configuration ### CONFIGURE OPENAI @@ -90,7 +93,7 @@ Option 1 (recommended): To use the ChatSql function of OPENAI, two conditions mu - You need an OPENAI_API_KEY. - The client's network can connect to the OPENAI website, and for users in China, a VPN is required. Note: If the local VPN is not fully effective, the network connectivity can be ensured by setting the network proxy HOST and PORT in the client. - + Option 2 (recommended): We provide a unified proxy service. @@ -99,18 +102,15 @@ Option 2 (recommended): We provide a unified proxy service. To facilitate users' quick use of AI capabilities, you can scan the QR code below to follow our WeChat public account and apply for our custom API_KEY. - - -After the application is completed, refer to the following figure for configuration and usage. Config Api Host as http://test.sqlgpt.cn/gateway/api/. + - ### CONFIGURE CUSTOM AI - Customized AI can be any LLM that you deployed, such as ChatGLM、ChatGPT、ERNIE Bot、Tongyi Qianwen, and so on. However, the customized interface need to conform to the protocol definition. Otherwise, secondary development may be required. Two DEMOs are provided in the code, the configuration is as shown below. In specific use, you can refer to the DEMO interface to write a custom interface, or directly perform secondary development in the DEMO interface. - DEMO for configuring customized stream output interface. - + - DEMO for configuring customized non-stream output interface. - + @@ -205,11 +205,11 @@ Thanks to all the students who contributed to Chat2DB~ ## Star History - + - - - Star History Chart + + + Star History Chart diff --git a/README_CN.md b/README_CN.md index d88f2920b..c2b12e8bc 100644 --- a/README_CN.md +++ b/README_CN.md @@ -32,12 +32,16 @@ Languages: 中文 [English](README.md)    Chat2DB 是一款有开源免费的多数据库客户端工具,支持windows、mac本地安装,也支持服务器端部署,web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力,能够将自然语言转换为SQL,也可以将SQL转换为自然语言,可以给出研发人员SQL的优化建议,极大的提升人员的效率,是AI时代数据库研发人员的利器,未来即使不懂SQL的运营业务也可以使用快速查询业务数据、生成报表能力。 ## ✨ 特性 - 🌈 AI智能助手,支持自然语言转SQL、SQL转自然语言、SQL优化建议 +- 🔥 SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 - 👭 支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题 - ⚙️ 强大的数据管理能力,支持数据表、视图、存储过程、函数、触发器、索引、序列、用户、角色、授权等管理 - 🔌 强大的扩展能力,目前已经支持MySQL、PostgreSQL、Oracle、SQLServer、ClickHouse、OceanBase、H2、SQLite等等,未来会支持更多的数据库 - 🛡 前端使用 Electron 开发,提供 Windows、Mac、Linux 客户端、网页版本一体化的解决方案 - 🎁 支持环境隔离、线上、日常数据权限分离 +## 产品展示 + + ## ⏬ 下载安装 | 描述 | 下载地址 | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -72,15 +76,15 @@ Languages: 中文 [English](README.md) | InfluxDB| Planning | ## 🌰 使用Demo ### 创建数据源 - + ### 数据源管理 - + ### SQL控制台 及 AI智能助手 #### 使用前需要配置OpenAI的Api Key及本地代理配置 - - + + ## 🔥 AI配置 @@ -88,8 +92,8 @@ Languages: 中文 [English](README.md) 方式一(推荐):使用OPENAI的ChatSql功能需要满足两个条件 - 1、需要有一个openAI的key:OPENAI_API_KEY - 2、客户端网络可以连接到OPENAI官网,国内需要科学上网。注意:如果本地VPN未能全局生效,可以通过在客户端中设置网络代理HOST和PORT来保证网络连通性 -- - +- + 方式二(推荐):使用我们提供了一个统一的代理服务。 - 1、不需要openAI的key @@ -97,9 +101,7 @@ Languages: 中文 [English](README.md) 为了方便大家更快速的使用AI的能力,可以关注微信公众号,回复"AI" 获得我们的自定义API_KEY,申请完成之后参考下图进行配置即可进行使用 - - - + ## 📦 Docker部署 @@ -224,11 +226,11 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 ## Star History - + - - - Star History Chart + + + Star History Chart From e21418c022c126e7b40dcd596f9d1206039b1183 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 10:54:43 +0800 Subject: [PATCH 0165/1069] add ai sql source --- .../ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java | 5 +++++ .../chat2db/server/web/api/controller/ai/ChatController.java | 2 ++ .../server/web/api/controller/config/ConfigController.java | 1 + 3 files changed, 8 insertions(+) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java index 589527a15..d475f83e7 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java @@ -27,6 +27,11 @@ public enum AiSqlSourceEnum implements BaseEnum { */ AZUREAI("AZURE OPENAI"), + /** + * CHAT2DB OPENAI + */ + CHAT2DBAI("CHAT2DB OPENAI"), + ; final String description; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index e9fc3dd58..10ec09bf5 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -223,6 +223,7 @@ private SseEmitter distributeAI(String msg, SseEmitter sseEmitter, String uid) t AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : + case CHAT2DBAI: return chatWithOpenAi(msg, sseEmitter, uid); case RESTAI : return chatWithRestAi(msg, sseEmitter); @@ -241,6 +242,7 @@ private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sse AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : + case CHAT2DBAI: return chatWithOpenAiSql(queryRequest, sseEmitter, uid); case RESTAI : return chatWithRestAi(queryRequest.getMessage(), sseEmitter); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index 473e73921..8e732f30c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -68,6 +68,7 @@ public ActionResult addChatGptSystemConfig(@RequestBody AISystemConfigRequest re AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : + case CHAT2DBAI: saveOpenAIConfig(request); case RESTAI : saveRestAIConfig(request); From 520dab408efb89f58b136a986e5f9ab310a9fbdf Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 11:07:18 +0800 Subject: [PATCH 0166/1069] fix password issue --- ...235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" index fb02bd85c..1cc536278 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" @@ -1,2 +1,2 @@ INSERT INTO DATA_SOURCE (GMT_CREATE, GMT_MODIFIED, ALIAS, URL, USER_NAME, PASSWORD, TYPE, USER_ID, HOST, PORT, SSH,JDBC) -VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'DEMO@db.sqlgpt.cn', 'jdbc:mysql://db.sqlgpt.cn:3306/DEMO', 'demo', 'demo', 'MYSQL', 0, 'db.sqlgpt.cn', '3306', '{"use":false}', '8.0'); +VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'DEMO@db.sqlgpt.cn', 'jdbc:mysql://db.sqlgpt.cn:3306/DEMO', 'demo', 'kok39AYoOSM=', 'MYSQL', 0, 'db.sqlgpt.cn', '3306', '{"use":false}', '8.0'); From 53082f45f09d7861f5f4126f882b1e368a10736e Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 11:23:33 +0800 Subject: [PATCH 0167/1069] bugfix --- .../ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java | 2 +- .../web/api/controller/rdb/request/TableBriefQueryRequest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index 0871a2885..472538d17 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -60,7 +60,7 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); - if (!result.success() && dataSource == null) { + if (!result.success() || dataSource == null) { throw new ParamBusinessException("dataSourceId"); } ConnectInfo connectInfo = new ConnectInfo(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java index 2c9ea3fc5..e8ba850d0 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java @@ -27,7 +27,6 @@ public class TableBriefQueryRequest extends PageQueryRequest implements DataSour /** * DB名称 */ - @NotNull private String databaseName; /** From bfa5c2a493a0e57aaf21fe881ac54b79251e3c1a Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 11:33:30 +0800 Subject: [PATCH 0168/1069] bugfix --- .../server/web/api/controller/ai/ChatController.java | 6 ++++++ .../server/web/api/controller/config/ConfigController.java | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 10ec09bf5..f19968b21 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -221,6 +221,9 @@ private SseEmitter distributeAI(String msg, SseEmitter sseEmitter, String uid) t ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); + if (Objects.isNull(aiSqlSourceEnum)) { + aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; + } switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : case CHAT2DBAI: @@ -240,6 +243,9 @@ private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sse ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); + if (Objects.isNull(aiSqlSourceEnum)) { + aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; + } switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : case CHAT2DBAI: diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index 8e732f30c..c4d699c6f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -62,10 +62,15 @@ public ActionResult systemConfig(@RequestBody SystemConfigRequest request) { public ActionResult addChatGptSystemConfig(@RequestBody AISystemConfigRequest request) { String sqlSource = StringUtils.isNotBlank(request.getAiSqlSource()) ? request.getAiSqlSource() : AiSqlSourceEnum.OPENAI.getCode(); + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); + if (Objects.isNull(aiSqlSourceEnum)) { + aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; + sqlSource = AiSqlSourceEnum.OPENAI.getCode(); + } SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource) .build(); configService.createOrUpdate(param); - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); + switch (Objects.requireNonNull(aiSqlSourceEnum)) { case OPENAI : case CHAT2DBAI: From 338d78365172f540a2970295d8285c5636b5c21f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 12:12:11 +0800 Subject: [PATCH 0169/1069] fix: some bug --- .../src/blocks/Setting/AiSetting/index.tsx | 7 +- .../CreateConnection/config/dataSource.ts | 1960 +++++++++++++++++ chat2db-client/src/constants/common.ts | 4 + chat2db-client/src/constants/database.ts | 35 + chat2db-client/src/i18n/en-us/setting.ts | 2 +- .../main/workspace/components/Tree/index.tsx | 2 +- chat2db-client/src/service/ai.ts | 2 +- chat2db-client/src/typings/ai.ts | 3 +- 8 files changed, 2009 insertions(+), 6 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 864fbe37d..eead36e7d 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -30,6 +30,9 @@ export default function SettingAI(props: IProps) { if (newChatGPTConfig.apiHost && !newChatGPTConfig.apiHost?.endsWith('/')) { newChatGPTConfig.apiHost = newChatGPTConfig.apiHost + '/'; } + if (chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { + newChatGPTConfig.apiHost = `${window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway/'}api/` + } configService.setChatGptSystemConfig(newChatGPTConfig).then((res) => { message.success(i18n('common.text.submittedSuccessfully')); }); @@ -51,13 +54,13 @@ export default function SettingAI(props: IProps) { }} value={chatGPTConfig?.aiSqlSource} > - Chat2DB AI + Chat2DB AI Open AI Azure AI {i18n('setting.tab.custom')}

- {chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DB && ( + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && (
Api Key
diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index 4765073ba..c4dbc2d87 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -1707,4 +1707,1964 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], type: DatabaseTypeCode.DM }, + //DB2 + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '50000', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:db2://localhost:50000', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:db2:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:db2://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.DB2 + }, + //presto + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '8080', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:presto://localhost:8080', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:presto:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:presto://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.PRESTO + }, + //oceanbase + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '2883', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:oceanbase://localhost:2883', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:oceanbase:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:oceanbase://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.OCEANBASE + }, + //redis + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '6379', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:redis://localhost:6379', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:redis://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.REDIS + }, + //redis + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '6379', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:redis://localhost:6379', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:redis://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.REDIS + }, + //hive + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '10000', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:hive2://localhost:10000', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:hive2://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.HIVE + }, + //hive + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '10000', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:hive2://localhost:10000', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:hive2://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.HIVE + }, + //hive + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '10000', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:hive2://localhost:10000', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:hive2://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.HIVE + }, + //KINGBASE + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '54321', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:kingbase8://127.0.0.1:54321', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:kingbase8:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:kingbase8://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.KINGBASE + }, + //MONGODB + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '27017', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authentication', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'mongodb://localhost:27017', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'mongodb://{host}:{port}/{database}', + }, + ssh: { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '28%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ] + }, + extendInfo: [ + + ], + type: DatabaseTypeCode.MONGODB + }, ]; diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 7b1ff0552..00845fd46 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -11,6 +11,10 @@ export enum DatabaseTypeCode { MARIADB = 'MARIADB', CLICKHOUSE = 'CLICKHOUSE', DM = "DM", + OCEANBASE = "OCEANBASE", + PRESTO = "PRESTO", + HIVE = "HIVE", + KINGBASE = "KINGBASE", } export enum ConsoleOpenedStatus { diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index 7e6e2369b..af0170b8d 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -76,6 +76,41 @@ export const databaseMap: { // port: 5236, icon: '\ue655', }, + [DatabaseTypeCode.OCEANBASE]: { + name: 'OceanBase', + img: moreDBLogo, + code: DatabaseTypeCode.OCEANBASE, + // port: 2883, + icon: '\ue982', + }, + [DatabaseTypeCode.REDIS]: { + name: 'Redis', + img: moreDBLogo, + code: DatabaseTypeCode.REDIS, + // port: 6379, + icon: '\ue6a2', + }, + [DatabaseTypeCode.HIVE]: { + name: 'Hive', + img: moreDBLogo, + code: DatabaseTypeCode.HIVE, + // port: 10000, + icon: '\ue60e', + }, + [DatabaseTypeCode.KINGBASE]: { + name: 'KingBase', + img: moreDBLogo, + code: DatabaseTypeCode.KINGBASE, + // port: 54321, + icon: '\ue6a0', + }, + [DatabaseTypeCode.MONGODB]: { + name: 'MongoDB', + img: moreDBLogo, + code: DatabaseTypeCode.MONGODB, + // port: 27017, + icon: '\uec21', + }, }; export const databaseTypeList = Object.keys(databaseMap).map((keys) => { diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 9a0a22779..e81618b68 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -24,7 +24,7 @@ export default { 'setting.label.customAiUrl': 'User-defined interface Url', 'setting.placeholder.httpsProxy': 'Not required. Set HTTP proxy {1} when requesting OPENAI interface.', 'setting.placeholder.apiKey': 'OpenAI official website to view the APIKEY', - 'setting.placeholder.chat2dbApiKey': '使用Chat2DB提供的APIKEY', + 'setting.placeholder.chat2dbApiKey': 'Use the APIKEY provided by Chat2DB', 'setting.placeholder.customUrl': 'URL of the REST interface of the AI', 'setting.placeholder.apiHost': 'This parameter is mandatory. The default value is https://api.openai.com/', 'setting.message.urlTestError': 'The interface test failed. Procedure', diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index bb145091b..48c573122 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -164,7 +164,7 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { } function nodeDoubleClick() { - if (data.treeNodeType === TreeNodeType.TABLE || data.treeNodeType === TreeNodeType.COLUMN) { + if (data.treeNodeType === TreeNodeType.TABLE) { dispatch({ type: 'workspace/setDoubleClickTreeNodeData', payload: data diff --git a/chat2db-client/src/service/ai.ts b/chat2db-client/src/service/ai.ts index 165606cb2..dc370e887 100644 --- a/chat2db-client/src/service/ai.ts +++ b/chat2db-client/src/service/ai.ts @@ -2,6 +2,6 @@ import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; import { IRemainingUse } from '@/typings/ai'; import createRequest from './base'; -const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/:key', { outside: true }); +const getRemainingUse = createRequest<{ key: string }, IRemainingUse>('/client/remaininguses/:key', { errorLevel: false, outside: true }); export default { getRemainingUse }; diff --git a/chat2db-client/src/typings/ai.ts b/chat2db-client/src/typings/ai.ts index 6c01e5187..dd58510fb 100644 --- a/chat2db-client/src/typings/ai.ts +++ b/chat2db-client/src/typings/ai.ts @@ -1,5 +1,6 @@ export enum AiSqlSourceType { - CHAT2DB='CHAT2DB', + CHAT2DB = 'CHAT2DB', + CHAT2DBAI = 'CHAT2DBAI', OPENAI = 'OPENAI', AZUREAI = 'AZUREAI', RESTAI = 'RESTAI', From 58dd471e3a7bb5b1a1a1b016d9cb3465ca1dcc3a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 12:18:47 +0800 Subject: [PATCH 0170/1069] =?UTF-8?q?path=E6=8B=BC=E6=8E=A5=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/blocks/Setting/AiSetting/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index eead36e7d..2013c689e 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -6,6 +6,7 @@ import i18n from '@/i18n'; import classnames from 'classnames'; import { IAIState } from '@/models/ai'; import styles from './index.less'; +const path = require('path'); interface IProps { handleUpdateAiConfig: (payload: IAIState['keyAndAiType']) => void; @@ -31,7 +32,7 @@ export default function SettingAI(props: IProps) { newChatGPTConfig.apiHost = newChatGPTConfig.apiHost + '/'; } if (chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { - newChatGPTConfig.apiHost = `${window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway/'}api/` + newChatGPTConfig.apiHost = path.join(window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway/', 'api/') } configService.setChatGptSystemConfig(newChatGPTConfig).then((res) => { message.success(i18n('common.text.submittedSuccessfully')); From 24f004bd47479866cfd0fa0da717676f83ba6205 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 2 Jul 2023 12:38:31 +0800 Subject: [PATCH 0171/1069] fix conflict --- .vscode/settings.json | 1 + chat2db-client/package.json | 8 +- chat2db-client/src/main/index.js | 10 +- chat2db-client/src/main/menu.js | 31 +- chat2db-client/src/pages/main/index.tsx | 62 +- chat2db-client/src/service/base.ts | 8 +- chat2db-client/yarn.lock | 872 +++++++++++++----------- 7 files changed, 524 insertions(+), 468 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 6ca277f4a..8e235023f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "RESTAI", "Sercurity", "sortablejs", + "togglefullscreen", "wechat", "wireframe" ] diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 78c7a2c3c..29b71462c 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -2,18 +2,18 @@ "name": "chat2db", "version": "1.0.0", "private": true, - "author": "fjy, hexi", - "main": "src/main/index.js", "repository": { "type": "git", "url": "https://github.com/chat2db/Chat2DB" }, + "author": "fjy, hexi", + "main": "src/main/index.js", "scripts": { "build": "npm run build:web && npm run build:main", - "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:desktop": "npm run build:web:desktop && npm run build:main:prod", "build:main": "cross-env NODE_ENV=development electron-builder", "build:main:prod": "cross-env NODE_ENV=production electron-builder", + "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:web": "umi build", "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", @@ -53,7 +53,7 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", - "electron": "^25.0.1", + "electron": "20.1.4", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index d68786b8f..a6c332927 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -5,6 +5,7 @@ const fs = require('fs'); const registerAppMenu = require('./menu'); const i18n = require('./i18n'); const { loadMainResource } = require('./utils'); + let mainWindow = null; function createWindow() { @@ -36,10 +37,6 @@ function createWindow() { event.preventDefault(); shell.openExternal(url); }); - - mainWindow.on('closed', () => { - mainWindow = null; - }); } // const menu = Menu.buildFromTemplate(menuBar); @@ -50,11 +47,12 @@ app.on('ready', () => { registerAppMenu(); app.on('activate', function () { - if (BrowserWindow.getAllWindows().length === 0) { + if (mainWindow === null) { createWindow(); } }); }); + app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); @@ -74,7 +72,7 @@ app.on('before-quit', (event) => { response.on('data', (res) => { let data = JSON.parse(res.toString()); }); - response.on('end', () => { }); + response.on('end', () => {}); }); request.end(); }); diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index 2fae0bc21..6f4a7b8a3 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -19,23 +19,13 @@ const registerAppMenu = () => { }); }, }, - { type: 'separator' }, - { - label: '刷新', - accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'Ctrl+R', - click() { - const focusedWindow = BrowserWindow.getFocusedWindow(); - if (focusedWindow) { - focusedWindow.reload(); - } - }, - }, // { // label: '检查更新', // }, { type: 'separator' }, { label: '退出', + accelerator: process.platform === 'darwin' ? 'Cmd+Q' : 'Ctrl+ALT+F4', click() { // 退出程序 app.quit(); @@ -56,6 +46,25 @@ const registerAppMenu = () => { { label: '全选', role: 'selectAll' }, ], }, + { + // label: i18n('menu.edit'), + label: '视图', + submenu: [ + { + label: '刷新', + accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'Ctrl+R', + click() { + const focusedWindow = BrowserWindow.getFocusedWindow(); + if (focusedWindow) { + focusedWindow.reload(); + } + }, + }, + { type: 'separator' }, + { label: '全屏', role: 'togglefullscreen' }, + ], + }, + { label: '帮助', submenu: [ diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 63ccd9969..87683f9cc 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -11,7 +11,6 @@ import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; import { findObjListValue } from '@/utils'; import TestVersion from '@/components/TestVersion'; - import DataSource from './connection'; import Workspace from './workspace'; import Dashboard from './dashboard'; @@ -20,7 +19,6 @@ import Chat from './chat'; import styles from './index.less'; import { INavItem } from '@/typings/main'; const navConfig: INavItem[] = [ - { key: 'workspace', icon: '\ue616', @@ -43,11 +41,11 @@ const navConfig: INavItem[] = [ key: 'github', icon: '\ue885', iconFontSize: 26, - openBrowser: 'https://github.com/alibaba/Chat2DB', + openBrowser: 'https://github.com/chat2db/Chat2DB/', }, ]; -const initPageIndex = navConfig.findIndex(t => `#/${t.key}` === window.location.hash); +const initPageIndex = navConfig.findIndex((t) => `#/${t.key}` === window.location.hash); interface IProps { mainModel: IMainPageType['state']; @@ -61,14 +59,14 @@ function MainPage(props: IProps) { const { curPage } = mainModel; const { curConnection } = connectionModel; const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > -1 ? initPageIndex : 2]); - console.log() + console.log(); useEffect(() => { // activeNav 发生变化,同步到全局状态管理 dispatch({ type: 'mainPage/updateCurPage', - payload: activeNav.key - }) + payload: activeNav.key, + }); // activeNav 发生变化 如果没有选中连接并且不在connections 那么需要跳转到 连接页面 // if (!curConnection?.id && activeNav.key !== 'connections') { // setActiveNav(navConfig[2]); @@ -76,30 +74,32 @@ function MainPage(props: IProps) { // activeNav 变化 同步地址栏变化 // change url,but no page refresh // window.history.pushState({}, "", `/#/${activeNav.key}`); - }, [activeNav]) + }, [activeNav]); useEffect(() => { // 全局状态curPage发生变化,activeNav 需要同步变化 if (curPage && curPage !== activeNav.key) { - const newActiveNav = navConfig[findObjListValue(navConfig, 'key', curPage)] - setActiveNav(newActiveNav) + const newActiveNav = navConfig[findObjListValue(navConfig, 'key', curPage)]; + setActiveNav(newActiveNav); } - }, [curPage]) + }, [curPage]); useEffect(() => { if (curConnection?.id) { dispatch({ type: 'workspace/fetchDatabaseAndSchema', payload: { - dataSourceId: curConnection.id - } - }) + dataSourceId: curConnection.id, + }, + }); } - }, [curConnection]) + }, [curConnection]); function switchingNav(item: INavItem) { if (item.openBrowser) { - window.open(item.openBrowser); + window.open(item.openBrowser, '_blank'); + // shell.openExternal(item.openBrowser); + console.log('new-window========>', item.openBrowser); } else { setActiveNav(item); } @@ -108,7 +108,7 @@ function MainPage(props: IProps) { return (
- { }} className={styles.brandLogo} /> + {}} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( @@ -117,7 +117,7 @@ function MainPage(props: IProps) { className={classnames({ [styles.activeNav]: item.key == activeNav.key, })} - onClick={switchingNav.bind(null, item)} + onClick={() => switchingNav(item)} > {/*
    {item.title}
    */} @@ -132,11 +132,7 @@ function MainPage(props: IProps) {
    {navConfig.map((item) => { return ( - + {chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && ( + + ) + } ); } diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index b0845aca2..1417d2e5f 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -37,6 +37,9 @@ function Setting(props: IProps) { const [currentMenu, setCurrentMenu] = useState(0); useEffect(() => { configService.getChatGptSystemConfig().then((res: IChatGPTConfig) => { + if (!res) { + return + } handleUpdateAiConfig({ key: res.apiKey, aiType: res.aiSqlSource, @@ -44,7 +47,7 @@ function Setting(props: IProps) { setChatGPTConfig({ ...res, restAiStream: res.restAiStream || true, - aiSqlSource: res.aiSqlSource || AiSqlSourceType.OPENAI, + aiSqlSource: res.aiSqlSource || AiSqlSourceType.CHAT2DBAI, }); }); }, []); @@ -106,8 +109,8 @@ function Setting(props: IProps) { {text ? ( {text} ) : ( - - )} + + )}
    (); + const [modal, contextHolder] = Modal.useModal(); + const [popularizeModal, setPopularizeModal] = useState(false) useEffect(() => { if (appendValue) { @@ -238,10 +241,7 @@ function Console(props: IProps) { ); const popUpPrompts = () => { - Modal.info({ - title: i18n('chat.input.remain.dialog.tips'), - content: , - }); + setPopularizeModal(true) }; return (
    @@ -272,7 +272,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> @@ -304,6 +304,14 @@ function Console(props: IProps) { {i18n('common.button.format')}
    + setPopularizeModal(false)} + > + + +
); } diff --git a/chat2db-client/src/components/Popularize/index.less b/chat2db-client/src/components/Popularize/index.less new file mode 100644 index 000000000..7197329e3 --- /dev/null +++ b/chat2db-client/src/components/Popularize/index.less @@ -0,0 +1,32 @@ +@import '../../styles/var.less'; + +.box { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; +} + +.title{ + font-size: 16px; + font-weight: bold; + text-align: center; +} + +.text{ + p{ + text-align: center; + font-size: 14px; + margin-top: 10px; + } +} + + +.wechatImg{ + margin: 20px auto 0px; + width: 160px; + height: 160px; + background-image: url('https://oss-chat2db.alibaba.com/static/wechat.webp'); + background-size: cover; + background-repeat: no-repeat; +} \ No newline at end of file diff --git a/chat2db-client/src/components/Popularize/index.tsx b/chat2db-client/src/components/Popularize/index.tsx new file mode 100644 index 000000000..a6960aa88 --- /dev/null +++ b/chat2db-client/src/components/Popularize/index.tsx @@ -0,0 +1,20 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import i18n from '@/i18n'; + +interface IProps { + className?: string; +} + +export default memo(function Popularize(props) { + const { className } = props + return
+ {/*
获取更多次数
*/} +
+
+

{i18n('common.text.wechatPopularizeAi')}

+

{i18n('common.text.wechatPopularize')}

+
+
+}) diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 7df3890dc..6e7b775c7 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -55,4 +55,6 @@ export default { 'common.text.setting': 'Setting', 'common.text.tryToRestart': 'Try To Restart', 'common.text.contactUs': 'Contact Us', + 'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.', + 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index fba0602a2..da9e2472b 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -1,5 +1,4 @@ export default { 'chat.input.remain': '剩余 {1} 次', 'chat.input.remain.tooltip': '选中表的schema将会被添加到prompt中', - 'chat.input.remain.dialog.tips': '关注公众号,发送“推广”获取更多体验次数', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index ac1816758..f0f3fcb48 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -55,5 +55,6 @@ export default { 'common.text.setting': '设置', 'common.text.tryToRestart': '尝试重新启动', 'common.text.contactUs': '联系我们', - + 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', + 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', }; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 96f918d64..acde390aa 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -162,7 +162,7 @@ export default function createRequest

(url: string, options?: I break; } - const eventualUrl = outside ? path.join(outsideUrlPrefix, _url) : `${_baseURL}${_url}`; + const eventualUrl = outside ? `${outsideUrlPrefix}${_url}` : `${_baseURL}${_url}`; request[method](eventualUrl, { [dataName]: params }) .then((res) => { From 47d12dfe76e902a4ee5fa6d3da14db1acfd87484 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 16:20:13 +0800 Subject: [PATCH 0176/1069] update readme --- README.md | 2 +- README_CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e1c33bf9a..953a93d94 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Languages: English | [中文](README_CN.md) - 🎁 Support environment isolation, online, and daily data permission separation ## DEMO - + ## ⏬ Download and Install diff --git a/README_CN.md b/README_CN.md index c2b12e8bc..99c58ce78 100644 --- a/README_CN.md +++ b/README_CN.md @@ -40,7 +40,7 @@ Languages: 中文 [English](README.md) - 🎁 支持环境隔离、线上、日常数据权限分离 ## 产品展示 - + ## ⏬ 下载安装 | 描述 | 下载地址 | From 6da60f05b6a3c77fea84b83ae030246ea3089a0c Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 16:36:40 +0800 Subject: [PATCH 0177/1069] Update README.md add demo --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 953a93d94..020eb6216 100644 --- a/README.md +++ b/README.md @@ -213,3 +213,6 @@ Thanks to all the students who contributed to Chat2DB~ + + + From e12e3eaa37ba3a8ecf6d73d84114dfab717a98e9 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 16:46:10 +0800 Subject: [PATCH 0178/1069] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E4=B8=80=E4=B8=AA=E9=94=99=E8=AF=AF=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E5=90=8E=20=E5=86=8D=E4=B9=9F=E6=97=A0=E6=B3=95=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E8=BF=9E=E6=8E=A5=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/blocks/Setting/About/index.tsx | 2 +- .../src/blocks/Setting/AiSetting/index.tsx | 4 +- .../src/components/Popularize/index.tsx | 7 ++- .../src/components/TestVersion/index.tsx | 4 +- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/zh-cn/common.ts | 2 + chat2db-client/src/i18n/zh-cn/connection.ts | 2 +- chat2db-client/src/i18n/zh-cn/dashboard.ts | 2 +- chat2db-client/src/models/connection.ts | 19 +++--- chat2db-client/src/models/workspace.ts | 63 ++++++++++++------- chat2db-client/src/pages/main/index.tsx | 16 ++++- chat2db-client/src/service/connection.ts | 2 +- 12 files changed, 81 insertions(+), 43 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index a7db72382..8cf4ddf20 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -15,7 +15,7 @@ export default function AboutUs() { {i18n('setting.text.currentEnv')}:{__ENV}

- {i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build + {i18n('setting.text.currentVersion')}:v{'2.0.0' || __APP_VERSION__} build {__BUILD_TIME__}
diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 8bfe46776..d7021c6b9 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -33,7 +33,7 @@ export default function SettingAI(props: IProps) { newChatGPTConfig.apiHost = newChatGPTConfig.apiHost + '/'; } if (chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { - newChatGPTConfig.apiHost = `${window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway'}${'/api/'}` + newChatGPTConfig.apiHost = `${window._appGatewayParams.baseUrl || 'http://test.sqlgpt.cn/gateway'}${'/model/'}` } configService.setChatGptSystemConfig(newChatGPTConfig).then((res) => { message.success(i18n('common.text.submittedSuccessfully')); @@ -201,7 +201,7 @@ export default function SettingAI(props: IProps) {
{chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && ( - + ) } diff --git a/chat2db-client/src/components/Popularize/index.tsx b/chat2db-client/src/components/Popularize/index.tsx index a6960aa88..1f7d25563 100644 --- a/chat2db-client/src/components/Popularize/index.tsx +++ b/chat2db-client/src/components/Popularize/index.tsx @@ -5,15 +5,18 @@ import i18n from '@/i18n'; interface IProps { className?: string; + source?: 'setting' } export default memo(function Popularize(props) { - const { className } = props + const { className, source } = props return
{/*
获取更多次数
*/}
-

{i18n('common.text.wechatPopularizeAi')}

+ { + source === 'setting' ?

{i18n('common.text.wechatPopularizeAi2')}

:

{i18n('common.text.wechatPopularizeAi')}

+ }

{i18n('common.text.wechatPopularize')}

diff --git a/chat2db-client/src/components/TestVersion/index.tsx b/chat2db-client/src/components/TestVersion/index.tsx index 26f520124..e98e5e12d 100644 --- a/chat2db-client/src/components/TestVersion/index.tsx +++ b/chat2db-client/src/components/TestVersion/index.tsx @@ -52,7 +52,7 @@ export default memo(function TestVersion(props) { const openNotification = (responseText: any) => { console.log(responseText) try { - if (responseText.version !== '1.1.0') { + if (responseText.version !== '2.0.0') { const key = `open${Date.now()}`; const btn = ( @@ -66,7 +66,7 @@ export default memo(function TestVersion(props) { ); notificationApi.open({ message: i18n('common.text.updateReminder'), - description: `${'common.text.detectionLatestVersion'} v${responseText.version}`, + description: `${i18n('common.text.detectionLatestVersion')} v${responseText.version}`, btn, key, onClose: close, diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 6e7b775c7..363502e3a 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -56,5 +56,6 @@ export default { 'common.text.tryToRestart': 'Try To Restart', 'common.text.contactUs': 'Contact Us', 'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.', + 'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index f0f3fcb48..2bc3e69d2 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -56,5 +56,7 @@ export default { 'common.text.tryToRestart': '尝试重新启动', 'common.text.contactUs': '联系我们', 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', + 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', + }; diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 8ccd75a2c..94276f73a 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -1,5 +1,5 @@ export default { - 'connection.title.connections': '连接池', + 'connection.title.connections': '连接', 'connection.title.createConnection': '创建数据源', 'connection.title.editConnection': '修改数据源', 'connection.label.name': '名称', diff --git a/chat2db-client/src/i18n/zh-cn/dashboard.ts b/chat2db-client/src/i18n/zh-cn/dashboard.ts index 8a3af1890..b841606a0 100644 --- a/chat2db-client/src/i18n/zh-cn/dashboard.ts +++ b/chat2db-client/src/i18n/zh-cn/dashboard.ts @@ -5,5 +5,5 @@ export default { 'dashboard.modal.addTitle': '新增仪表盘', 'dashboard.modal.name.placeholder': "请输入仪表盘名", 'dashboard.delete': '删除', - 'dashboard.editor.cascader.placeholder': '请选择连接池', + 'dashboard.editor.cascader.placeholder': '请选择连接', }; diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index 1302ebcda..013b7503b 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -16,7 +16,7 @@ export interface IConnectionModelType { namespace: 'connection'; state: IConnectionModelState; reducers: { - // 设置连接池列表 + // 设置连接列表 setConnectionList: Reducer; setCurConnection: Reducer; }; @@ -32,7 +32,7 @@ const ConnectionModel: IConnectionModelType = { connectionList: [], }, reducers: { - // 设置连接池列表 + // 设置连接列表 setConnectionList(state, { payload }) { return { ...state, @@ -49,11 +49,16 @@ const ConnectionModel: IConnectionModelType = { effects: { *fetchConnectionList(_, { call, put }) { - const res = (yield connectionService.getList({ pageNo: 1, pageSize: 999 })) as IPageResponse; - yield put({ - type: 'setConnectionList', - payload: res.data, - }); + try { + const res = (yield connectionService.getList({ pageNo: 1, pageSize: 999 })) as IPageResponse; + yield put({ + type: 'setConnectionList', + payload: res.data, + }); + } + catch { + + } }, }, }; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 3548eb275..acfe5d8c2 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -104,36 +104,51 @@ const WorkspaceModel: IWorkspaceModelType = { effects: { *fetchDatabaseAndSchema({ payload }, { put }) { - const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; - yield put({ - type: 'setDatabaseAndSchema', - payload: res, - }); + try { + const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; + yield put({ + type: 'setDatabaseAndSchema', + payload: res, + }); + } + catch { + + } + }, *fetchGetSavedConsole({ payload, callback }, { put }) { - const res = (yield historyService.getSavedConsoleList({ - pageNo: 1, - pageSize: 999, - ...payload - })) as IPageResponse; - if (callback && typeof callback === 'function') { - callback(res); + try { + const res = (yield historyService.getSavedConsoleList({ + pageNo: 1, + pageSize: 999, + ...payload + })) as IPageResponse; + if (callback && typeof callback === 'function') { + callback(res); + } + } + catch { } }, *fetchGetCurTableList({ payload, callback }, { put, call }) { - const res = (yield treeConfig[TreeNodeType.TABLES].getChildren!({ - pageNo: 1, - pageSize: 999, - ...payload, - })) as ITreeNode[]; - // 异步操作完成后调用回调函数 - if (callback && typeof callback === 'function') { - callback(res); + try { + const res = (yield treeConfig[TreeNodeType.TABLES].getChildren!({ + pageNo: 1, + pageSize: 999, + ...payload, + })) as ITreeNode[]; + // 异步操作完成后调用回调函数 + if (callback && typeof callback === 'function') { + callback(res); + } + yield put({ + type: 'setCurTableList', + payload: res, + }); + } + catch { + } - yield put({ - type: 'setCurTableList', - payload: res, - }); }, }, }; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 87683f9cc..c354071a9 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState, PropsWithChildren } from 'react'; import i18n from '@/i18n'; -import { Button } from 'antd'; +import { Button, message } from 'antd'; import { history, connect } from 'umi'; import classnames from 'classnames'; import Setting from '@/blocks/Setting'; @@ -15,6 +15,7 @@ import DataSource from './connection'; import Workspace from './workspace'; import Dashboard from './dashboard'; import Chat from './chat'; +import sqlService, { MetaSchemaVO } from '@/service/sql'; import styles from './index.less'; import { INavItem } from '@/typings/main'; @@ -86,6 +87,17 @@ function MainPage(props: IProps) { useEffect(() => { if (curConnection?.id) { + // sqlService.getDatabaseSchemaList({ dataSourceId: curConnection.id }).then(res => [ + // dispatch({ + // type: 'workspace/setDatabaseAndSchema', + // payload: res, + // }) + // ]).catch(() => { + // dispatch({ + // type: 'workspace/setDatabaseAndSchema', + // payload: {}, + // }) + // }) dispatch({ type: 'workspace/fetchDatabaseAndSchema', payload: { @@ -108,7 +120,7 @@ function MainPage(props: IProps) { return (
- {}} className={styles.brandLogo} /> + { }} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index 8d69742f1..b3b9ca2c2 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -9,7 +9,7 @@ export interface IGetConnectionParams { } /** - * 查询连接池列表 + * 查询连接列表 */ const getList = createRequest< IGetConnectionParams, From d266830acc4566ddf4a9a04582f3477c4d4f3d71 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 17:01:44 +0800 Subject: [PATCH 0179/1069] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 020eb6216..c3dceb3bb 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,10 @@ Languages: English | [中文](README_CN.md) - 🎁 Support environment isolation, online, and daily data permission separation ## DEMO - + + +https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f + ## ⏬ Download and Install From 77daeeef76df0cb53196b1c8ee01b2cd0b826847 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 17:03:07 +0800 Subject: [PATCH 0180/1069] add demo --- ...5\214\226demo\344\277\241\346\201\257.sql" | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" index 1cc536278..5dcff8870 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_6__\345\210\235\345\247\213\345\214\226demo\344\277\241\346\201\257.sql" @@ -1,2 +1,41 @@ INSERT INTO DATA_SOURCE (GMT_CREATE, GMT_MODIFIED, ALIAS, URL, USER_NAME, PASSWORD, TYPE, USER_ID, HOST, PORT, SSH,JDBC) VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 'DEMO@db.sqlgpt.cn', 'jdbc:mysql://db.sqlgpt.cn:3306/DEMO', 'demo', 'kok39AYoOSM=', 'MYSQL', 0, 'db.sqlgpt.cn', '3306', '{"use":false}', '8.0'); + +INSERT INTO DASHBOARD (ID, GMT_CREATE, GMT_MODIFIED, NAME, DESCRIPTION, SCHEMA, DELETED, USER_ID) +VALUES (1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '学生成绩分析', '学生成绩分析', '[[1],[2],[3]]', 'N', 0); + +INSERT INTO CHART (ID, GMT_CREATE, GMT_MODIFIED, SCHEMA, DATA_SOURCE_ID, DATABASE_NAME, DDL, DELETED, USER_ID) +VALUES (1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '{"chartType":"Column","xAxis":"name","yAxis":"total_score"}', 1, 'DEMO', 'SELECT s.name, sc.chinese_score, sc.math_score, sc.english_score, sc.science_score, sc.humanities_score, +(sc.chinese_score + sc.math_score + sc.english_score + sc.science_score + sc.humanities_score) AS total_score +FROM student s +JOIN score sc ON s.id = sc.student_id', 'N', 0); + +INSERT INTO CHART (ID, GMT_CREATE, GMT_MODIFIED, SCHEMA, DATA_SOURCE_ID, DATABASE_NAME, DDL, DELETED, USER_ID) +VALUES (2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '{"chartType":"Pie","xAxis":"grade"}', 1, 'DEMO', 'SELECT s.name, + score.chinese_score, + score.math_score, + score.english_score, + score.science_score, + score.humanities_score, + (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) AS total_score, + CASE + WHEN (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) < 630 THEN "D" + WHEN (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) >= 630 AND (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) <= 735 THEN "C" + WHEN (score.chinese_score + score.math_score + score.english_score + score.science_score + score.humanities_score) > 840 THEN "A" + ELSE "B" + END AS grade +FROM score +JOIN student s ON score.student_id = s.id', 'N', 0); + +INSERT INTO CHART (ID, GMT_CREATE, GMT_MODIFIED, SCHEMA, DATA_SOURCE_ID, DATABASE_NAME, DDL, DELETED, USER_ID) +VALUES (3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '{"chartType":"Line","xAxis":"name","yAxis":"chinese_score"}', 1, 'DEMO', 'SELECT s.name, sc.chinese_score, sc.math_score, sc.english_score, sc.science_score, sc.humanities_score, +(sc.chinese_score + sc.math_score + sc.english_score + sc.science_score + sc.humanities_score) AS total_score +FROM student s +JOIN score sc ON s.id = sc.student_id', 'N', 0); + +INSERT INTO DASHBOARD_CHART_RELATION (GMT_CREATE, GMT_MODIFIED, DASHBOARD_ID, CHART_ID) +VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 1); +INSERT INTO DASHBOARD_CHART_RELATION (GMT_CREATE, GMT_MODIFIED, DASHBOARD_ID, CHART_ID) +VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 2); +INSERT INTO DASHBOARD_CHART_RELATION (GMT_CREATE, GMT_MODIFIED, DASHBOARD_ID, CHART_ID) +VALUES (CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, 1, 3); \ No newline at end of file From c74209ca52698f9688c83fa2791030a342b5f25d Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 17:55:29 +0800 Subject: [PATCH 0181/1069] dashboardFix --- .../chat2db/server/domain/core/impl/DashboardServiceImpl.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java index fe7e2237b..880fef138 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java @@ -60,6 +60,9 @@ public ActionResult update(DashboardUpdateParam param) { param.setGmtModified(LocalDateTime.now()); DashboardDO dashboardDO = dashboardConverter.updateParam2do(param); dashboardMapper.updateById(dashboardDO); + if (CollectionUtils.isEmpty(param.getChartIds())) { + return ActionResult.isSuccess(); + } deleteDashboardRelation(dashboardDO.getId()); insertDashboardRelation(dashboardDO.getId(), param.getChartIds()); return ActionResult.isSuccess(); From 6820d35b096b3601163d272e95f0b13b13207e4a Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 17:56:34 +0800 Subject: [PATCH 0182/1069] fix ssh bug --- .../src/main/java/ai/chat2db/spi/util/JdbcUtils.java | 2 ++ 1 file changed, 2 insertions(+) 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 36060f985..5716dfb58 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 @@ -206,6 +206,8 @@ public static DataSourceConnect testConnect(String url, String host, String port // 加载驱动 try { if (ssh.isUse()) { + ssh.setRHost(host); + ssh.setRPort(port); session = SSHManager.getSSHSession(ssh); url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); } From 94026d173226e820295b0aba9599d6e5642e8a0e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 18:01:53 +0800 Subject: [PATCH 0183/1069] =?UTF-8?q?feat:=E8=87=AA=E5=8A=A8=E4=BF=9D?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Console/MonacoEditor/index.tsx | 1 + .../src/components/Console/index.tsx | 53 +++++++++++++++++-- .../components/WorkspaceLeft/index.tsx | 3 ++ .../components/WorkspaceRight/index.tsx | 5 +- .../components/WorkspaceRightItem/index.tsx | 4 +- chat2db-client/src/utils/index.ts | 29 ++++++++++ 6 files changed, 90 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 0ef2a9da9..ffb83ce3d 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -46,6 +46,7 @@ export interface IHintData { function MonacoEditor(props: IProps, ref: ForwardedRef) { const { id, className, language = 'sql', didMount, options, isActive, onSave, onExecute, defaultValue, appendValue } = props; + console.log(defaultValue) const editorRef = useRef(); const [appTheme] = useTheme(); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 9b0ce833b..ebd8c6515 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -17,7 +17,8 @@ import i18n from '@/i18n'; import { IRemainingUse } from '@/typings/ai'; import { IAIState } from '@/models/ai'; import { WECHAT_MP_URL } from '@/constants/social'; -import Popularize from '@/components/Popularize' +import Popularize from '@/components/Popularize'; +import { handelLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils' enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -41,10 +42,13 @@ export type IAppendValue = { }; interface IProps { + /** 是谁在调用我 */ + source: 'workspace', /** 是否是活跃的console,用于快捷键 */ isActive?: boolean; /** 添加或修改的内容 */ appendValue?: IAppendValue; + defaultValue?: string; /** 是否开启AI输入 */ hasAiChat: boolean; /** 是否可以开启SQL转到自然语言的相关ai操作 */ @@ -72,7 +76,7 @@ interface IProps { } function Console(props: IProps) { - const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value, aiModel, dispatch } = props; + const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value, aiModel, dispatch, source, defaultValue } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -83,7 +87,9 @@ function Console(props: IProps) { const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); const monacoHint = useRef(); const [modal, contextHolder] = Modal.useModal(); - const [popularizeModal, setPopularizeModal] = useState(false) + const [popularizeModal, setPopularizeModal] = useState(false); + const timerRef = useRef(); + let finalDefaultValue = defaultValue; useEffect(() => { if (appendValue) { @@ -101,6 +107,44 @@ function Console(props: IProps) { monacoHint.current = editorRef?.current?.handleRegisterTigger(myEditorHintData); }, [props.tables]); + useEffect(() => { + if (source !== 'workspace') { + return + } + // 离开时保存 + if (!isActive) { + // 离开时清除定时器 + if (timerRef.current) { + clearInterval(timerRef.current) + handelLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); + } + } else { + // 活跃时自动保存 + timingAutoSave(); + } + return () => { + if (timerRef.current) { + clearInterval(timerRef.current) + } + } + }, [isActive]) + + useEffect(() => { + if (source !== 'workspace') { + return + } + const value = readLocalStorageSavedConsoleText(executeParams.consoleId!) + if (value !== undefined) { + editorRef?.current?.setValue(value, 'reset'); + } + }, []) + + function timingAutoSave() { + timerRef.current = setInterval(() => { + handelLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); + }, 10000) + } + const tableListName = useMemo(() => { const tableList = (props.tables || []).map((t) => t.name); @@ -213,7 +257,9 @@ function Console(props: IProps) { status: ConsoleStatus.RELEASE, ddl: value, }; + historyServer.updateSavedConsole(p).then((res) => { + handelLocalStorageSavedConsole(executeParams.consoleId!, 'delete'); message.success(i18n('common.tips.saveSuccessfully')); props.onConsoleSave && props.onConsoleSave(); }); @@ -264,6 +310,7 @@ function Console(props: IProps) { { return (
    { + openConsole(t) + }} key={t.id} className={styles.saveItem} > 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 204e1ce2d..afa6ad17f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -10,6 +10,7 @@ import LoadingContent from '@/components/Loading/LoadingContent'; import WorkspaceRightItem from '../WorkspaceRightItem'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IAIState } from '@/models/ai'; +import { handelLocalStorageSavedConsole } from '@/utils' interface IProps { className?: string; @@ -159,7 +160,9 @@ const WorkspaceRight = memo(function (props) { if (window!.status === 'DRAFT') { historyService.deleteSavedConsole({ id: window!.id }); } else { - historyService.updateSavedConsole(p); + historyService.updateSavedConsole(p).then(() => { + handelLocalStorageSavedConsole(p.id, 'delete') + }); } }; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index d9060b10a..69ddc5497 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -31,7 +31,7 @@ interface IProps { const WorkspaceRightItem = memo(function (props) { const { className, data, workspaceModel, aiModel, isActive, dispatch } = props; const draggableRef = useRef(); - const [appendValue, setAppendValue] = useState({ text: data.initDDL }); + const [appendValue, setAppendValue] = useState(); const [resultData, setResultData] = useState([]); const { doubleClickTreeNodeData, curTableList, curWorkspaceParams } = workspaceModel; const [showResult, setShowResult] = useState(false); @@ -57,6 +57,8 @@ const WorkspaceRightItem = memo(function (props) {
    (list: T[], key: K, value: }) return flag } + +export function handelLocalStorageSavedConsole(id: number, type: 'save' | 'delete', text?: string) { + + const saved = localStorage.getItem(`timing-auto-save-console-v1`); + let savedObj: any = {} + if (saved) { + savedObj = JSON.parse(saved) + } + + if (type === 'save') { + savedObj[id] = text || ''; + } else if (type === 'delete') { + delete savedObj[id] + } + + localStorage.setItem(`timing-auto-save-console-v1`, JSON.stringify(savedObj)) + +} + + +export function readLocalStorageSavedConsoleText(id: number) { + const saved = localStorage.getItem(`timing-auto-save-console-v1`); + let savedObj: any = {} + if (saved) { + savedObj = JSON.parse(saved) + } + return savedObj[id] || '' +} + From b8e65508ba3b84d1d42d4078d824c5f7aefa4aea Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 18:02:14 +0800 Subject: [PATCH 0184/1069] =?UTF-8?q?feat:=E8=87=AA=E5=8A=A8=E4=BF=9D?= =?UTF-8?q?=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/components/Console/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index ebd8c6515..b2b7089a8 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -142,7 +142,7 @@ function Console(props: IProps) { function timingAutoSave() { timerRef.current = setInterval(() => { handelLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); - }, 10000) + }, 5000) } const tableListName = useMemo(() => { From 6f7ca887b07598112a6b5c005afa43f14cb2dfba Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 18:17:18 +0800 Subject: [PATCH 0185/1069] =?UTF-8?q?sql=E9=87=8D=E5=A4=8D=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/components/Console/MonacoEditor/index.tsx | 1 + chat2db-client/src/components/Console/index.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index ffb83ce3d..d25ce7894 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -293,6 +293,7 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e let newRange: IRangeType = range; if (range === 'reset') { editor.setValue(text) + return } switch (range) { case 'cover': diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index b2b7089a8..27fce9045 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -89,7 +89,6 @@ function Console(props: IProps) { const [modal, contextHolder] = Modal.useModal(); const [popularizeModal, setPopularizeModal] = useState(false); const timerRef = useRef(); - let finalDefaultValue = defaultValue; useEffect(() => { if (appendValue) { From b09ccfdd5cca233e349f4d954be5c6fb45b32324 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 18:44:33 +0800 Subject: [PATCH 0186/1069] =?UTF-8?q?fix:=E5=9B=BE=E8=A1=A8=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E4=BC=A0databaseName?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Console/MonacoEditor/index.tsx | 1 - .../src/components/Console/index.tsx | 1 - .../src/components/TestVersion/index.tsx | 1 - .../pages/main/dashboard/chart-item/index.tsx | 22 ++++++++++++++----- chat2db-client/src/pages/main/index.tsx | 1 - 5 files changed, 17 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index d25ce7894..12310db78 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -46,7 +46,6 @@ export interface IHintData { function MonacoEditor(props: IProps, ref: ForwardedRef) { const { id, className, language = 'sql', didMount, options, isActive, onSave, onExecute, defaultValue, appendValue } = props; - console.log(defaultValue) const editorRef = useRef(); const [appTheme] = useTheme(); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 27fce9045..789066055 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -99,7 +99,6 @@ function Console(props: IProps) { useEffect(() => { monacoHint.current?.dispose(); const myEditorHintData: any = {}; - console.log(props.tables); props.tables?.map((item: any) => { myEditorHintData[item.name] = []; }); diff --git a/chat2db-client/src/components/TestVersion/index.tsx b/chat2db-client/src/components/TestVersion/index.tsx index e98e5e12d..89871d85d 100644 --- a/chat2db-client/src/components/TestVersion/index.tsx +++ b/chat2db-client/src/components/TestVersion/index.tsx @@ -50,7 +50,6 @@ export default memo(function TestVersion(props) { } const openNotification = (responseText: any) => { - console.log(responseText) try { if (responseText.version !== '2.0.0') { const key = `open${Date.now()}`; 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 d691cdfa2..5fb3c2043 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -1,5 +1,5 @@ import { IChartItem, IChartType, IConnectionDetails, ITreeNode } from '@/typings'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useMemo } from 'react'; import styles from './index.less'; import addImage from '@/assets/img/add.svg'; import cs from 'classnames'; @@ -79,6 +79,11 @@ function ChartItem(props: IChartItemProps) { const { id } = props; + + useEffect(() => { + console.log(chartData) + }, [chartData]) + useEffect(() => { if (id !== undefined) { queryChartData(); @@ -102,6 +107,7 @@ function ChartItem(props: IChartItemProps) { if (!curConnection) { return; } + console.log(chartData) setChartData({ ...chartData, dataSourceId: curConnection.id, @@ -151,7 +157,9 @@ function ChartItem(props: IChartItemProps) { if (result && result[0]) { sqlData = handleSQLResult2ChartData(result[0]); } + setChartData({ + ...res, ...chartData, sqlData, }); @@ -298,6 +306,13 @@ function ChartItem(props: IChartItemProps) { ); }; + const initDDLMemo = useMemo(() => { + return { + text: initDDL, + range: 'front', + } + }, [initDDL]) + const renderEditorBlock = () => { const { sqlData = {} } = chartData || {}; const options = Object.keys(sqlData).map((i) => ({ label: i, value: i })); @@ -307,10 +322,7 @@ function ChartItem(props: IChartItemProps) {
    (navConfig[initPageIndex > -1 ? initPageIndex : 2]); - console.log(); useEffect(() => { // activeNav 发生变化,同步到全局状态管理 From cee4d72bdeb615e212aea67cc4c0ec589a660dea Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 18:59:32 +0800 Subject: [PATCH 0187/1069] =?UTF-8?q?fix:=E8=AE=B0=E5=BD=95=E5=BD=93?= =?UTF-8?q?=E5=89=8D=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/pages/main/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 4e9c8ed5a..ced8d0caa 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -46,7 +46,7 @@ const navConfig: INavItem[] = [ }, ]; -const initPageIndex = navConfig.findIndex((t) => `#/${t.key}` === window.location.hash); +const initPageIndex = navConfig.findIndex((t) => `${t.key}` === localStorage.getItem('curPage')); interface IProps { mainModel: IMainPageType['state']; @@ -82,6 +82,7 @@ function MainPage(props: IProps) { const newActiveNav = navConfig[findObjListValue(navConfig, 'key', curPage)]; setActiveNav(newActiveNav); } + localStorage.setItem('curPage', curPage) }, [curPage]); useEffect(() => { From 6d6f56efabc3c69cad79e4e9331fbef263b4eac8 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 19:02:41 +0800 Subject: [PATCH 0188/1069] ai prompt --- .../server/web/api/controller/ai/ChatController.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index f19968b21..e47f2153e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -220,7 +220,11 @@ public SseEmitter completions(ChatQueryRequest queryRequest, @RequestHeader Map< private SseEmitter distributeAI(String msg, SseEmitter sseEmitter, String uid) throws IOException { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); + String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); + if (Objects.nonNull(config)) { + aiSqlSource = config.getContent(); + } + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); if (Objects.isNull(aiSqlSourceEnum)) { aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; } @@ -242,7 +246,11 @@ private SseEmitter distributeAI(String msg, SseEmitter sseEmitter, String uid) t private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); - AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(config.getContent()); + String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); + if (Objects.nonNull(config)) { + aiSqlSource = config.getContent(); + } + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); if (Objects.isNull(aiSqlSourceEnum)) { aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; } From a6ff34d9c2d7a6b77a4043e48ab3215858f41995 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 19:04:25 +0800 Subject: [PATCH 0189/1069] Changing the repo name --- .github/workflows/release.yml | 6 +++--- .github/workflows/release_test.yml | 6 +++--- README.md | 14 +++++++------- README_CN.md | 16 ++++++++-------- chat2db-client/src/constants/appConfig.ts | 2 +- .../listener/AzureOpenAIEventSourceListener.java | 4 ++-- .../ai/listener/OpenAIEventSourceListener.java | 4 ++-- .../ai/listener/RestAIEventSourceListener.java | 2 +- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1df69df58..cc35413dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -233,7 +233,7 @@ jobs: content: | { "title": "Windows-release-打包完成通知", - "text": "# Windows-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " + "text": "# Windows-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " } # 构建完成通知 @@ -246,7 +246,7 @@ jobs: content: | { "title": "MacOS-amd64-release-构建完成通知", - "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar) " + "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.jar) " } # 构建完成通知 @@ -259,5 +259,5 @@ jobs: content: | { "title": "MacOS-arm64-release-构建完成通知", - "text": "# MacOS-arm64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " + "text": "# MacOS-arm64-release-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " } diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 060ce0d39..748243faf 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -237,7 +237,7 @@ jobs: content: | { "title": "Windows-test-打包完成通知", - "text": "# Windows-test-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss-chat2db.alibaba.com/test/win/x64/Chat2DB-Test%20Setup%201.0.${{ github.run_id }}-Test.exe](https://oss-chat2db.alibaba.com/test/win/x64/Chat2DB-Test%20Setup%201.0.${{ github.run_id }}-Test.exe) " + "text": "# Windows-test-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss-chat2db.alibaba.com/test/win/x64/Chat2DB-Test%20Setup%201.0.${{ github.run_id }}-Test.exe](https://oss-chat2db.alibaba.com/test/win/x64/Chat2DB-Test%20Setup%201.0.${{ github.run_id }}-Test.exe) " } # 构建完成通知 @@ -250,7 +250,7 @@ jobs: content: | { "title": "MacOS-amd64-test-构建完成通知", - "text": "# MacOS-amd64-test-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/test/mac/amd/Chat2DB-Test-1.0.${{ github.run_id }}-Test.dmg](https://oss-chat2db.alibaba.com/test/mac/amd/Chat2DB-Test-1.0.${{ github.run_id }}-Test.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/test/jar/${{ github.run_id }}/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/test/jar/${{ github.run_id }}/chat2db-server-start.jar) " + "text": "# MacOS-amd64-test-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/test/mac/amd/Chat2DB-Test-1.0.${{ github.run_id }}-Test.dmg](https://oss-chat2db.alibaba.com/test/mac/amd/Chat2DB-Test-1.0.${{ github.run_id }}-Test.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/test/jar/${{ github.run_id }}/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/test/jar/${{ github.run_id }}/chat2db-server-start.jar) " } # 构建完成通知 @@ -263,5 +263,5 @@ jobs: content: | { "title": "MacOS-arm64-test-构建完成通知", - "text": "# MacOS-arm64-test-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/alibaba/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss-chat2db.alibaba.com/test/mac/arm/Chat2DB-Test-1.0.${{ github.run_id }}-Test-arm64.dmg](https://oss-chat2db.alibaba.com/test/mac/arm/Chat2DB-Test-1.0.${{ github.run_id }}-Test-arm64.dmg) " + "text": "# MacOS-arm64-test-打包完成通知 \n ![bang](https://oss-chat2db.alibaba.com/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss-chat2db.alibaba.com/test/mac/arm/Chat2DB-Test-1.0.${{ github.run_id }}-Test-arm64.dmg](https://oss-chat2db.alibaba.com/test/mac/arm/Chat2DB-Test-1.0.${{ github.run_id }}-Test-arm64.dmg) " } diff --git a/README.md b/README.md index e1c33bf9a..5552e69cb 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,11 @@

    Share Chat2DB Repository

    - -Share on Telegram - -Share on Reddit - + +Share on Telegram + +Share on Reddit +

    **License Notation**: Chat2DB is constructed and distributed for personal and non-commercial use only. For commercial use of this project, please contact corresponding authors. @@ -185,10 +185,10 @@ But front debugging need mapping of resources, you can download [XSwitch](https: * Issue ## Stargazers -[![Stargazers repo roster for @alibaba/Chat2DB](https://reporoster.com/stars/alibaba/Chat2DB)](https://github.com/alibaba/Chat2DB/stargazers) +[![Stargazers repo roster for @alibaba/Chat2DB](https://reporoster.com/stars/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) ## Forkers -[![Forkers repo roster for @alibaba/Chat2DB](https://reporoster.com/forks/alibaba/Chat2DB)](https://github.com/alibaba/Chat2DB/network/members) +[![Forkers repo roster for @alibaba/Chat2DB](https://reporoster.com/forks/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) ## ☎️ Contact Us Please star and fork on GitHub before joining the group. diff --git a/README_CN.md b/README_CN.md index c2b12e8bc..149975b28 100644 --- a/README_CN.md +++ b/README_CN.md @@ -16,11 +16,11 @@

    分享 Chat2DB

    - -Share on Telegram - -Share on Reddit - + +Share on Telegram + +Share on Reddit +

    **许可说明**: Chat2DB开源内容仅供个人免费使用,如想将该项目用于商业用途,请先联系该项目作者。 @@ -191,7 +191,7 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 解决办法:手动下载相关驱动放入到 ~/.chat2db/jdbc-lib 目录下 -下载链接 参考:Application jdbc-jar-downLoad-urls +下载链接 参考:Application jdbc-jar-downLoad-urls - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-8.0.30.jar - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-5.1.47.jar - https://oss-chat2db.alibaba.com/lib/clickhouse-jdbc-0.3.2-patch8-http.jar @@ -206,10 +206,10 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 ## Stargazers -[![Stargazers repo roster for @alibaba/Chat2DB](https://reporoster.com/stars/alibaba/Chat2DB)](https://github.com/alibaba/Chat2DB/stargazers) +[![Stargazers repo roster for @alibaba/Chat2DB](https://reporoster.com/stars/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) ## Forkers -[![Forkers repo roster for @alibaba/Chat2DB](https://reporoster.com/forks/alibaba/Chat2DB)](https://github.com/alibaba/Chat2DB/network/members) +[![Forkers repo roster for @alibaba/Chat2DB](https://reporoster.com/forks/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) ## ☎️ 联系我们 diff --git a/chat2db-client/src/constants/appConfig.ts b/chat2db-client/src/constants/appConfig.ts index 0d3388b45..0d46ae7d5 100644 --- a/chat2db-client/src/constants/appConfig.ts +++ b/chat2db-client/src/constants/appConfig.ts @@ -1,2 +1,2 @@ export const APP_NAME = 'Chat2DB'; -export const GITHUB_URL = 'https://github.com/alibaba/Chat2DB/blob/main/CHANGELOG.md' \ No newline at end of file +export const GITHUB_URL = 'https://github.com/chat2db/Chat2DB/blob/main/CHANGELOG.md' \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java index 2950578cb..1b05a023d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java @@ -75,9 +75,9 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { if (Objects.isNull(response)) { String message = t.getMessage(); if ("No route to host".equals(message)) { - message = "网络连接超时,请检查网络连通性,参考文章"; + message = "网络连接超时,请检查网络连通性,参考文章"; } else { - message = "Azure AI无法正常访问,请参考文章进行配置"; + message = "Azure AI无法正常访问,请参考文章进行配置"; } Message sseMessage = new Message(); sseMessage.setContent(message); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java index 61d579886..04e9e08ca 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java @@ -81,9 +81,9 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { if (Objects.isNull(response)) { String message = t.getMessage(); if ("No route to host".equals(message)) { - message = "网络连接超时,请检查网络连通性,参考文章"; + message = "网络连接超时,请检查网络连通性,参考文章"; } else { - message = "AI无法正常访问,请参考文章进行配置"; + message = "AI无法正常访问,请参考文章进行配置"; } Message sseMessage = new Message(); sseMessage.setContent(message); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java index f4315ece4..8f6aa47d3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java @@ -78,7 +78,7 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); - message = message + ", AI无法正常访问, 请参考文章进行配置"; + message = message + ", AI无法正常访问, 请参考文章进行配置"; Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() From 055eac77eed9e5b8d47b2cf002961844daad024d Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 2 Jul 2023 19:25:12 +0800 Subject: [PATCH 0190/1069] ai prompt --- .../server/web/api/controller/ai/ChatController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index e47f2153e..b0d910a11 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -518,9 +518,9 @@ private String buildPrompt(ChatQueryRequest queryRequest) { default: break; } - if (I18nUtils.isEn()) { - schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty); - } + //if (I18nUtils.isEn()) { + // schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty); + //} return schemaProperty; } } From 13324d8a5b5ebd0e779c1c08211ab4e4ba940ce9 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 19:49:46 +0800 Subject: [PATCH 0191/1069] update README.md --- README.md | 34 ++++++++++++++++------------------ README_CN.md | 34 +++++++++++++++++----------------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index edf0d7f9c..b0ed8d425 100644 --- a/README.md +++ b/README.md @@ -5,10 +5,10 @@ An intelligent and versatile general-purpose SQL client and reporting tool for databases which integrates ChatGPT capabilities. [![License](https://img.shields.io/github/license/alibaba/fastjson2?color=4D7A97&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0.html) -[![GitHub release](https://img.shields.io/github/release/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/releases) -[![GitHub Stars](https://img.shields.io/github/stars/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/stargazers) -[![GitHub Forks](https://img.shields.io/github/forks/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/fork) -[![GitHub Contributors](https://img.shields.io/github/contributors/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/graphs/contributors) +[![GitHub release](https://img.shields.io/github/release/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/releases) +[![GitHub Stars](https://img.shields.io/github/stars/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) +[![GitHub Forks](https://img.shields.io/github/forks/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/fork) +[![GitHub Contributors](https://img.shields.io/github/contributors/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/graphs/contributors)
    @@ -51,7 +51,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 | Windows | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB%20Setup%201.0.11.exe](https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB%20Setup%201.0.11.exe) | | MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11-arm64.dmg](https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11-arm64.dmg) | | MacOS X64 | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11.dmg](https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/1.0.11/ali-dbhub-server-start.jar](https://oss-chat2db.alibaba.com/release/1.0.11/ali-dbhub-server-start.jar) | +| Jar包 | [https://oss-chat2db.alibaba.com/release/1.0.11/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/1.0.11/chat2db-server-start.jar) | ## 🚀 Supported databases | Databases | Status | @@ -136,24 +136,24 @@ $ git clone git@github.com:alibaba/Chat2DB.git ``` - Front-End installation ```bash -$ cd Chat2DB/ali-dbhub-client +$ cd Chat2DB/chat2db-client $ npm install # Mounting front-end dependency $ npm run build:prod # Package js to the source directory on the back end ``` - Backend debug ```bash -$ cd ../ali-dbhub-server +$ cd ../chat2db-server $ mvn clean install # maven 3.8 or later needs to be installed -$ cd ali-dbhub-server/ali-dbhub-server-start/target/ -$ java -jar -Dchatgpt.apiKey=xxxxx ali-dbhub-server-start.jar # To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. +$ cd chat2db-server/chat2db-server-start/target/ +$ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. $ # open http://127.0.0.1:10821 to start debug Note: Front-end installation is required ``` - Front-End debug ```bash -$ cd Chat2DB/ali-dbhub-client -$ npm install -$ npm run start +$ cd Chat2DB/chat2db-client +$ yarn +$ npm run start:web $ # open http://127.0.0.1:10821 to start Front-End debug $ # Note Front-end page completely depends on the service, so front-end students need to debug the back-end project ``` @@ -188,10 +188,10 @@ But front debugging need mapping of resources, you can download [XSwitch](https: * Issue ## Stargazers -[![Stargazers repo roster for @alibaba/Chat2DB](https://reporoster.com/stars/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) +[![Stargazers repo roster for @chat2db/Chat2DB](https://reporoster.com/stars/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) ## Forkers -[![Forkers repo roster for @alibaba/Chat2DB](https://reporoster.com/forks/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) +[![Forkers repo roster for @chat2db/Chat2DB](https://reporoster.com/forks/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) ## ☎️ Contact Us Please star and fork on GitHub before joining the group. @@ -201,8 +201,8 @@ Follow our WeChat public account ## ❤️ Acknowledgements Thanks to all the students who contributed to Chat2DB~ - - + + @@ -216,6 +216,4 @@ Thanks to all the students who contributed to Chat2DB~ - - diff --git a/README_CN.md b/README_CN.md index 89ea6e2dd..55ae19bc1 100644 --- a/README_CN.md +++ b/README_CN.md @@ -6,10 +6,10 @@ [![License](https://img.shields.io/github/license/alibaba/fastjson2?color=4D7A97&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Java support](https://img.shields.io/badge/Java-17+-green?logo=java&logoColor=white)](https://openjdk.java.net/) -[![GitHub release](https://img.shields.io/github/release/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/releases) -[![GitHub Stars](https://img.shields.io/github/stars/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/stargazers) -[![GitHub Forks](https://img.shields.io/github/forks/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/fork) -[![GitHub Contributors](https://img.shields.io/github/contributors/alibaba/ali-dbhub)](https://github.com/alibaba/ali-dbhub/graphs/contributors) +[![GitHub release](https://img.shields.io/github/release/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/releases) +[![GitHub Stars](https://img.shields.io/github/stars/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) +[![GitHub Forks](https://img.shields.io/github/forks/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/fork) +[![GitHub Contributors](https://img.shields.io/github/contributors/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/graphs/contributors)
    @@ -48,7 +48,7 @@ Languages: 中文 [English](README.md) | Windows | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB%20Setup%201.0.11.exe](https://oss-chat2db.alibaba.com/release/1.0.10/Chat2DB%20Setup%201.0.10.exe) | | MacOS ARM64 (Apple芯片) | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11-arm64.dmg](https://oss-chat2db.alibaba.com/release/1.0.10/Chat2DB-1.0.10-arm64.dmg) | | MacOS X64 (Intel芯片) | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11.dmg](https://oss-chat2db.alibaba.com/release/1.0.10/Chat2DB-1.0.10.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/1.0.11/ali-dbhub-server-start.jar](https://oss-chat2db.alibaba.com/release/1.0.10/ali-dbhub-server-start.jar) | +| Jar包 | [https://oss-chat2db.alibaba.com/release/1.0.11/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/1.0.10/chat2db-server-start.jar) | ## 🚀 支持的数据库 | 数据库 | 支持计划 | @@ -127,26 +127,26 @@ Languages: 中文 [English](README.md) ## 💻 本地调试 - git clone到本地 ```bash -$ git clone git@github.com:alibaba/Chat2DB.git +$ git clone git@github.com:chat2db/Chat2DB.git ``` - 前端安装 ```bash -$ cd Chat2DB/ali-dbhub-client +$ cd Chat2DB/chat2db-client $ npm install # 安装前端依赖 $ npm run build:prod # 把js打包生成到后端的source目录 ``` - 后端调试 ```bash -$ cd ../ali-dbhub-server +$ cd ../chat2db-server $ mvn clean install # 需要安装maven 3.8以上版本 -$ cd ali-dbhub-server/ali-dbhub-server-start/target/ -$ java -jar -Dchatgpt.apiKey=xxxxx ali-dbhub-server-start.jar # 启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 +$ cd chat2db-server/chat2db-server-start/target/ +$ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 $ # 打开 http://127.0.0.1:10821 开启调试 注:需要进行前端安装 ``` - 前端调试 ```bash -$ cd Chat2DB/ali-dbhub-client +$ cd Chat2DB/chat2db-client $ npm install $ npm run start $ # 打开 http://127.0.0.1:10821 开启前端调试 @@ -181,7 +181,7 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 ## 📑 文档 * 官方文档 -* Issue +* Issue ## 常见问题 @@ -191,7 +191,7 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 解决办法:手动下载相关驱动放入到 ~/.chat2db/jdbc-lib 目录下 -下载链接 参考:Application jdbc-jar-downLoad-urls +下载链接 参考:Application jdbc-jar-downLoad-urls - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-8.0.30.jar - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-5.1.47.jar - https://oss-chat2db.alibaba.com/lib/clickhouse-jdbc-0.3.2-patch8-http.jar @@ -206,10 +206,10 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 ## Stargazers -[![Stargazers repo roster for @alibaba/Chat2DB](https://reporoster.com/stars/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) +[![Stargazers repo roster for @chat2db/Chat2DB](https://reporoster.com/stars/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) ## Forkers -[![Forkers repo roster for @alibaba/Chat2DB](https://reporoster.com/forks/alibaba/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) +[![Forkers repo roster for @chat2db/Chat2DB](https://reporoster.com/forks/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) ## ☎️ 联系我们 @@ -220,8 +220,8 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 ## ❤️ 致谢 感谢所有为Chat2DB贡献力量的同学们~ - - + + ## Star History From d186aee7949697dd5426a3c8bdaae0d6d91b4d55 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 19:55:43 +0800 Subject: [PATCH 0192/1069] update README.md --- README.md | 14 +++++++------- README_CN.md | 5 +++++ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b0ed8d425..79f4e6372 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ Languages: English | [中文](README_CN.md)
    +## DEMO + +https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f + + ## 📖 Introduction    Chat2DB is a multi-database client tool that is open-source and free from Alibaba. It supports local installation on Windows and Mac, as well as server-side deployment and web page access. Compared to traditional database client software such as Navicat and DBeaver, Chat2DB integrates AIGC's capabilities and is able to convert natural language into SQL. It can also convert SQL into natural language and provide optimization suggestions for SQL to greatly enhance the efficiency of developers. It is a tool for database developers in the AI era, and even non-SQL business operators in the future can use it to quickly query business data and generate reports. ## ✨ Features @@ -37,12 +42,6 @@ Languages: English | [中文](README_CN.md) - 🛡 Front-end development using Electron, providing a solution that integrates Windows, Mac, Linux clients, and web versions - 🎁 Support environment isolation, online, and daily data permission separation -## DEMO - - -https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f - - ## ⏬ Download and Install @@ -195,7 +194,8 @@ But front debugging need mapping of resources, you can download [XSwitch](https: ## ☎️ Contact Us Please star and fork on GitHub before joining the group. -Follow our WeChat public account +Follow our WeChat public account. + ## ❤️ Acknowledgements diff --git a/README_CN.md b/README_CN.md index 55ae19bc1..f3d09b131 100644 --- a/README_CN.md +++ b/README_CN.md @@ -28,6 +28,11 @@ Languages: 中文 [English](README.md)
    +## 案例视频 + +https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f + + ## 📖 简介    Chat2DB 是一款有开源免费的多数据库客户端工具,支持windows、mac本地安装,也支持服务器端部署,web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力,能够将自然语言转换为SQL,也可以将SQL转换为自然语言,可以给出研发人员SQL的优化建议,极大的提升人员的效率,是AI时代数据库研发人员的利器,未来即使不懂SQL的运营业务也可以使用快速查询业务数据、生成报表能力。 ## ✨ 特性 From 7afb226269a2914edcd28159ba81e1392f99baa1 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 2 Jul 2023 19:53:00 +0800 Subject: [PATCH 0193/1069] feat: improve i18n --- chat2db-client/src/i18n/en-us/dashboard.ts | 3 ++- chat2db-client/src/i18n/zh-cn/common.ts | 1 - chat2db-client/src/i18n/zh-cn/dashboard.ts | 3 ++- .../pages/main/dashboard/chart-item/index.tsx | 23 ++++++++----------- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/chat2db-client/src/i18n/en-us/dashboard.ts b/chat2db-client/src/i18n/en-us/dashboard.ts index 9cabbdd81..93cc80ebb 100644 --- a/chat2db-client/src/i18n/en-us/dashboard.ts +++ b/chat2db-client/src/i18n/en-us/dashboard.ts @@ -4,6 +4,7 @@ export default { 'dashboard.modal.editTitle': 'Edit Dashboard', 'dashboard.modal.addTitle': 'Add Dashboard', 'dashboard.modal.name.placeholder': "Please enter dashboard's name.", + 'dashboard.export2image': 'Export to image', 'dashboard.delete': 'Delete', - 'dashboard.editor.cascader.placeholder': 'Please select a connection pool', + 'dashboard.editor.cascader.placeholder': 'Please select a connection pool', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 2bc3e69d2..06e2f6f35 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -58,5 +58,4 @@ export default { 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', - }; diff --git a/chat2db-client/src/i18n/zh-cn/dashboard.ts b/chat2db-client/src/i18n/zh-cn/dashboard.ts index b841606a0..93cfaea5b 100644 --- a/chat2db-client/src/i18n/zh-cn/dashboard.ts +++ b/chat2db-client/src/i18n/zh-cn/dashboard.ts @@ -3,7 +3,8 @@ export default { 'dashboard.edit': '编辑', 'dashboard.modal.editTitle': '编辑仪表盘', 'dashboard.modal.addTitle': '新增仪表盘', - 'dashboard.modal.name.placeholder': "请输入仪表盘名", + 'dashboard.modal.name.placeholder': '请输入仪表盘名', 'dashboard.delete': '删除', + 'dashboard.export2image': '导出图片', 'dashboard.editor.cascader.placeholder': '请选择连接', }; 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 5fb3c2043..fe7967257 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -79,11 +79,6 @@ function ChartItem(props: IChartItemProps) { const { id } = props; - - useEffect(() => { - console.log(chartData) - }, [chartData]) - useEffect(() => { if (id !== undefined) { queryChartData(); @@ -107,7 +102,7 @@ function ChartItem(props: IChartItemProps) { if (!curConnection) { return; } - console.log(chartData) + console.log(chartData); setChartData({ ...chartData, dataSourceId: curConnection.id, @@ -204,8 +199,8 @@ function ChartItem(props: IChartItemProps) { schema: JSON.stringify(form.getFieldsValue(true)), }; await updateChart(params); - setIsEditing(false) - message.success(i18n('common.tips.saveSuccessfully')) + setIsEditing(false); + message.success(i18n('common.tips.saveSuccessfully')); }; const handleChartConfigChange = () => { @@ -310,8 +305,8 @@ function ChartItem(props: IChartItemProps) { return { text: initDDL, range: 'front', - } - }, [initDDL]) + }; + }, [initDDL]); const renderEditorBlock = () => { const { sqlData = {} } = chartData || {}; @@ -371,7 +366,7 @@ function ChartItem(props: IChartItemProps) { }} className={styles.dataSourceSelect} placeholder={i18n('dashboard.editor.cascader.placeholder')} - // style={{ width: '100%' }} + // style={{ width: '100%' }} />
    @@ -430,19 +425,19 @@ function ChartItem(props: IChartItemProps) { items: [ { key: 'Edit', - label: 'Edit', + label: i18n('dashboard.edit'), onClick: () => { setIsEditing(true); }, }, { key: 'Export', - label: 'Export to image', + label: i18n('dashboard.export2image'), onClick: onExport2Image, }, { key: 'delete', - label: 'Delete', + label: i18n('dashboard.delete'), onClick: onDeleteChart, }, ], From f5bf7074a13c34184424f091cb2ca4ea6eff4c54 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 20:00:50 +0800 Subject: [PATCH 0194/1069] =?UTF-8?q?fix:=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/components/Console/index.tsx | 4 ++-- .../workspace/components/WorkspaceRight/index.tsx | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 789066055..d13775a0e 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -132,7 +132,7 @@ function Console(props: IProps) { return } const value = readLocalStorageSavedConsoleText(executeParams.consoleId!) - if (value !== undefined) { + if (value) { editorRef?.current?.setValue(value, 'reset'); } }, []) @@ -334,7 +334,7 @@ function Console(props: IProps) { {i18n('common.button.execute')} {hasSaveBtn && ( - )} 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 afa6ad17f..36498d245 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -157,13 +157,13 @@ const WorkspaceRight = memo(function (props) { if (!window?.status) { return; } - if (window!.status === 'DRAFT') { - historyService.deleteSavedConsole({ id: window!.id }); - } else { - historyService.updateSavedConsole(p).then(() => { - handelLocalStorageSavedConsole(p.id, 'delete') - }); - } + // if (window!.status === 'DRAFT') { + // historyService.deleteSavedConsole({ id: window!.id }); + // } else { + historyService.updateSavedConsole(p).then(() => { + handelLocalStorageSavedConsole(p.id, 'delete') + }); + // } }; function render() { From 6f2bf4d3c289866b53364f3bbdb3096fec71f4c0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 2 Jul 2023 20:01:40 +0800 Subject: [PATCH 0195/1069] feat: Try to repair the UMI configuration --- chat2db-client/{.umirc.desktop.ts => .umirc.prod.desktop.ts} | 0 chat2db-client/.umirc.prod.ts | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename chat2db-client/{.umirc.desktop.ts => .umirc.prod.desktop.ts} (100%) diff --git a/chat2db-client/.umirc.desktop.ts b/chat2db-client/.umirc.prod.desktop.ts similarity index 100% rename from chat2db-client/.umirc.desktop.ts rename to chat2db-client/.umirc.prod.desktop.ts diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index a3200ad4c..6ac580b59 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -12,7 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: './', + publicPath: './static/front/', chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, From b6772a24f9a76a0158fa9796e2cd0113dbc7d109 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 2 Jul 2023 20:27:24 +0800 Subject: [PATCH 0196/1069] fix: modify release_test ci/cd --- .github/workflows/release_test.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 748243faf..4a661a62a 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -90,7 +90,7 @@ jobs: uses: actions/setup-node@main with: node-version: 16 - cache: 'yarn' + cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock # 安装java @@ -106,7 +106,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822 + yarn run build:web:prod --appVersion=1.0.${{ github.run_id }} --appPort=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -200,7 +200,6 @@ jobs: cp -r chat2db-client/release/latest-mac.yml ./oss_temp_file/mac/amd ls chat2db-client/release/ - # 准备要需要的数据 MacOS arm64 - name: Prepare upload for MacOS arm64 if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} From d28fd828d6f64055f0c3761c1bf1807da650232e Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 20:28:48 +0800 Subject: [PATCH 0197/1069] update README.md --- .../src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java | 2 ++ 1 file changed, 2 insertions(+) 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 246031c8f..b44344f6f 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 @@ -99,6 +99,8 @@ private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { String host = connectInfo.getHost(); String port = connectInfo.getPort() + ""; try { + ssh.setRHost(host); + ssh.setRPort(port); session = getSession(ssh); if (session != null) { url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); From c22472c9d757f2891ee15bf7688ef91af2823229 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 2 Jul 2023 20:49:07 +0800 Subject: [PATCH 0198/1069] fix: modify release CI/CD config --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc35413dd..2c6c04a7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -104,7 +104,7 @@ jobs: uses: actions/setup-node@main with: node-version: 16 - cache: 'yarn' + cache: "yarn" cache-dependency-path: chat2db-client/yarn.lock # 安装java @@ -120,7 +120,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:desktop --appVersion=${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:prod --appVersion=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -163,7 +163,6 @@ jobs: args: "-c.extraMetadata.version=${{ steps.chat2db_version.outputs.substring }} --mac --x64" release: true - # amd64 notarization - name: Notarization amd64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} @@ -218,7 +217,7 @@ jobs: endpoint: "oss-accelerate.aliyuncs.com" access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} - ossutil-version: 'latest' + ossutil-version: "latest" - name: Upload to oss run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db/release/${{ steps.chat2db_version.outputs.substring }}/ From 17b2ef19d91e849eec27ef909985759ed4084161 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 21:09:26 +0800 Subject: [PATCH 0199/1069] update README.md --- .../server/domain/support/util/JdbcUtils.java | 31 ++++++++++++------- .../java/ai/chat2db/spi/model/SSHInfo.java | 20 ++++++++++++ .../java/ai/chat2db/spi/util/JdbcUtils.java | 3 +- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java index 23b9277a5..2d3e278b9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java @@ -1,6 +1,22 @@ package ai.chat2db.server.domain.support.util; -import cn.hutool.core.date.DateUtil; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.Map; + +import com.alibaba.druid.DbType; + import ai.chat2db.server.domain.support.enums.DataTypeEnum; import ai.chat2db.server.domain.support.enums.DbTypeEnum; import ai.chat2db.server.domain.support.enums.DriverTypeEnum; @@ -8,19 +24,11 @@ import ai.chat2db.server.domain.support.model.SSHInfo; import ai.chat2db.server.domain.support.sql.IDriverManager; import ai.chat2db.server.domain.support.sql.SSHManager; -import com.alibaba.druid.DbType; +import cn.hutool.core.date.DateUtil; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; -import java.sql.*; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Locale; -import java.util.Map; - /** * jdbc工具类 * @@ -242,9 +250,10 @@ public static DataSourceConnect testConnect(String url, String host, String port if (session != null) { try { session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); + session.disconnect(); + } catch (JSchException e) { } - session.disconnect(); } } dataSourceConnect.setDescription("成功"); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java index 31352549a..9af1c56b7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java @@ -4,6 +4,8 @@ */ package ai.chat2db.spi.model; +import java.util.Objects; + import lombok.Data; /** @@ -68,4 +70,22 @@ public class SSHInfo { */ private String rPort; + @Override + public boolean equals(Object o) { + if (this == o) {return true;} + if (o == null || getClass() != o.getClass()) {return false;} + SSHInfo sshInfo = (SSHInfo)o; + return use == sshInfo.use && Objects.equals(hostName, sshInfo.hostName) && Objects.equals(port, + sshInfo.port) && Objects.equals(userName, sshInfo.userName) && Objects.equals(localPort, + sshInfo.localPort) && Objects.equals(authenticationType, sshInfo.authenticationType) + && Objects.equals(password, sshInfo.password) && Objects.equals(keyFile, sshInfo.keyFile) + && Objects.equals(passphrase, sshInfo.passphrase) && Objects.equals(rHost, sshInfo.rHost) + && Objects.equals(rPort, sshInfo.rPort); + } + + @Override + public int hashCode() { + return Objects.hash(use, hostName, port, userName, localPort, authenticationType, password, keyFile, passphrase, + rHost, rPort); + } } \ No newline at end of file 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 5716dfb58..e64180af5 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 @@ -235,9 +235,10 @@ public static DataSourceConnect testConnect(String url, String host, String port if (session != null) { try { session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); + session.disconnect(); + } catch (JSchException e) { } - session.disconnect(); } } dataSourceConnect.setDescription("成功"); From 7118c205e7292a25f4af274202f7be58053fb3d3 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 21:15:41 +0800 Subject: [PATCH 0200/1069] update README.md --- .../src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 b44344f6f..6bd1464a9 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 @@ -126,9 +126,13 @@ private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { if (session != null) { try { session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); - session.disconnect(); } catch (JSchException e) { - log.error("session close error", e); + log.error("session delPortForwardingL error", e); + } + try { + session.disconnect(); + } catch (Exception e) { + log.error("session disconnect error", e); } } throw new RuntimeException("getConnect error", e1); From 2c818d17f01dcec7a6d550208a5c5d9bd31c4eaf Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 21:24:34 +0800 Subject: [PATCH 0201/1069] update README.md --- .../ai/chat2db/spi/sql/Chat2DBContext.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 6bd1464a9..a12a60206 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 @@ -180,15 +180,15 @@ public static void removeContext() { } catch (SQLException e) { log.error("close connection error", e); } - Session session = connectInfo.getSession(); - if (session != null) { - try { - session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); - session.disconnect(); - } catch (JSchException e) { - log.error("close session error", e); - } - } + //Session session = connectInfo.getSession(); + //if (session != null) { + // try { + // session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); + // session.disconnect(); + // } catch (JSchException e) { + // log.error("close session error", e); + // } + //} CONNECT_INFO_THREAD_LOCAL.remove(); } } From 19b2b38716ed54ebb6566a7844f612b0f644226d Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 21:34:08 +0800 Subject: [PATCH 0202/1069] release docker --- .github/workflows/pushdocker.yml | 13 +++++++------ docker/Dockerfile | 4 ++-- docker/docker-build.sh | 1 - 3 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 docker/docker-build.sh diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index ed7fe70c9..eeb57ee83 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -3,9 +3,10 @@ name: Push To Docker # Workflow's trigger -on: - release: - types: [ published ] +#on: +# release: +# types: [ published ] +on: [ push, pull_request ] # Workflow's jobs jobs: @@ -44,10 +45,10 @@ jobs: # 构建静态文件信息 - name: Npm install & build & copy run: | - cd ali-dbhub-client + cd chat2db-client npm install npm run build:desktop - mv dist ../ali-dbhub-server/ali-dbhub-server-start/src/main/resources/static/front + mv dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 安装java - name: Install Java and Maven @@ -59,7 +60,7 @@ jobs: # 编译服务端java版本 - name: Build Java - run: mvn clean package -B '-Dmaven.test.skip=true' -f ali-dbhub-server/pom.xml + run: mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml - name: Set up QEMU diff --git a/docker/Dockerfile b/docker/Dockerfile index 153b3c2ec..8d4fcdbff 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -3,8 +3,8 @@ FROM openjdk:17 # 定义进入容器时默认位置,接下来后续操作的工作位置 WORKDIR /app # 将当前目录下的jar包复制到docker容器的/app 目录下 -ADD ali-dbhub-server/ali-dbhub-server-start/target/ali-dbhub-server-start.jar ali-dbhub-server-start.jar +ADD chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-server-start.jar # 让当前容器暴露10824 EXPOSE 10824 # 运行jar包 -ENTRYPOINT ["java","-Dspring.profiles.active=release","-jar","ali-dbhub-server-start.jar"] \ No newline at end of file +ENTRYPOINT ["java","-Dspring.profiles.active=release","-jar","chat2db-server-start.jar"] \ No newline at end of file diff --git a/docker/docker-build.sh b/docker/docker-build.sh deleted file mode 100644 index 38dc3d717..000000000 --- a/docker/docker-build.sh +++ /dev/null @@ -1 +0,0 @@ -docker build --no-cache -f docker/Dockerfile -t chat2db/chat2db:v1.0.111-dev . \ No newline at end of file From b9129b540f804f9965c5195cb2a63979b48cd580 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 21:35:01 +0800 Subject: [PATCH 0203/1069] release docker --- .github/workflows/pushdocker.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index eeb57ee83..9e51cdfb6 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -3,10 +3,9 @@ name: Push To Docker # Workflow's trigger -#on: -# release: -# types: [ published ] -on: [ push, pull_request ] +on: + release: + types: [ published ] # Workflow's jobs jobs: From fdb0bb5e81cb09d1c05db1a32fe33c519d9392dd Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 21:36:35 +0800 Subject: [PATCH 0204/1069] release docker --- .github/workflows/pushdocker.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index 9e51cdfb6..f4d371b8e 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -3,9 +3,10 @@ name: Push To Docker # Workflow's trigger -on: - release: - types: [ published ] +#on: +# release: +# types: [ published ] +on: [ push, pull_request ] # Workflow's jobs jobs: @@ -26,8 +27,7 @@ jobs: id: chat2db_version uses: bhowell2/github-substring-action@1.0.1 with: - value: ${{ github.ref }} - index_of_str: "refs/tags/v" + value: 2.0.0 # 输出基础信息 From 8eb51b567e4237ce96ecc9b5f5a6c25a5dda0c01 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 21:37:20 +0800 Subject: [PATCH 0205/1069] Remove useless code --- .../chat2db-server-domain-support/pom.xml | 62 -- .../support/datasource/DataSourceManger.java | 115 ---- .../support/datasource/IDataSource.java | 57 -- .../support/datasource/MyDataSource.java | 34 - .../domain/support/dialect/BaseMapper.java | 77 --- .../support/dialect/BaseMetaSchema.java | 145 ----- .../domain/support/dialect/MetaSchema.java | 183 ------ .../ClickhouseMetaSchemaSupport.java | 20 - .../support/dialect/common/SQLKeyConst.java | 41 -- .../support/dialect/common/SQLParam.java | 26 - .../support/dialect/common/SQLType.java | 32 - .../common/enums/BaseCollationEnum.java | 19 - .../common/enums/BaseColumnTypeEnum.java | 19 - .../common/enums/BaseIndexTypeEnum.java | 19 - .../common/handler/BooleanTypeHandler.java | 50 -- .../dialect/common/model/SpiExample.java | 28 - .../dialect/db2/DB2MetaSchemaSupport.java | 75 --- .../dialect/dm/DMMetaSchemaSupport.java | 43 -- .../support/dialect/h2/H2CollationEnum.java | 38 -- .../support/dialect/h2/H2ColumnTypeEnum.java | 46 -- .../support/dialect/h2/H2IndexTypeEnum.java | 42 -- .../dialect/h2/H2MetaSchemaSupport.java | 109 ---- .../h2/handler/H2IndexTypeHandler.java | 53 -- .../dialect/hive/HiveMetaSchemaSupport.java | 20 - .../kingbase/KingBaseSchemaSupport.java | 20 - .../mariadb/MariaDBMetaSchemaSupport.java | 21 - .../mongodb/MongodbMetaSchemaSupport.java | 20 - .../dialect/mysql/MysqlCollationEnum.java | 38 -- .../dialect/mysql/MysqlMetaSchemaSupport.java | 46 -- .../handler/MysqlCollationTypeHandler.java | 54 -- .../mysql/handler/MysqlColumnKeyHandler.java | 49 -- .../mysql/handler/MysqlExtraTypeHandler.java | 49 -- .../mysql/handler/MysqlIndexTypeHandler.java | 52 -- .../oceanbase/OceanBaseMetaSchemaSupport.java | 20 - .../oracle/OracleMetaSchemaSupport.java | 52 -- .../PostgresqlMetaSchemaSupport.java | 201 ------ .../postgresql/model/PostgresqlBaseModel.java | 20 - .../postgresql/model/PostgresqlColumn.java | 34 - .../model/PostgresqlConstraintCheck.java | 19 - .../model/PostgresqlConstraintExclusive.java | 19 - .../PostgresqlConstraintExclusiveField.java | 19 - .../model/PostgresqlConstraintForeign.java | 22 - .../PostgresqlConstraintForeignReference.java | 20 - .../model/PostgresqlConstraintUnique.java | 22 - .../PostgresqlConstraintUniqueField.java | 21 - .../model/PostgresqlForeignServer.java | 23 - .../model/PostgresqlForeignTable.java | 26 - .../postgresql/model/PostgresqlInherit.java | 24 - .../postgresql/model/PostgresqlPartition.java | 23 - .../model/PostgresqlPartitionColumn.java | 18 - .../postgresql/model/PostgresqlResemble.java | 20 - .../postgresql/model/PostgresqlTable.java | 26 - .../model/PostgresqlTableIndex.java | 36 -- .../model/PostgresqlTableIndexField.java | 20 - .../presto/PrestoMetaSchemaSupport.java | 27 - .../dialect/redis/RedisMetaSchemaSupport.java | 75 --- .../sqlite/SQLiteMetaSchemaSupport.java | 53 -- .../sqlserver/SqlServerMetaSchemaSupport.java | 78 --- .../domain/support/enums/CellTypeEnum.java | 50 -- .../domain/support/enums/CollationEnum.java | 55 -- .../domain/support/enums/ColumnTypeEnum.java | 50 -- .../domain/support/enums/DataTypeEnum.java | 99 --- .../domain/support/enums/DbTypeEnum.java | 239 ------- .../domain/support/enums/DriverTypeEnum.java | 163 ----- .../domain/support/enums/IndexTypeEnum.java | 40 -- .../domain/support/enums/SqlTypeEnum.java | 36 -- .../server/domain/support/model/Cell.java | 49 -- .../domain/support/model/CreateTableSql.java | 25 - .../support/model/DataSourceConnect.java | 34 - .../server/domain/support/model/Database.java | 22 - .../domain/support/model/DriverEntry.java | 31 - .../domain/support/model/ExecuteResult.java | 88 --- .../server/domain/support/model/Function.java | 43 -- .../server/domain/support/model/Header.java | 30 - .../server/domain/support/model/KeyValue.java | 39 -- .../domain/support/model/Procedure.java | 43 -- .../server/domain/support/model/SSHInfo.java | 71 --- .../server/domain/support/model/SSLInfo.java | 12 - .../server/domain/support/model/Schema.java | 25 - .../support/model/ShowDatabaseResult.java | 22 - .../server/domain/support/model/Sql.java | 24 - .../server/domain/support/model/Table.java | 65 -- .../domain/support/model/TableColumn.java | 145 ----- .../domain/support/model/TableIndex.java | 64 -- .../support/model/TableIndexColumn.java | 102 --- .../server/domain/support/model/Trigger.java | 12 - .../domain/support/sql/ConnectInfo.java | 598 ------------------ .../domain/support/sql/DbhubContext.java | 157 ----- .../domain/support/sql/IDriverManager.java | 201 ------ .../domain/support/sql/SQLExecutor.java | 407 ------------ .../server/domain/support/sql/SSHManager.java | 84 --- .../server/domain/support/util/JSchUtils.java | 40 -- .../domain/support/util/JdbcJarUtils.java | 152 ----- .../server/domain/support/util/JdbcUtils.java | 263 -------- .../domain/support/util/ResultSetUtils.java | 101 --- .../server/domain/support/util/SqlUtils.java | 436 ------------- .../druid/sql/parser/DbhubSQLParserUtils.java | 49 -- chat2db-server/chat2db-server-domain/pom.xml | 1 - 98 files changed, 6787 deletions(-) delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/pom.xml delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/DataSourceManger.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/IDataSource.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/MyDataSource.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMapper.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMetaSchema.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/MetaSchema.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/clickhouse/ClickhouseMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLKeyConst.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLType.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseCollationEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseColumnTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseIndexTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/handler/BooleanTypeHandler.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/model/SpiExample.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/db2/DB2MetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/dm/DMMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2CollationEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2ColumnTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2IndexTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2MetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/handler/H2IndexTypeHandler.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/hive/HiveMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/kingbase/KingBaseSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mariadb/MariaDBMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mongodb/MongodbMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlCollationEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlCollationTypeHandler.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlColumnKeyHandler.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlExtraTypeHandler.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlIndexTypeHandler.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oceanbase/OceanBaseMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oracle/OracleMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/PostgresqlMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlBaseModel.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlColumn.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintCheck.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusive.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusiveField.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeign.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeignReference.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUnique.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUniqueField.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignServer.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignTable.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlInherit.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartition.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartitionColumn.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlResemble.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTable.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndex.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndexField.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/presto/PrestoMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/redis/RedisMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlite/SQLiteMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlserver/SqlServerMetaSchemaSupport.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CellTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CollationEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/ColumnTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DataTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DbTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DriverTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/IndexTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/SqlTypeEnum.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Cell.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/CreateTableSql.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DataSourceConnect.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Database.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DriverEntry.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ExecuteResult.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Function.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Header.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/KeyValue.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Procedure.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSHInfo.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSLInfo.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Schema.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ShowDatabaseResult.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Sql.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Table.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableColumn.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndex.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndexColumn.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Trigger.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/ConnectInfo.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/DbhubContext.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/IDriverManager.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SQLExecutor.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SSHManager.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JSchUtils.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcJarUtils.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/ResultSetUtils.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/SqlUtils.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/com/alibaba/druid/sql/parser/DbhubSQLParserUtils.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/pom.xml deleted file mode 100644 index 6eef10687..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - ai.chat2db - chat2db-server-domain - ${revision} - ../pom.xml - - 4.0.0 - chat2db-server-domain-support - - - ai.chat2db - chat2db-server-tools-common - - - com.alibaba - druid - - - - - com.h2database - h2 - - - org.springframework - spring-jdbc - - - com.jcraft - jsch - 0.1.53 - - - com.oracle.ojdbc - orai18n - 19.3.0.0 - test - - - mysql - mysql-connector-java - 8.0.30 - test - - - com.squareup.okhttp3 - okhttp - - - com.zaxxer - HikariCP - - - com.baomidou - mybatis-plus-boot-starter - - - diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/DataSourceManger.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/DataSourceManger.java deleted file mode 100644 index 2e5aaf73e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/DataSourceManger.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.datasource; - -import java.net.URLClassLoader; -import java.sql.Connection; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - -import javax.sql.DataSource; - -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; -import ai.chat2db.server.domain.support.sql.ConnectInfo; -import ai.chat2db.server.domain.support.sql.IDriverManager; -import com.alibaba.fastjson2.JSON; - -import com.zaxxer.hikari.HikariDataSource; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ObjectUtils; - -/** - * @author jipengfei - * @version : DataSourceManger.java - */ -@Slf4j -public class DataSourceManger { - - protected static final ConcurrentHashMap DATA_SOURCE_MAP = new ConcurrentHashMap(); - - public static DataSource getDataSource(ConnectInfo connectInfo) { - String key = connectInfo.getDataSourceId().toString(); - MyDataSource dataSource = DATA_SOURCE_MAP.get(key); - if (dataSource != null) { - if (!connectInfo.equals(dataSource.getConnectInfo())) { - try { - dataSource.getHikariDataSource().close(); - DATA_SOURCE_MAP.remove(key); - } catch (Exception e) { - } - } else { - return dataSource.getHikariDataSource(); - } - } - synchronized (key) { - dataSource = DATA_SOURCE_MAP.get(key); - if (dataSource != null) { - return dataSource.getHikariDataSource(); - } else { - try { - dataSource = createDataSource(connectInfo); - DATA_SOURCE_MAP.put(key, dataSource); - return dataSource.getHikariDataSource(); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - } - - private static MyDataSource createDataSource(ConnectInfo connectInfo) throws Exception { - DriverTypeEnum driverTypeEnum = DriverTypeEnum.getDriver(connectInfo.getDbType(), connectInfo.getJdbc()); - ClassLoader classLoader = IDriverManager.getClassLoader(driverTypeEnum); - //IDataSource dataSource = new IDataSource(connectInfo, driverTypeEnum, classLoader); - //dataSource.setName(connectInfo.getAlias()); - //dataSource.setDriverClassLoader(classLoader); - //dataSource.setDriverClassName(driverTypeEnum.getDriverClass()); - //dataSource.setUrl(connectInfo.getUrl()); - //dataSource.setInitialSize(2); - //dataSource.setMinIdle(0); - //dataSource.setMaxActive(5); - //dataSource.setMaxWait(3000L); - //dataSource.setMinEvictableIdleTimeMillis(300000L); - //dataSource.setUsername(connectInfo.getUser()); - //dataSource.setPassword(connectInfo.getPassword()); - //dataSource.setConnectionErrorRetryAttempts(2); - //dataSource.setBreakAfterAcquireFailure(true); - //dataSource.setRemoveAbandoned(true); - //dataSource.setRemoveAbandonedTimeout(1800); - //dataSource.setTestOnBorrow(true); - //dataSource.setValidationQuery("select 1"); - //if (!ObjectUtils.isEmpty(connectInfo.getExtendMap())) { - // Properties properties = new Properties(); - // properties.putAll(connectInfo.getExtendMap()); - // dataSource.setConnectProperties(properties); - //} - //return dataSource; - Thread.currentThread().setContextClassLoader(classLoader); - log.info("createDataSource classLoader hashCode:{}"+ classLoader.hashCode()); - log.info("createDataSource classLoader url :{}"+ JSON.toJSONString (((URLClassLoader)classLoader).getURLs())); - HikariDataSource myDataSource = (HikariDataSource)classLoader.loadClass("com.zaxxer.hikari.HikariDataSource") - .newInstance(); - myDataSource.setDriverClassName(driverTypeEnum.getDriverClass()); - myDataSource.setJdbcUrl(connectInfo.getUrl()); - myDataSource.setUsername(connectInfo.getUser()); - myDataSource.setPassword(connectInfo.getPassword()); - myDataSource.setAutoCommit(true); - myDataSource.setConnectionTimeout(30000); - myDataSource.setIdleTimeout(600000); - myDataSource.setMaximumPoolSize(5); - myDataSource.setMinimumIdle(1); - myDataSource.setPoolName(connectInfo.getAlias()); - myDataSource.setConnectionTestQuery("select 1"); - if (!ObjectUtils.isEmpty(connectInfo.getExtendMap())) { - Properties properties = new Properties(); - properties.putAll(connectInfo.getExtendMap()); - myDataSource.setDataSourceProperties(properties); - } - Connection connection = myDataSource.getConnection(); - if (connection != null) {connection.close();} - return new MyDataSource(connectInfo, myDataSource); - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/IDataSource.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/IDataSource.java deleted file mode 100644 index 07804ec35..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/IDataSource.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.datasource; - -import java.io.Serial; -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; -import ai.chat2db.server.domain.support.model.SSHInfo; -import ai.chat2db.server.domain.support.sql.ConnectInfo; -import ai.chat2db.server.domain.support.sql.SSHManager; -import com.alibaba.druid.pool.DruidDataSource; - -import com.jcraft.jsch.Session; - -/** - * @author jipengfei - * @version : DbhubDataSource.java - */ -public class IDataSource extends DruidDataSource { - @Serial - private static final long serialVersionUID = -232274227856574115L; - - public ConnectInfo getConnectInfo() { - return connectInfo; - } - - private ConnectInfo connectInfo; - - public IDataSource(ConnectInfo connectInfo, DriverTypeEnum driverTypeEnum, ClassLoader classLoader) { - this.connectInfo = connectInfo; - - } - - @Override - public void init() throws SQLException { - if (inited) { - return; - } - connectSession(); - super.init(); - inited = true; - } - - private void connectSession() { - SSHInfo ssh = connectInfo.getSsh(); - if (ssh != null && ssh.isUse()) { - Session session = SSHManager.getSSHSession(ssh); - String url = connectInfo.getUrl(); - url = url.replace(connectInfo.getHost(), "127.0.0.1").replace(connectInfo.getPort() + "", - ssh.getLocalPort()); - setUrl(url); - } - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/MyDataSource.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/MyDataSource.java deleted file mode 100644 index ea317e678..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/datasource/MyDataSource.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.datasource; - -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; -import ai.chat2db.server.domain.support.sql.ConnectInfo; - -import com.zaxxer.hikari.HikariDataSource; - -/** - * @author jipengfei - * @version : MyDataSource.java - */ -public class MyDataSource { - - public ConnectInfo getConnectInfo() { - return connectInfo; - } - - private ConnectInfo connectInfo; - - public HikariDataSource getHikariDataSource() { - return hikariDataSource; - } - - private HikariDataSource hikariDataSource; - - public MyDataSource(ConnectInfo connectInfo, HikariDataSource hikariDataSource) { - this.connectInfo = connectInfo; - this.hikariDataSource = hikariDataSource; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMapper.java deleted file mode 100644 index 4105bc176..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMapper.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect; - -import java.util.List; - -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.model.TableColumn; -import ai.chat2db.server.domain.support.model.TableIndexColumn; -import org.apache.ibatis.annotations.Param; - -/** - * @author jipengfei - * @version : BaseMapper.java - */ -public interface BaseMapper { - /** - * 查询Database - * - * @return - */ - List showDatabases(); - - /** - * 查询所有表中所有列信息 - * - * @param databaseName - * @param tableName - * @return - */ - List selectColumns(@Param("databaseName") String databaseName,@Param("tableSchema") String tableSchema, @Param("tableName") String tableName); - - /** - * 删除表 - * - * @param databaseName - * @param tableName - */ - void dropTable(@Param("databaseName") String databaseName, @Param("tableName") String tableName); - - /** - * 查询所有的表 - * - * @param databaseName - * @param tableSchema - * @return - */ - List
selectTables(@Param("databaseName") String databaseName, @Param("tableSchema") String tableSchema,@Param("tableName") String tableName); - - /** - * @param databaseName - * @return - */ - Long selectTableCount(@Param("databaseName") String databaseName); - - /** - * 查询建表语句 - * - * @param databaseName - * @param tableName - * @return - */ - String showCreateTable(@Param("databaseName") String databaseName,@Param("tableSchema") String tableSchema, - @Param("tableName") String tableName); - - /** - * 查询表索引信息 - * - * @param databaseName - * @param tableName - * @return - */ - List selectTableIndexes(@Param("databaseName") String databaseName,@Param("tableSchema") String tableSchema, - @Param("tableName") String tableName); -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMetaSchema.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMetaSchema.java deleted file mode 100644 index da1b731d5..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/BaseMetaSchema.java +++ /dev/null @@ -1,145 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect; - -import java.util.List; - -import ai.chat2db.server.domain.support.dialect.common.SQLParam; -import ai.chat2db.server.domain.support.dialect.common.SQLType; -import ai.chat2db.server.domain.support.model.Function; -import ai.chat2db.server.domain.support.model.Procedure; -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.model.TableColumn; -import ai.chat2db.server.domain.support.model.TableIndex; -import ai.chat2db.server.domain.support.model.Trigger; -import ai.chat2db.server.domain.support.sql.SQLExecutor; -import com.alibaba.druid.sql.parser.DbhubSQLParserUtils; - -/** - * @author jipengfei - * @version : BaseMetaSchema.java - */ -public abstract class BaseMetaSchema implements MetaSchema { - - @Override - public List databases() { - return SQLExecutor.getInstance().databases(); - } - - public String getSQL(SQLType sqlType, SQLParam params) { - switch (sqlType) { - case CREATE_DATABASE: - return "CREATE DATABASE " + params.getDatabaseName(); - case DROP_DATABASE: - return "DROP DATABASE " + params.getDatabaseName(); - case MODIFY_DATABASE: - return "ALTER DATABASE " + params.getDatabaseName() + " RENAME TO " + params.getNewDatabaseName(); - case CREATE_SCHEMA: - return "CREATE SCHEMA " + params.getSchemaName(); - case DROP_SCHEMA: - return "DROP SCHEMA " + params.getSchemaName(); - case MODIFY_SCHEMA: - return "ALTER SCHEMA " + params.getSchemaName() + " RENAME TO " + params.getNewSchemaName(); - } - return null; - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - String sql = getSQL(SQLType.MODIFY_DATABASE, SQLParam.builder().databaseName(databaseName) - .newDatabaseName(newDatabaseName).build()); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public void createDatabase(String databaseName) { - String sql = getSQL(SQLType.CREATE_DATABASE, SQLParam.builder().databaseName(databaseName).build()); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public void dropDatabase(String databaseName) { - String sql = getSQL(SQLType.DROP_DATABASE, SQLParam.builder().databaseName(databaseName).build()); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public void createSchema(String databaseName, String schemaName) { - String sql = getSQL(SQLType.CREATE_SCHEMA, SQLParam.builder().databaseName(databaseName).schemaName(schemaName) - .build()); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - String sql = getSQL(SQLType.DROP_SCHEMA, SQLParam.builder().databaseName(databaseName).schemaName(schemaName) - .build()); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - String sql = getSQL(SQLType.MODIFY_SCHEMA, SQLParam.builder().databaseName(databaseName).schemaName(schemaName) - .newSchemaName(newSchemaName).build()); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public List schemas(String databaseName) { - return SQLExecutor.getInstance().schemas(databaseName, null); - } - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - return null; - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - String sql = "drop table " + DbhubSQLParserUtils.format(dbType(),tableName); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); - } - - @Override - public List
tables(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(databaseName, schemaName, tableName, new String[] {"TABLE"}); - } - - @Override - public List views(String databaseName, String schemaName) { - return SQLExecutor.getInstance().tables(databaseName, schemaName, null, new String[] {"VIEW"}); - } - - @Override - public List functions(String databaseName, String schemaName) { - return SQLExecutor.getInstance().functions(databaseName, schemaName); - } - - @Override - public List triggers(String databaseName, String schemaName) { - return null; - } - - @Override - public List procedures(String databaseName, String schemaName) { - return SQLExecutor.getInstance().procedures(databaseName, schemaName); - } - - @Override - public List columns(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().columns(databaseName, schemaName, tableName, null); - } - - @Override - public List columns(String databaseName, String schemaName, String tableName, - String columnName) { - return SQLExecutor.getInstance().columns(databaseName, schemaName, tableName, columnName); - } - - @Override - public List indexes(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().indexes(databaseName, schemaName, tableName); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/MetaSchema.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/MetaSchema.java deleted file mode 100644 index f8f60e0e0..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/MetaSchema.java +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect; - -import java.util.List; - -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.model.Function; -import ai.chat2db.server.domain.support.model.Procedure; -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.model.TableColumn; -import ai.chat2db.server.domain.support.model.TableIndex; -import ai.chat2db.server.domain.support.model.Trigger; -import jakarta.validation.constraints.NotEmpty; - -/** - * @author jipengfei - * @version : MetaSchemaManager.java, v 0.1 2022年12月14日 16:26 jipengfei Exp $ - */ -public interface MetaSchema { - /** - * 支持的数据库类型 - * - * @return - */ - DbTypeEnum dbType(); - - /** - * 查询所有的DATABASE - * - * @return - */ - List databases(); - - /** - * 修改数据库名称 - * @param databaseName - * @param newDatabaseName - */ - void modifyDatabase(String databaseName, String newDatabaseName); - - - /** - * 创建数据库 - * @param databaseName - */ - void createDatabase(String databaseName); - - - /** - * 删除数据库 - * @param databaseName - */ - void dropDatabase(String databaseName); - - /** - * 查询 DB 下schemas - * @param databaseName - * @return - */ - List schemas(String databaseName); - - /** - * 创建schema - * @param databaseName - * @param schemaName - */ - void createSchema(String databaseName, String schemaName); - - /** - * 删除schema - * @param databaseName - * @param schemaName - */ - void dropSchema(String databaseName, String schemaName); - - /** - * 修改schema - * @param databaseName - * @param schemaName - * @param newSchemaName - */ - void modifySchema(String databaseName, String schemaName, String newSchemaName); - - /** - * 展示建表语句 - * - * @param databaseName - * @param tableName - * @return - */ - String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); - - /** - * 删除表结构 - * - * @param databaseName - * @param tableName - * @return - */ - void dropTable(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); - - /** - * 分页查询表信息 - * - * @param databaseName - * @return - */ - List
tables(@NotEmpty String databaseName, String schemaName,String tableName); - - /** - * 查询所有视图 - * - * @param databaseName - * @param schemaName - * @return - */ - List views(@NotEmpty String databaseName, String schemaName); - - /** - * 查询所有的函数 - * - * @param databaseName - * @param schemaName - * @return - */ - List functions(@NotEmpty String databaseName, String schemaName); - - /** - * 查询所有触发器 - * - * @param databaseName - * @param schemaName - * @return - */ - List triggers(@NotEmpty String databaseName, String schemaName); - - /** - * 查询所有存储过程 - * - * @param databaseName - * @param schemaName - * @return - */ - List procedures(@NotEmpty String databaseName, String schemaName); - - /** - * 查询列的信息 - * - * @param databaseName - * @param tableName - * @return - */ - List columns(@NotEmpty String databaseName, String schemaName, - @NotEmpty String tableName); - - - /** - * 查询database下所有的列信息 - * - * @param databaseName - * @param schemaName - * @param tableName - * @param columnName - * @return - */ - List columns(@NotEmpty String databaseName, String schemaName,String tableName, String columnName); - - /** - * 查询列的信息 - * - * @param databaseName - * @param tableName * @return - */ - List indexes(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); - - - - //T getMapper(); - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/clickhouse/ClickhouseMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/clickhouse/ClickhouseMetaSchemaSupport.java deleted file mode 100644 index 3eef146bf..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/clickhouse/ClickhouseMetaSchemaSupport.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.clickhouse; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * @author jipengfei - * @version : ClickhouseMetaSchemaSupport.java - */ -public class ClickhouseMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.CLICKHOUSE; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLKeyConst.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLKeyConst.java deleted file mode 100644 index e6f75613b..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLKeyConst.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.common; - -/** - * @author jipengfei - * @version : SQLConst.java, v 0.1 2022年12月08日 15:08 jipengfei Exp $ - */ -public class SQLKeyConst { - public static final String PG_CREATE_TABLE_SIMPLE = "create table if not exists main.test_table( column_1 serial, column_2 varchar default 'dd'::character varying, column_3 bigserial, column_4 integer default 100, column_5 varchar not null constraint test_table_pk primary key, column_6 varchar, column_7 integer); comment on table main.test_table is '测试表'; comment on column main.test_table.column_6 is '设置备注'; alter table main.test_table owner to ali_dbhub_test; create index if not exists test_table_column_2_index on main.test_table (column_2); create unique index if not exists test_table_column_2_uindex on main.test_table (column_2); comment on index main.test_table_column_2_uindex is 'add'; "; - public static final String PG_ALTER_TABLE_SIMPLE = "alter table main.test_table rename column column_1 to column_001; alter table main.test_table add column_8 integer not null; create index test_table_column_8_index on main.test_table(column_8); "; - - public static final String MYSQL_CREATE_TABLE_SIMPLE = "CREATE TABLE `test`( `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', `date` datetime NULL COMMENT '日期', `string` varchar(128) NOT NULL DEFAULT 'Test' COMMENT '字符串', PRIMARY KEY (`id`), KEY `idx_string` (`string`)) DEFAULT CHARACTER SET=utf8mb4 COMMENT='测试表'; "; - public static final String MYSQL_ALTER_TABLE_SIMPLE = "ALTER TABLE `test` ADD COLUMN `number` bigint unsigned NULL COMMENT '数字'; ALTER TABLE `test` ADD UNIQUE INDEX uk_number(number); ALTER TABLE `test` DROP COLUMN `number`; "; - - - public static final String H2_CREATE_TABLE_SIMPLE = "CREATE TABLE `test`( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `gmt_modified` datetime NULL DEFAULT CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP COMMENT '修改时间', `date` datetime NULL COMMENT '日期', `string` varchar(128) NOT NULL DEFAULT 'Test' COMMENT '字符串', PRIMARY KEY (`id`));"; - - public static final String H2_ALTER_TABLE_SIMPLE = "ALTER TABLE `test` ADD COLUMN `number` bigint NULL COMMENT '数字'; CREATE UNIQUE INDEX uk_number ON `test`(`number`); ALTER TABLE `test` DROP COLUMN `number`;"; - - - public static final String ORACLE_CREATE_TABLE_SIMPLE = "create table if not exists main.test_table( column_1 serial, column_2 varchar default 'dd'::character varying, column_3 bigserial, column_4 integer default 100, column_5 varchar not null constraint test_table_pk primary key, column_6 varchar, column_7 integer); comment on table main.test_table is '测试表'; comment on column main.test_table.column_6 is '设置备注'; alter table main.test_table owner to ali_dbhub_test; create index if not exists test_table_column_2_index on main.test_table (column_2); create unique index if not exists test_table_column_2_uindex on main.test_table (column_2); comment on index main.test_table_column_2_uindex is 'add'; "; - public static final String ORACLE_ALTER_TABLE_SIMPLE = "alter table main.test_table rename column column_1 to column_001; alter table main.test_table add column_8 integer not null; create index test_table_column_8_index on main.test_table(column_8); "; - - - public static final String SQLSERVER_CREATE_TABLE_SIMPLE = "CREATE TABLE [dbo].[table_name] ( [id] bigint NOT NULL, [date] datetime NOT NULL, [String] varchar(1) NOT NULL, [number] bigint NULL);CREATE UNIQUE CLUSTERED INDEX [id] ON [dbo].[table_name] ( [id] ASC);CREATE NONCLUSTERED INDEX [table_name_date_index] ON [dbo].[table_name] ( [date] ASC);CREATE NONCLUSTERED INDEX [table_name_String_index] ON [dbo].[table_name] ( [String] ASC);CREATE UNIQUE NONCLUSTERED INDEX [table_name_pk] ON [dbo].[table_name] ( [number] ASC);EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'id';EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'date';EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'String';EXEC sp_addextendedproperty @name=N'MS_Description', @value=N'mmm', @level0type=N'SCHEMA', @level0name=N'dbo', @level1type=N'TABLE', @level1name=N'table_name', @level2type=N'COLUMN', @level2name=N'number';"; - - public static final String SQLSERVER_ALTER_TABLE_SIMPLE = "exec sp_addextendedproperty 'MS_Description', 'mm', 'SCHEMA', 'dbo', 'TABLE', 'table_name', 'COLUMN', 'id' go"; - - - public static final String SQLITE_CREATE_TABLE_SIMPLE = "CREATE TABLE person (\n" - + " id INTEGER PRIMARY KEY AUTOINCREMENT,\n" - + " name TEXT NOT NULL,\n" - + " age INTEGER\n" - + ");"; - public static final String SQLITE_ALTER_TABLE_SIMPLE = "ALTER TABLE person ADD COLUMN address TEXT;"; - - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLParam.java deleted file mode 100644 index c60cc2b94..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLParam.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.common; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : SQLParam.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class SQLParam { - private String databaseName; - private String newDatabaseName; - private String schemaName; - private String newSchemaName; - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLType.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLType.java deleted file mode 100644 index 7a00ae5b9..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/SQLType.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.common; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 需要执行的sql类型 - * - * @author jipengfei - * @version : SQLType.java - */ -public enum SQLType { - - MODIFY_DATABASE, - - CREATE_DATABASE, - - DROP_DATABASE, - - CREATE_SCHEMA, - DROP_SCHEMA, - MODIFY_SCHEMA; - - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseCollationEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseCollationEnum.java deleted file mode 100644 index 6f40ae456..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseCollationEnum.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.common.enums; - -import ai.chat2db.server.domain.support.enums.CollationEnum; -import ai.chat2db.server.tools.base.enums.BaseEnum; - -/** - * 排序类型 - * - * @author Jiaju Zhuang - */ -public interface BaseCollationEnum extends BaseEnum { - - /** - * 返回排序类型 - * - * @return - */ - CollationEnum getCollation(); -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseColumnTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseColumnTypeEnum.java deleted file mode 100644 index 03e16a885..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseColumnTypeEnum.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.common.enums; - -import ai.chat2db.server.domain.support.enums.ColumnTypeEnum; -import ai.chat2db.server.tools.base.enums.BaseEnum; - -/** - * 列的类型 - * - * @author Jiaju Zhuang - */ -public interface BaseColumnTypeEnum extends BaseEnum { - - /** - * 返回列的类型 - * - * @return - */ - ColumnTypeEnum getColumnType(); -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseIndexTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseIndexTypeEnum.java deleted file mode 100644 index 805401c59..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/enums/BaseIndexTypeEnum.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.common.enums; - -import ai.chat2db.server.domain.support.enums.IndexTypeEnum; -import ai.chat2db.server.tools.base.enums.BaseEnum; - -/** - * 索引的类型 - * - * @author Jiaju Zhuang - */ -public interface BaseIndexTypeEnum extends BaseEnum { - - /** - * 返回索引的类型 - * - * @return - */ - IndexTypeEnum getIndexType(); -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/handler/BooleanTypeHandler.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/handler/BooleanTypeHandler.java deleted file mode 100644 index a991ae299..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/handler/BooleanTypeHandler.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.common.handler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import ai.chat2db.server.tools.base.enums.YesOrNoEnum; - -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -/** - * @author jipengfei - * @version : NullableTypeHandler.java - */ -public class BooleanTypeHandler implements TypeHandler { - - @Override - public void setParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException { - } - - @Override - public Boolean getResult(ResultSet rs, String columnName) throws SQLException { - return parse(rs.getString(columnName)); - } - - @Override - public Boolean getResult(ResultSet rs, int columnIndex) throws SQLException { - return parse(rs.getString(columnIndex)); - - } - - @Override - public Boolean getResult(CallableStatement cs, int columnIndex) throws SQLException { - return parse(cs.getString(columnIndex)); - } - - private Boolean parse(String result) { - if (YesOrNoEnum.YES.getCode().equalsIgnoreCase(result)) { - return Boolean.TRUE; - } - return Boolean.FALSE; - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/model/SpiExample.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/model/SpiExample.java deleted file mode 100644 index 54370fa6c..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/common/model/SpiExample.java +++ /dev/null @@ -1,28 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.common.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 表信息 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class SpiExample { - /** - * 建表语句 - */ - private String createTable; - - /** - * 修改表结构 - */ - private String alterTable; -} - diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/db2/DB2MetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/db2/DB2MetaSchemaSupport.java deleted file mode 100644 index a2673cf7e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/db2/DB2MetaSchemaSupport.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.db2; - -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; - -/** - * @author jipengfei - * @version : DB2MetaSchemaSupport.java - */ -public class DB2MetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - private String functionSQL - = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" - + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " - + "NVARCHAR(MAX) = ''; DECLARE @ColumnDescriptions NVARCHAR(MAX) = N''; SELECT @CreateTableScript = CONCAT( " - + "'CREATE TABLE [', s.name, '].[' , t.name, '] (', STUFF( ( SELECT ', [' + c.name + '] ' + tp.name + CASE " - + "WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + IIF(c.max_length = -1, 'MAX', CAST(c" - + ".max_length AS NVARCHAR(10))) + ')' WHEN tp.name IN ('decimal', 'numeric') THEN '(' + CAST(c.precision AS " - + "NVARCHAR(10)) + ', ' + CAST(c.scale AS NVARCHAR(10)) + ')' ELSE '' END + ' ' + CASE WHEN c.is_nullable = 1" - + " THEN 'NULL' ELSE 'NOT NULL' END FROM sys.columns c JOIN sys.types tp ON c.user_type_id = tp.user_type_id " - + "WHERE c.object_id = t.object_id FOR XML PATH(''), TYPE ).value('/', 'nvarchar(max)'), 1, 1, ''), ');' ) " - + "FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name = @table_name AND s.name = " - + "@schema_name; SELECT @IndexScripts = @IndexScripts + 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' " - + "ELSE '' END + i.type_desc + ' INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' + STUFF( ( " - + "SELECT ', [' + c.name + ']' + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys" - + ".index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic" - + ".object_id = i.object_id AND ic.index_id = i.index_id ORDER BY ic.key_ordinal FOR XML PATH('') ), 1, 1, " - + "'') + ')' + CASE WHEN i.has_filter = 1 THEN ' WHERE ' + i.filter_definition ELSE '' END + ';' + CHAR(13) +" - + " CHAR(10) FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t" - + ".schema_id = s.schema_id WHERE i.type > 0 AND t.name = @table_name AND s.name " - + "= @schema_name; SELECT @ColumnDescriptions += 'EXEC sp_addextendedproperty @name=N''MS_Description'', " - + "@value=N''' + CAST(p.value AS NVARCHAR(MAX)) + ''', @level0type=N''SCHEMA'', @level0name=N''' + " - + "@schema_name + ''', @level1type=N''TABLE'', @level1name=N''' + @table_name + ''', @level2type=N''COLUMN''," - + " @level2name=N''' + c.name + ''';' + CHAR(13) + CHAR(10) FROM sys.extended_properties p JOIN sys.columns c" - + " ON p.major_id = c.object_id AND p.minor_id = c.column_id JOIN sys.tables t ON c.object_id = t.object_id " - + "JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.class = 1 AND t.name = @table_name AND s.name = " - + "@schema_name; SET @CreateTableScript = @CreateTableScript + CHAR(13) + CHAR(10) + @IndexScripts + CHAR(13)" - + " + CHAR(10)+ @ColumnDescriptions+ CHAR(10); RETURN @CreateTableScript; END"; - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.SQLSERVER; - } - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - try { - System.out.println(functionSQL); - SQLExecutor.getInstance().executeSql(functionSQL.replace("tableSchema", schemaName), resultSet -> null); - } catch (Exception e) { - //log.error("创建函数失败", e); - } - - String ddlSql = "SELECT " + schemaName + ".ufn_GetCreateTableScript('" + schemaName + "', '" + tableName - + "') AS sql"; - return SQLExecutor.getInstance().executeSql(ddlSql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/dm/DMMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/dm/DMMetaSchemaSupport.java deleted file mode 100644 index cdd1825d6..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/dm/DMMetaSchemaSupport.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.dm; - -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; -import ai.chat2db.server.domain.support.util.SqlUtils; - -/** - * @author jipengfei - * @version : DMMetaSchemaSupport.java - */ -public class DMMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.DM; - } - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - String selectObjectDDLSQL = String.format( - "select dbms_metadata.get_ddl(%s, %s, %s) AS \"sql\" from dual", - SqlUtils.formatSQLString("TABLE"), SqlUtils.formatSQLString(tableName), - SqlUtils.formatSQLString(schemaName)); - return SQLExecutor.getInstance().executeSql(selectObjectDDLSQL, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2CollationEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2CollationEnum.java deleted file mode 100644 index a72055d19..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2CollationEnum.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.h2; - -import ai.chat2db.server.domain.support.enums.CollationEnum; -import ai.chat2db.server.domain.support.dialect.common.enums.BaseCollationEnum; - -import lombok.Getter; - -/** - * 排序枚举 - * - * @author Jiaju Zhuang - */ -@Getter -public enum H2CollationEnum implements BaseCollationEnum { - /** - * ASC - */ - ASC("ASC", CollationEnum.ASC), - /** - * DESC - */ - DESC("DESC", CollationEnum.DESC), - ; - - final String code; - final CollationEnum collation; - - H2CollationEnum(String code, CollationEnum collation) { - this.code = code; - this.collation = collation; - } - - @Override - public String getDescription() { - return getCode(); - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2ColumnTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2ColumnTypeEnum.java deleted file mode 100644 index 78e5a990f..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2ColumnTypeEnum.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.h2; - -import ai.chat2db.server.domain.support.dialect.common.enums.BaseColumnTypeEnum; -import ai.chat2db.server.domain.support.enums.ColumnTypeEnum; - -import lombok.Getter; - -/** - * 列的类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum H2ColumnTypeEnum implements BaseColumnTypeEnum { - /** - * BIGINT - */ - BIGINT("BIGINT", ColumnTypeEnum.BIGINT), - /** - * BIGINT - */ - CHARACTER_VARYING("CHARACTER VARYING", ColumnTypeEnum.VARCHAR), - /** - * TIMESTAMP - */ - TIMESTAMP("TIMESTAMP", ColumnTypeEnum.TIMESTAMP), - /** - * INTEGER - */ - INTEGER("INTEGER", ColumnTypeEnum.INTEGER), - ; - - final String code; - final ColumnTypeEnum columnType; - - H2ColumnTypeEnum(String code, ColumnTypeEnum columnType) { - this.code = code; - this.columnType = columnType; - } - - @Override - public String getDescription() { - return getCode(); - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2IndexTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2IndexTypeEnum.java deleted file mode 100644 index 0611edaa6..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2IndexTypeEnum.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.h2; - -import ai.chat2db.server.domain.support.enums.IndexTypeEnum; -import ai.chat2db.server.domain.support.dialect.common.enums.BaseIndexTypeEnum; - -import lombok.Getter; - -/** - * 列的类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum H2IndexTypeEnum implements BaseIndexTypeEnum { - /** - * PRIMARY_KEY - */ - PRIMARY_KEY("PRIMARY KEY", IndexTypeEnum.PRIMARY_KEY), - /** - * UNIQUE INDEX - */ - UNIQUE("UNIQUE INDEX", IndexTypeEnum.UNIQUE), - /** - * NORMAL - */ - NORMAL("INDEX", IndexTypeEnum.NORMAL), - ; - - final String code; - final IndexTypeEnum indexType; - - H2IndexTypeEnum(String code, IndexTypeEnum indexType) { - this.code = code; - this.indexType = indexType; - } - - @Override - public String getDescription() { - return getCode(); - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2MetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2MetaSchemaSupport.java deleted file mode 100644 index c191c9b97..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/H2MetaSchemaSupport.java +++ /dev/null @@ -1,109 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.h2; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; -import jakarta.validation.constraints.NotEmpty; -import lombok.extern.slf4j.Slf4j; - -/** - * @author jipengfei - * @version : H2MetaSchemaSupport.java - */ -@Slf4j -public class H2MetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.H2; - - } - - @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - return getDDL(databaseName, schemaName, tableName); - } - - - private String getDDL(String databaseName, String schemaName, String tableName) { - try { - Connection connection = SQLExecutor.getInstance().getConnection(); - // 查询表结构信息 - ResultSet columns = connection.getMetaData().getColumns(databaseName, schemaName, tableName, null); - List columnDefinitions = new ArrayList<>(); - while (columns.next()) { - String columnName = columns.getString("COLUMN_NAME"); - String columnType = columns.getString("TYPE_NAME"); - int columnSize = columns.getInt("COLUMN_SIZE"); - String remarks = columns.getString("REMARKS"); - String defaultValue = columns.getString("COLUMN_DEF"); - String nullable = columns.getInt("NULLABLE") == ResultSetMetaData.columnNullable ? "NULL" : "NOT NULL"; - StringBuilder columnDefinition = new StringBuilder(); - columnDefinition.append(columnName).append(" ").append(columnType); - if (columnSize != 0) { - columnDefinition.append("(").append(columnSize).append(")"); - } - columnDefinition.append(" ").append(nullable); - if (defaultValue != null) { - columnDefinition.append(" DEFAULT ").append(defaultValue); - } - if (remarks != null) { - columnDefinition.append(" COMMENT '").append(remarks).append("'"); - } - columnDefinitions.add(columnDefinition.toString()); - } - - // 查询表索引信息 - ResultSet indexes = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, - false); - Map> indexMap = new HashMap<>(); - while (indexes.next()) { - String indexName = indexes.getString("INDEX_NAME"); - String columnName = indexes.getString("COLUMN_NAME"); - if (indexName != null) { - if (!indexMap.containsKey(indexName)) { - indexMap.put(indexName, new ArrayList<>()); - } - indexMap.get(indexName).add(columnName); - } - } - StringBuilder createTableDDL = new StringBuilder("CREATE TABLE "); - createTableDDL.append(tableName).append(" (\n"); - createTableDDL.append(String.join(",\n", columnDefinitions)); - createTableDDL.append("\n);\n"); - - System.out.println("DDL建表语句:"); - System.out.println(createTableDDL.toString()); - - // 输出索引信息 - System.out.println("\nDDL索引语句:"); - for (Map.Entry> entry : indexMap.entrySet()) { - String indexName = entry.getKey(); - List columnList = entry.getValue(); - String indexColumns = String.join(", ", columnList); - String createIndexDDL = String.format("CREATE INDEX %s ON %s (%s);", indexName, tableName, - indexColumns); - System.out.println(createIndexDDL); - createTableDDL.append(createIndexDDL); - } - return createTableDDL.toString(); - - } catch (Exception e) { - e.printStackTrace(); - } - return ""; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/handler/H2IndexTypeHandler.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/handler/H2IndexTypeHandler.java deleted file mode 100644 index 64be534ef..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/h2/handler/H2IndexTypeHandler.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.h2.handler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.h2.H2IndexTypeEnum; - -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -/** - * @author jipengfei - * @version : IndexTypeHandler.java, v 0.1 2022年12月15日 10:36 jipengfei Exp $ - */ -public class H2IndexTypeHandler implements TypeHandler { - @Override - public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { - } - - @Override - public String getResult(ResultSet rs, String columnName) throws SQLException { - return parse(rs.getString(columnName)); - } - - @Override - public String getResult(ResultSet rs, int columnIndex) throws SQLException { - return parse(rs.getString(columnIndex)); - - } - - @Override - public String getResult(CallableStatement cs, int columnIndex) throws SQLException { - return parse(cs.getString(columnIndex)); - } - - - private String parse(String result) { - - if (H2IndexTypeEnum.PRIMARY_KEY.getCode().equalsIgnoreCase(result)) { - return H2IndexTypeEnum.PRIMARY_KEY.getIndexType().getCode(); - } else if (H2IndexTypeEnum.UNIQUE.getCode().equalsIgnoreCase(result)) { - return H2IndexTypeEnum.UNIQUE.getIndexType().getCode(); - } else { - return H2IndexTypeEnum.NORMAL.getIndexType().getCode(); - } - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/hive/HiveMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/hive/HiveMetaSchemaSupport.java deleted file mode 100644 index 46bf19856..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/hive/HiveMetaSchemaSupport.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.hive; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * @author jipengfei - * @version : HiveMetaSchemaSupport.java - */ -public class HiveMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.HIVE; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/kingbase/KingBaseSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/kingbase/KingBaseSchemaSupport.java deleted file mode 100644 index c0cc57f5b..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/kingbase/KingBaseSchemaSupport.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.kingbase; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * @author jipengfei - * @version : H2MetaSchemaSupport.java - */ -public class KingBaseSchemaSupport extends BaseMetaSchema implements MetaSchema { - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.KINGBASE; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mariadb/MariaDBMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mariadb/MariaDBMetaSchemaSupport.java deleted file mode 100644 index ea708b91e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mariadb/MariaDBMetaSchemaSupport.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mariadb; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.dialect.mysql.MysqlMetaSchemaSupport; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * @author jipengfei - * @version : MariaDBMetaSchemaSupport.java - */ -public class MariaDBMetaSchemaSupport extends MysqlMetaSchemaSupport implements MetaSchema { - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.MARIADB; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mongodb/MongodbMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mongodb/MongodbMetaSchemaSupport.java deleted file mode 100644 index 8f9c5ad14..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mongodb/MongodbMetaSchemaSupport.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mongodb; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * @author jipengfei - * @version : MongodbMetaSchemaSupport.java - */ -public class MongodbMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.MONGODB; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlCollationEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlCollationEnum.java deleted file mode 100644 index 2d276f926..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlCollationEnum.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.chat2db.server.domain.support.dialect.mysql; - -import ai.chat2db.server.domain.support.enums.CollationEnum; -import ai.chat2db.server.domain.support.dialect.common.enums.BaseCollationEnum; - -import lombok.Getter; - -/** - * 排序枚举 - * - * @author Jiaju Zhuang - */ -@Getter -public enum MysqlCollationEnum implements BaseCollationEnum { - /** - * ASC - */ - ASC("A", CollationEnum.ASC), - /** - * DESC - */ - DESC("D", CollationEnum.DESC), - ; - - final String code; - final CollationEnum collation; - - MysqlCollationEnum(String code, CollationEnum collation) { - this.code = code; - this.collation = collation; - } - - @Override - public String getDescription() { - return getCode(); - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlMetaSchemaSupport.java deleted file mode 100644 index 0d22ccc39..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/MysqlMetaSchemaSupport.java +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mysql; - -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; -import jakarta.validation.constraints.NotEmpty; -import lombok.extern.slf4j.Slf4j; - -import static com.alibaba.druid.sql.parser.DbhubSQLParserUtils.format; - -/** - * @author jipengfei - * @version : MysqlMetaSchemaSupport.java, v 0.1 2022年12月14日 22:44 jipengfei Exp $ - */ -@Slf4j -public class MysqlMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.MYSQL; - } - - @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - String sql = "SHOW CREATE TABLE " + format(dbType(), databaseName) + "." - + format(dbType(), tableName); - return SQLExecutor.getInstance().executeSql(sql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("Create Table"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlCollationTypeHandler.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlCollationTypeHandler.java deleted file mode 100644 index 925c380b5..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlCollationTypeHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mysql.handler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.mysql.MysqlCollationEnum; - -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -/** - * - * @author jipengfei - * @version : MysqlCollationTypeHandler.java - */ -public class MysqlCollationTypeHandler implements TypeHandler { - @Override - public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { - - } - - @Override - public String getResult(ResultSet rs, String columnName) throws SQLException { - if (MysqlCollationEnum.DESC.getCode().equalsIgnoreCase(rs.getString(columnName))) { - return MysqlCollationEnum.DESC.getCollation().getCode(); - } else { - return MysqlCollationEnum.ASC.getCollation().getCode(); - } - } - - @Override - public String getResult(ResultSet rs, int columnIndex) throws SQLException { - if (MysqlCollationEnum.DESC.getCode().equalsIgnoreCase(rs.getString(columnIndex))) { - return MysqlCollationEnum.DESC.getCollation().getCode(); - } else { - return MysqlCollationEnum.ASC.getCollation().getCode(); - } - } - - @Override - public String getResult(CallableStatement cs, int columnIndex) throws SQLException { - if (MysqlCollationEnum.DESC.getCode().equalsIgnoreCase(cs.getString(columnIndex))) { - return MysqlCollationEnum.DESC.getCollation().getCode(); - } else { - return MysqlCollationEnum.ASC.getCollation().getCode(); - } - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlColumnKeyHandler.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlColumnKeyHandler.java deleted file mode 100644 index 15b0e7bfe..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlColumnKeyHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mysql.handler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -/** - * ColumnKey专用的转换器 - * - * @author 是仪 - */ -public class MysqlColumnKeyHandler implements TypeHandler { - - @Override - public void setParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException { - } - - @Override - public Boolean getResult(ResultSet rs, String columnName) throws SQLException { - return parse(rs.getString(columnName)); - } - - @Override - public Boolean getResult(ResultSet rs, int columnIndex) throws SQLException { - return parse(rs.getString(columnIndex)); - - } - - @Override - public Boolean getResult(CallableStatement cs, int columnIndex) throws SQLException { - return parse(cs.getString(columnIndex)); - } - - private Boolean parse(String result) { - if ("PRI".equalsIgnoreCase(result)) { - return Boolean.TRUE; - } - return Boolean.FALSE; - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlExtraTypeHandler.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlExtraTypeHandler.java deleted file mode 100644 index c54ff408d..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlExtraTypeHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mysql.handler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -/** - * extra专用的转换器 - * - * @author 是仪 - */ -public class MysqlExtraTypeHandler implements TypeHandler { - - @Override - public void setParameter(PreparedStatement ps, int i, Boolean parameter, JdbcType jdbcType) throws SQLException { - } - - @Override - public Boolean getResult(ResultSet rs, String columnName) throws SQLException { - return parse(rs.getString(columnName)); - } - - @Override - public Boolean getResult(ResultSet rs, int columnIndex) throws SQLException { - return parse(rs.getString(columnIndex)); - - } - - @Override - public Boolean getResult(CallableStatement cs, int columnIndex) throws SQLException { - return parse(cs.getString(columnIndex)); - } - - private Boolean parse(String result) { - if ("auto_increment".equalsIgnoreCase(result)) { - return Boolean.TRUE; - } - return Boolean.FALSE; - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlIndexTypeHandler.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlIndexTypeHandler.java deleted file mode 100644 index d5c31b945..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/mysql/handler/MysqlIndexTypeHandler.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.mysql.handler; - -import java.sql.CallableStatement; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.enums.IndexTypeEnum; - -import org.apache.ibatis.type.JdbcType; -import org.apache.ibatis.type.TypeHandler; - -/** - * @author jipengfei - * @version : IndexTypeHandler.java, v 0.1 2022年12月15日 10:36 jipengfei Exp $ - */ -public class MysqlIndexTypeHandler implements TypeHandler { - @Override - public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { - } - - @Override - public String getResult(ResultSet rs, String columnName) throws SQLException { - return parse(rs.getString(columnName), rs.getString("INDEX_NAME")); - } - - @Override - public String getResult(ResultSet rs, int columnIndex) throws SQLException { - return parse(rs.getString(columnIndex), rs.getString("INDEX_NAME")); - } - - @Override - public String getResult(CallableStatement cs, int columnIndex) throws SQLException { - return parse(cs.getString(columnIndex), cs.getString("INDEX_NAME")); - } - - private String parse(String result, String indexName) { - if ("PRIMARY".equalsIgnoreCase(indexName)) { - return IndexTypeEnum.PRIMARY_KEY.getCode(); - } else { - if ("1".equalsIgnoreCase(result)) { - return IndexTypeEnum.NORMAL.getCode(); - } else { - return IndexTypeEnum.UNIQUE.getCode(); - } - } - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oceanbase/OceanBaseMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oceanbase/OceanBaseMetaSchemaSupport.java deleted file mode 100644 index 4285de265..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oceanbase/OceanBaseMetaSchemaSupport.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.oceanbase; - -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.dialect.mysql.MysqlMetaSchemaSupport; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * @author jipengfei - * @version : OceanBaseMetaSchemaSupport.java - */ -public class OceanBaseMetaSchemaSupport extends MysqlMetaSchemaSupport implements MetaSchema { - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.OCEANBASE; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oracle/OracleMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oracle/OracleMetaSchemaSupport.java deleted file mode 100644 index 88f6c1b6a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/oracle/OracleMetaSchemaSupport.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.oracle; - -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; - -/** - * @author jipengfei - * @version : OracleMetaSchemaSupport.java - */ -public class OracleMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.ORACLE; - } - -// @Override -// public List databases() { -// return super.schemas(null); -// } -// -// @Override -// public List schemas(String databaseName) { -// return Lists.newArrayList(); -// } - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - String sql = "select dbms_metadata.get_ddl('TABLE','"+tableName+"') as sql from dual," - + "user_tables where table_name = '" + tableName + "'"; - return SQLExecutor.getInstance().executeSql(sql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - - return null; - }); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/PostgresqlMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/PostgresqlMetaSchemaSupport.java deleted file mode 100644 index a7d03eb4f..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/PostgresqlMetaSchemaSupport.java +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql; - -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; - -import lombok.extern.slf4j.Slf4j; - -import static ai.chat2db.server.domain.support.enums.DbTypeEnum.POSTGRESQL; - -/** - * @author jipengfei - * @version : PostgresqlDataBase.java, v 0.1 2022年12月08日 14:48 jipengfei Exp $ - */ -@Slf4j -public class PostgresqlMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return POSTGRESQL; - } - - private String functionSQL = - " CREATE OR REPLACE FUNCTION showcreatetable(namespace character varying, tablename character " - + "varying)\n" - + " RETURNS character varying AS\n" - + "\n" - + " $BODY$\n" - + " declare\n" - + " tableScript character varying default '';\n" - + "\n" - + " begin\n" - + " -- columns\n" - + " tableScript:=tableScript || ' CREATE TABLE '|| tablename|| ' ( '|| chr(13)||chr(10) || " - + "array_to_string" - + "(\n" - + " array(\n" - + " select ' ' || concat_ws(' ',fieldName, fieldType, fieldLen, indexType, isNullStr, fieldComment" - + " ) as " - + "column_line\n" - + " from (\n" - + " select a.attname as fieldName,format_type(a.atttypid,a.atttypmod) as fieldType,(case when " - + "atttypmod-4>0 then\n" - + " atttypmod-4 else 0 end) as fieldLen,\n" - + " (case when (select count(*) from pg_constraint where conrelid = a.attrelid and conkey[1]=attnum " - + "and\n" - + " contype='p')>0 then 'PRI'\n" - + " when (select count(*) from pg_constraint where conrelid = a.attrelid and conkey[1]=attnum and " - + "contype='u')>0\n" - + " then 'UNI'\n" - + " when (select count(*) from pg_constraint where conrelid = a.attrelid and conkey[1]=attnum and " - + "contype='f')>0\n" - + " then 'FRI'\n" - + " else '' end) as indexType,\n" - + " (case when a.attnotnull=true then 'not null' else 'null' end) as isNullStr,\n" - + " ' comment ' || col_description(a.attrelid,a.attnum) as fieldComment\n" - + " from pg_attribute a where attstattarget=-1 and attrelid = (select c.oid from pg_class c," - + "pg_namespace n" - + " where\n" - + " c.relnamespace=n.oid and n.nspname =namespace and relname =tablename)\n" - + "\n" - + " ) as string_columns\n" - + " ),','||chr(13)||chr(10)) || ',';\n" - + "\n" - + "\n" - + " -- 约束\n" - + " tableScript:= tableScript || chr(13)||chr(10) || array_to_string(\n" - + " array(\n" - + " select concat(' CONSTRAINT ',conname ,c ,u,p,f) from (\n" - + " select conname,\n" - + " case when contype='c' then ' CHECK('|| ( select findattname(namespace,tablename,'c') ) ||')' " - + "end " - + "as c " - + ",\n" - + " case when contype='u' then ' UNIQUE('|| ( select findattname(namespace,tablename,'u') ) ||')' " - + "end " - + "as u" - + " ,\n" - + " case when contype='p' then ' PRIMARY KEY ('|| ( select findattname(namespace,tablename,'p') ) " - + "||')' " - + "end as p ,\n" - + " case when contype='f' then ' FOREIGN KEY('|| ( select findattname(namespace,tablename,'u') ) " - + "||') " - + "REFERENCES '||\n" - + " (select p.relname from pg_class p where p.oid=c.confrelid ) || '('|| ( select\n" - + " findattname(namespace,tablename,'u') ) ||')' end as f\n" - + " from pg_constraint c\n" - + " where contype in('u','c','f','p') and conrelid=(\n" - + " select oid from pg_class where relname=tablename and relnamespace =(\n" - + " select oid from pg_namespace where nspname = namespace\n" - + " )\n" - + " )\n" - + " ) as t\n" - + " ) ,',' || chr(13)||chr(10) ) || chr(13)||chr(10) ||' ); ';\n" - + "\n" - + " -- indexs\n" - + " -- CREATE UNIQUE INDEX pg_language_oid_index ON pg_language USING btree (oid); -- table " - + "pg_language\n" - + "\n" - + "\n" - + " --\n" - + " /** **/\n" - + " --- 获取非约束索引 column\n" - + " -- CREATE UNIQUE INDEX pg_language_oid_index ON pg_language USING btree (oid); -- table " - + "pg_language\n" - + " tableScript:= tableScript || chr(13)||chr(10) || chr(13)||chr(10) || array_to_string(\n" - + " array(\n" - + " select 'CREATE INDEX ' || indexrelname || ' ON ' || tablename || ' USING btree '|| '(' || " - + "attname " - + "|| " - + "');' from (\n" - + " SELECT\n" - + " i.relname AS indexrelname , x.indkey,\n" - + "\n" - + " ( select array_to_string (\n" - + " array(\n" - + " select a.attname from pg_attribute a where attrelid=c.oid and a.attnum in ( select unnest(x" - + ".indkey) )\n" - + "\n" - + " )\n" - + " ,',' ) )as attname\n" - + "\n" - + " FROM pg_class c\n" - + " JOIN pg_index x ON c.oid = x.indrelid\n" - + " JOIN pg_class i ON i.oid = x.indexrelid\n" - + " LEFT JOIN pg_namespace n ON n.oid = c.relnamespace\n" - + " WHERE c.relname=tablename and i.relname not in\n" - + " ( select constraint_name from information_schema.key_column_usage where table_name=tablename )\n" - + " )as t\n" - + " ) ,','|| chr(13)||chr(10));\n" - + "\n" - + "\n" - + " -- COMMENT COMMENT ON COLUMN sys_activity.id IS '主键';\n" - + " tableScript:= tableScript || chr(13)||chr(10) || chr(13)||chr(10) || array_to_string(\n" - + " array(\n" - + " SELECT 'COMMENT ON COLUMN' || tablename || '.' || a.attname ||' IS '|| ''''|| d.description " - + "||''''\n" - + " FROM pg_class c\n" - + " JOIN pg_description d ON c.oid=d.objoid\n" - + " JOIN pg_attribute a ON c.oid = a.attrelid\n" - + " WHERE c.relname=tablename\n" - + " AND a.attnum = d.objsubid),','|| chr(13)||chr(10)) ;\n" - + "\n" - + " return tableScript;\n" - + "\n" - + " end\n" - + " $BODY$ LANGUAGE plpgsql;\n" - + "\n" - + " CREATE OR REPLACE FUNCTION findattname(namespace character varying, tablename character " - + "varying, " - + "ctype" - + " character\n" - + " varying)\n" - + " RETURNS character varying as $BODY$\n" - + "\n" - + " declare\n" - + " tt oid ;\n" - + " aname character varying default '';\n" - + "\n" - + " begin\n" - + " tt := oid from pg_class where relname= tablename and relnamespace =(select oid from " - + "pg_namespace " - + "where\n" - + " nspname=namespace) ;\n" - + " aname:= array_to_string(\n" - + " array(\n" - + " select a.attname from pg_attribute a\n" - + " where a.attrelid=tt and a.attnum in (\n" - + " select unnest(conkey) from pg_constraint c where contype=ctype\n" - + " and conrelid=tt and array_to_string(conkey,',') is not null\n" - + " )\n" - + " ),',');\n" - + "\n" - + " return aname;\n" - + " end\n" - + " $BODY$ LANGUAGE plpgsql"; - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - SQLExecutor.getInstance().executeSql(functionSQL.replaceFirst("tableSchema", schemaName), resultSet -> null); - String ddlSql = "select showcreatetable('" + schemaName + "','" + tableName + "') as sql"; - return SQLExecutor.getInstance().executeSql(ddlSql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlBaseModel.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlBaseModel.java deleted file mode 100644 index d48bdd485..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlBaseModel.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlBaseModel.java, v 0.1 2022年12月11日 15:07 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlBaseModel { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlColumn.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlColumn.java deleted file mode 100644 index 55f841be0..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlColumn.java +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.support.model.TableColumn; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlColumn.java, v 0.1 2022年12月11日 14:38 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class PostgresqlColumn extends TableColumn implements Serializable { - @Serial - private static final long serialVersionUID = 6895029737377229332L; - private Integer ordinalPosition; - private boolean isArray; - private Integer precision; - private Integer scale; - private String sortRule; - private Integer attinhcount; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintCheck.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintCheck.java deleted file mode 100644 index 36327a885..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintCheck.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlConstraintCheck.java, v 0.1 2022年12月11日 15:24 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintCheck { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusive.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusive.java deleted file mode 100644 index 8981acbcd..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusive.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : dd.java, v 0.1 2022年12月11日 15:19 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintExclusive { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusiveField.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusiveField.java deleted file mode 100644 index 6ae9b1987..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintExclusiveField.java +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlConstraintExclusiveField.java, v 0.1 2022年12月11日 15:19 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintExclusiveField { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeign.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeign.java deleted file mode 100644 index 794ded1fd..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeign.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlConstraintForeign.java, v 0.1 2022年12月11日 15:14 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintForeign { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeignReference.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeignReference.java deleted file mode 100644 index 5552669e7..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintForeignReference.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : kk.java, v 0.1 2022年12月11日 15:16 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintForeignReference { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUnique.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUnique.java deleted file mode 100644 index 862d2f0f4..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUnique.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlConstraintUnique.java, v 0.1 2022年12月11日 15:21 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintUnique { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUniqueField.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUniqueField.java deleted file mode 100644 index 2b401faa7..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlConstraintUniqueField.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlConstraintUniqueField.java, v 0.1 2022年12月11日 15:22 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlConstraintUniqueField { - private String fieldName; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignServer.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignServer.java deleted file mode 100644 index 2fbbb37ec..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignServer.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlForeignServer.java, v 0.1 2022年12月11日 16:23 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class PostgresqlForeignServer { - private String serverName; - private String serverCatalog; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignTable.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignTable.java deleted file mode 100644 index 563bab610..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlForeignTable.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.util.LinkedHashMap; -import java.util.Map; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlForeignTable.java, v 0.1 2022年12月11日 16:19 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class PostgresqlForeignTable extends PostgresqlTable{ - private PostgresqlForeignServer server; - private Map foreignTableOptions = new LinkedHashMap(); -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlInherit.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlInherit.java deleted file mode 100644 index cd912b472..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlInherit.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : ll.java, v 0.1 2022年12月11日 15:26 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class PostgresqlInherit { - private List fullTableNames; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartition.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartition.java deleted file mode 100644 index 04cf6817d..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartition.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.util.ArrayList; -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlPartition.java, v 0.1 2022年12月11日 15:31 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlPartition { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartitionColumn.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartitionColumn.java deleted file mode 100644 index 6db2344c9..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlPartitionColumn.java +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlPartitionColumn.java, v 0.1 2022年12月11日 17:09 jipengfei Exp $ - */ -@Data -public class PostgresqlPartitionColumn { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlResemble.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlResemble.java deleted file mode 100644 index 360578c48..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlResemble.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlResemble.java, v 0.1 2022年12月11日 15:09 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlResemble { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTable.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTable.java deleted file mode 100644 index a14563e92..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTable.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import ai.chat2db.server.domain.support.model.Table; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : TableVO.java, v 0.1 2022年12月11日 14:34 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlTable extends Table { - - public boolean isForeignTable(){ - return false; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndex.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndex.java deleted file mode 100644 index 5783e7a6a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndex.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import java.util.List; - -import ai.chat2db.server.domain.support.model.TableIndex; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlTableIndex.java, v 0.1 2022年12月11日 15:27 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class PostgresqlTableIndex extends TableIndex { - private String tableSchemaName; - private long oid; - private String accessMethod; - private String tableSpace; - private int fillRate; - private boolean unique; - private boolean cluster; - private boolean parallelBuild; - private String constraint; - private int fieldCount; - private List fields; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndexField.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndexField.java deleted file mode 100644 index a33dfc84b..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/postgresql/model/PostgresqlTableIndexField.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Alipay.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.postgresql.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : PostgresqlTableIndexField.java, v 0.1 2022年12月11日 15:37 jipengfei Exp $ - */ -@Data -@SuperBuilder -@NoArgsConstructor -public class PostgresqlTableIndexField { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/presto/PrestoMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/presto/PrestoMetaSchemaSupport.java deleted file mode 100644 index 45a716dda..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/presto/PrestoMetaSchemaSupport.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.presto; - - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import jakarta.validation.constraints.NotEmpty; - -/** - * @author jipengfei - * @version : PrestoMetaSchemaSupport.java - */ -public class PrestoMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.PRESTO; - } - @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - return ""; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/redis/RedisMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/redis/RedisMetaSchemaSupport.java deleted file mode 100644 index a2276e58d..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/redis/RedisMetaSchemaSupport.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.redis; - -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.sql.SQLExecutor; - -import jakarta.validation.constraints.NotEmpty; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : RedisMetaSchemaSupport.java - */ -public class RedisMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.REDIS; - } - @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - return ""; - } - - - @Override - public List databases() { - List databases = new ArrayList<>(); - return SQLExecutor.getInstance().executeSql("config get databases", resultSet -> { - try { - if (resultSet.next()) { - Object count = resultSet.getObject(2); - if(StringUtils.isNotBlank(count.toString())) { - for (int i = 0; i < Integer.parseInt(count.toString()); i++) { - databases.add(String.valueOf(i)); - } - } - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return databases; - }); - } - - @Override - public List
tables(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().executeSql("scan 0 MATCH * COUNT 1000", resultSet -> { - List
tables = new ArrayList<>(); - try { - while (resultSet.next()) { - ArrayList list = (ArrayList)resultSet.getObject(2); - for (Object object : list) { - Table table = new Table(); - table.setName(object.toString()); - tables.add(table); - } - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return tables; - }); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlite/SQLiteMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlite/SQLiteMetaSchemaSupport.java deleted file mode 100644 index ea50b0b36..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlite/SQLiteMetaSchemaSupport.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.sqlite; - -import java.sql.SQLException; -import java.util.List; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; - -import com.google.common.collect.Lists; -import lombok.extern.slf4j.Slf4j; - -/** - * @author jipengfei - * @version : SqlserverMetaSchemaSupport.java - */ -@Slf4j -public class SQLiteMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.SQLITE; - } - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - String sql = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" + tableName + "'"; - return SQLExecutor.getInstance().executeSql(sql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } - @Override - public List databases() { - return Lists.newArrayList("main"); - } - - @Override - public List schemas(String databaseName) { - return Lists.newArrayList(); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlserver/SqlServerMetaSchemaSupport.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlserver/SqlServerMetaSchemaSupport.java deleted file mode 100644 index 1f3f99d20..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/dialect/sqlserver/SqlServerMetaSchemaSupport.java +++ /dev/null @@ -1,78 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.dialect.sqlserver; - -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.dialect.BaseMetaSchema; -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.sql.SQLExecutor; - -import lombok.extern.slf4j.Slf4j; - -/** - * @author jipengfei - * @version : SqlserverMetaSchemaSupport.java - */ -@Slf4j -public class SqlServerMetaSchemaSupport extends BaseMetaSchema implements MetaSchema { - - private String functionSQL - = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" - + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " - + "NVARCHAR(MAX) = ''; DECLARE @ColumnDescriptions NVARCHAR(MAX) = N''; SELECT @CreateTableScript = CONCAT( " - + "'CREATE TABLE [', s.name, '].[' , t.name, '] (', STUFF( ( SELECT ', [' + c.name + '] ' + tp.name + CASE " - + "WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + IIF(c.max_length = -1, 'MAX', CAST(c" - + ".max_length AS NVARCHAR(10))) + ')' WHEN tp.name IN ('decimal', 'numeric') THEN '(' + CAST(c.precision AS " - + "NVARCHAR(10)) + ', ' + CAST(c.scale AS NVARCHAR(10)) + ')' ELSE '' END + ' ' + CASE WHEN c.is_nullable = 1" - + " THEN 'NULL' ELSE 'NOT NULL' END FROM sys.columns c JOIN sys.types tp ON c.user_type_id = tp.user_type_id " - + "WHERE c.object_id = t.object_id FOR XML PATH(''), TYPE ).value('/', 'nvarchar(max)'), 1, 1, ''), ');' ) " - + "FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name = @table_name AND s.name = " - + "@schema_name; SELECT @IndexScripts = @IndexScripts + 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' " - + "ELSE '' END + i.type_desc + ' INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' + STUFF( ( " - + "SELECT ', [' + c.name + ']' + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys" - + ".index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic" - + ".object_id = i.object_id AND ic.index_id = i.index_id ORDER BY ic.key_ordinal FOR XML PATH('') ), 1, 1, " - + "'') + ')' + CASE WHEN i.has_filter = 1 THEN ' WHERE ' + i.filter_definition ELSE '' END + ';' + CHAR(13) +" - + " CHAR(10) FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t" - + ".schema_id = s.schema_id WHERE i.type > 0 AND t.name = @table_name AND s.name " - + "= @schema_name; SELECT @ColumnDescriptions += 'EXEC sp_addextendedproperty @name=N''MS_Description'', " - + "@value=N''' + CAST(p.value AS NVARCHAR(MAX)) + ''', @level0type=N''SCHEMA'', @level0name=N''' + " - + "@schema_name + ''', @level1type=N''TABLE'', @level1name=N''' + @table_name + ''', @level2type=N''COLUMN''," - + " @level2name=N''' + c.name + ''';' + CHAR(13) + CHAR(10) FROM sys.extended_properties p JOIN sys.columns c" - + " ON p.major_id = c.object_id AND p.minor_id = c.column_id JOIN sys.tables t ON c.object_id = t.object_id " - + "JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.class = 1 AND t.name = @table_name AND s.name = " - + "@schema_name; SET @CreateTableScript = @CreateTableScript + CHAR(13) + CHAR(10) + @IndexScripts + CHAR(13)" - + " + CHAR(10)+ @ColumnDescriptions+ CHAR(10); RETURN @CreateTableScript; END"; - - @Override - public DbTypeEnum dbType() { - return DbTypeEnum.SQLSERVER; - } - - @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - try { - System.out.println(functionSQL); - SQLExecutor.getInstance().executeSql(functionSQL.replace("tableSchema", schemaName), resultSet -> null); - } catch (Exception e) { - //log.error("创建函数失败", e); - } - - String ddlSql = "SELECT " + schemaName + ".ufn_GetCreateTableScript('" + schemaName + "', '" + tableName - + "') AS sql"; - return SQLExecutor.getInstance().executeSql(ddlSql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CellTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CellTypeEnum.java deleted file mode 100644 index 50b1ed684..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CellTypeEnum.java +++ /dev/null @@ -1,50 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; - -import lombok.Getter; - -/** - * 驱动类枚举 - * - * @author Jiaju Zhuang - */ -@Getter -public enum CellTypeEnum implements BaseEnum { - /** - * 字符串 - */ - STRING("字符串"), - - /** - * 数字 - */ - BIG_DECIMAL("数字"), - - /** - * 日期 - */ - DATE("日期"), - - /** - * 二进制流 - */ - BYTE("二进制流"), - - /** - * 空数据 - */ - EMPTY("空数据"), - ; - - final String description; - - CellTypeEnum(String description) { - this.description = description; - } - - @Override - public String getCode() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CollationEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CollationEnum.java deleted file mode 100644 index 422797e9f..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/CollationEnum.java +++ /dev/null @@ -1,55 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import com.alibaba.druid.sql.ast.SQLOrderingSpecification; - -import lombok.Getter; - -/** - * 排序枚举 - * - * @author Jiaju Zhuang - */ -@Getter -public enum CollationEnum implements BaseEnum { - /** - * ASC - */ - ASC("asc", SQLOrderingSpecification.ASC), - - /** - * DESC - */ - DESC("desc", SQLOrderingSpecification.DESC), - - ; - - final String description; - - final SQLOrderingSpecification sqlOrderingSpecification; - - CollationEnum(String description, SQLOrderingSpecification sqlOrderingSpecification) { - this.description = description; - this.sqlOrderingSpecification = sqlOrderingSpecification; - } - - @Override - public String getCode() { - return this.name(); - } - - public static boolean equals(String collation1, String collation2) { - return equals(EasyEnumUtils.getEnum(CollationEnum.class, collation1), - EasyEnumUtils.getEnum(CollationEnum.class, collation2)); - } - - public static boolean equals(CollationEnum collation1, CollationEnum collation2) { - // 想同直接返回 - if (collation1 == collation2) { - return true; - } - // 有一个是倒序 就是不相同 ,其他都是相同 - return !(collation1 == CollationEnum.DESC || collation2 == CollationEnum.DESC); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/ColumnTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/ColumnTypeEnum.java deleted file mode 100644 index 0b442b2df..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/ColumnTypeEnum.java +++ /dev/null @@ -1,50 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; - -import lombok.Getter; - -/** - * 列的类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum ColumnTypeEnum implements BaseEnum { - /** - * BIGINT - */ - BIGINT, - - /** - * VARCHAR - */ - VARCHAR, - - /** - * TIMESTAMP - */ - TIMESTAMP, - - /** - * DATETIME - */ - DATETIME, - - /** - * INTEGER - */ - INTEGER, - - ; - - @Override - public String getCode() { - return this.name(); - } - - @Override - public String getDescription() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DataTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DataTypeEnum.java deleted file mode 100644 index cb487c777..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DataTypeEnum.java +++ /dev/null @@ -1,99 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; -import lombok.Getter; - -/** - * 驱动类枚举 - * - * @author Jiaju Zhuang - */ -@Getter -public enum DataTypeEnum implements BaseEnum { - /** - * 布尔值 - */ - BOOLEAN("布尔值"), - - /** - * 数字 - */ - NUMERIC("数字"), - - /** - * 字符串 - */ - STRING("字符串"), - - /** - * 日期 - */ - DATETIME("日期"), - - /** - * 二进制 - */ - BINARY("空数据"), - - /** - * 内容 - */ - CONTENT("内容"), - - /** - * 结构 - */ - STRUCT("结构"), - - /** - * 文档 - */ - DOCUMENT("文档"), - - /** - * 数组 - */ - ARRAY("数组"), - - - /** - * 对象 - */ - OBJECT("对象"), - - - /** - * 引用 - */ - REFERENCE("引用"), - - - /** - * 行号 - */ - ROWID("行号"), - - - /** - * 任何 - */ - ANY("任何"), - - - /** - * 未知 - */ - UNKNOWN("未知"), - ; - - final String description; - - DataTypeEnum(String description) { - this.description = description; - } - - @Override - public String getCode() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DbTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DbTypeEnum.java deleted file mode 100644 index 58fa40869..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DbTypeEnum.java +++ /dev/null @@ -1,239 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import java.util.HashMap; -import java.util.Map; - -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.dialect.clickhouse.ClickhouseMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.common.model.SpiExample; -import ai.chat2db.server.domain.support.dialect.db2.DB2MetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.dm.DMMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.h2.H2MetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.hive.HiveMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.kingbase.KingBaseSchemaSupport; -import ai.chat2db.server.domain.support.dialect.mariadb.MariaDBMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.mongodb.MongodbMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.mysql.MysqlMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.oceanbase.OceanBaseMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.oracle.OracleMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.postgresql.PostgresqlMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.presto.PrestoMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.redis.RedisMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.sqlite.SQLiteMetaSchemaSupport; -import ai.chat2db.server.domain.support.dialect.sqlserver.SqlServerMetaSchemaSupport; -import ai.chat2db.server.tools.base.enums.BaseEnum; - -import lombok.Getter; - -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.H2_ALTER_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.H2_CREATE_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.MYSQL_ALTER_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.MYSQL_CREATE_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.ORACLE_ALTER_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.ORACLE_CREATE_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.PG_ALTER_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.PG_CREATE_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.SQLITE_ALTER_TABLE_SIMPLE; -import static ai.chat2db.server.domain.support.dialect.common.SQLKeyConst.SQLITE_CREATE_TABLE_SIMPLE; - -/** - * 数据类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum DbTypeEnum implements BaseEnum { - /** - * MySQL - */ - MYSQL("MySQL"), - - /** - * PostgreSQL - */ - POSTGRESQL("PostgreSQL"), - - /** - * Oracle - */ - ORACLE("Oracle"), - - /** - * SQLServer - */ - SQLSERVER("SQLServer"), - - /** - * SQLite - */ - SQLITE("SQLite"), - - /** - * H2 - */ - H2("H2"), - - /** - * ADB MySQL - */ - ADB_POSTGRESQL("PostgreSQL"), - - /** - * ClickHouse - */ - CLICKHOUSE("ClickHouse"), - - /** - * OceanBase - */ - OCEANBASE("OceanBase"), - - /** - * DB2 - */ - DB2("DB2"), - - /** - * MMARIADB - */ - MARIADB("MariaDB"), - - /** - * DM 达梦 - */ - DM("DM"), - - - /** - * MONGODB - */ - MONGODB("Mongodb"), - - /** - * PRESTO - */ - PRESTO("Presto"), - - /** - * HIVE - */ - HIVE("Hive"), - - - /** - * REDIS - */ - REDIS("Redis"), - - /** - * KingBase - */ - KINGBASE("KingBase"); - - final String description; - - private static Map META_SCHEMA_MAP = new HashMap<>(); - - static { - META_SCHEMA_MAP.put(H2, new H2MetaSchemaSupport()); - META_SCHEMA_MAP.put(MYSQL, new MysqlMetaSchemaSupport()); - META_SCHEMA_MAP.put(POSTGRESQL, new PostgresqlMetaSchemaSupport()); - META_SCHEMA_MAP.put(ORACLE, new OracleMetaSchemaSupport()); - META_SCHEMA_MAP.put(SQLSERVER, new SqlServerMetaSchemaSupport()); - META_SCHEMA_MAP.put(SQLITE, new SQLiteMetaSchemaSupport()); - META_SCHEMA_MAP.put(OCEANBASE, new OceanBaseMetaSchemaSupport()); - META_SCHEMA_MAP.put(MARIADB, new MariaDBMetaSchemaSupport()); - META_SCHEMA_MAP.put(CLICKHOUSE, new ClickhouseMetaSchemaSupport()); - META_SCHEMA_MAP.put(DB2, new DB2MetaSchemaSupport()); - META_SCHEMA_MAP.put(DM, new DMMetaSchemaSupport()); - META_SCHEMA_MAP.put(MONGODB, new MongodbMetaSchemaSupport()); - META_SCHEMA_MAP.put(PRESTO, new PrestoMetaSchemaSupport()); - META_SCHEMA_MAP.put(REDIS, new RedisMetaSchemaSupport()); - META_SCHEMA_MAP.put(KINGBASE, new KingBaseSchemaSupport()); - META_SCHEMA_MAP.put(HIVE, new HiveMetaSchemaSupport()); - } - - DbTypeEnum(String description) { - this.description = description; - } - - /** - * 通过名称获取枚举 - * - * @param name - * @return - */ - public static DbTypeEnum getByName(String name) { - for (DbTypeEnum dbTypeEnum : DbTypeEnum.values()) { - if (dbTypeEnum.name().equals(name)) { - return dbTypeEnum; - } - } - return null; - } - - @Override - public String getCode() { - return this.name(); - } - - public MetaSchema metaSchema() { - return META_SCHEMA_MAP.get(this); - } - - public SpiExample example() { - SpiExample SpiExample = null; - switch (this) { - case H2: - SpiExample = SpiExample.builder().createTable(H2_CREATE_TABLE_SIMPLE).alterTable(H2_ALTER_TABLE_SIMPLE) - .build(); - break; - case MYSQL: - SpiExample = SpiExample.builder().createTable(MYSQL_CREATE_TABLE_SIMPLE).alterTable( - MYSQL_ALTER_TABLE_SIMPLE).build(); - break; - case POSTGRESQL: - SpiExample = SpiExample.builder().createTable(PG_CREATE_TABLE_SIMPLE).alterTable(PG_ALTER_TABLE_SIMPLE) - .build(); - break; - case ORACLE: - SpiExample = SpiExample.builder().createTable(ORACLE_CREATE_TABLE_SIMPLE).alterTable( - ORACLE_ALTER_TABLE_SIMPLE).build(); - break; - case SQLSERVER: - SpiExample = SpiExample.builder().createTable(ORACLE_CREATE_TABLE_SIMPLE).alterTable( - ORACLE_ALTER_TABLE_SIMPLE).build(); - break; - case SQLITE: - SpiExample = SpiExample.builder().createTable(SQLITE_CREATE_TABLE_SIMPLE).alterTable( - SQLITE_ALTER_TABLE_SIMPLE).build(); - break; - case OCEANBASE: - SpiExample = SpiExample.builder().createTable(MYSQL_CREATE_TABLE_SIMPLE).alterTable( - MYSQL_ALTER_TABLE_SIMPLE).build(); - break; - case CLICKHOUSE: - SpiExample = SpiExample.builder().createTable(MYSQL_CREATE_TABLE_SIMPLE).alterTable( - MYSQL_ALTER_TABLE_SIMPLE).build(); - break; - case MARIADB: - SpiExample = SpiExample.builder().createTable(MYSQL_CREATE_TABLE_SIMPLE).alterTable( - MYSQL_ALTER_TABLE_SIMPLE).build(); - break; - case DB2: - SpiExample = SpiExample.builder().createTable(MYSQL_CREATE_TABLE_SIMPLE).alterTable( - MYSQL_ALTER_TABLE_SIMPLE).build(); - break; - case DM: - SpiExample = SpiExample.builder().createTable(MYSQL_CREATE_TABLE_SIMPLE).alterTable( - MYSQL_ALTER_TABLE_SIMPLE).build(); - break; - case PRESTO: - SpiExample = SpiExample.builder().createTable("").alterTable("").build(); - break; - default: - } - return SpiExample; - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DriverTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DriverTypeEnum.java deleted file mode 100644 index 26d14e12a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/DriverTypeEnum.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.enums; - -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : DriverTypeEnum.java - */ -public enum DriverTypeEnum { - /** - * MySQL - */ - MYSQL_DRIVER(DbTypeEnum.MYSQL, "com.mysql.cj.jdbc.Driver", "mysql-connector-java-8.0.30.jar","8.0"), - - /** - * MySQL 5.1版本 - */ - MYSQL_DRIVER_5_1(DbTypeEnum.MYSQL, "com.mysql.jdbc.Driver", "mysql-connector-java-5.1.47.jar","5.0"), - - /** - * PostgreSQL - */ - POSTGRESQL_DRIVER(DbTypeEnum.POSTGRESQL, "org.postgresql.Driver", "postgresql-42.5.1.jar",""), - - /** - * Oracle - */ - ORACLE_DRIVER(DbTypeEnum.ORACLE, "oracle.jdbc.driver.OracleDriver", "ojdbc8-19.3.0.0.jar,orai18n-19.3.0.0.jar",""), - - /** - * SQLServer - */ - SQLSERVER_DRIVER(DbTypeEnum.SQLSERVER, "com.microsoft.sqlserver.jdbc.SQLServerDriver", - "mssql-jdbc-11.2.1.jre17.jar",""), - - /** - * SQLite - */ - SQLITE_DRIVER(DbTypeEnum.SQLITE, "org.sqlite.JDBC", "sqlite-jdbc-3.39.3.0.jar",""), - - /** - * H2 - */ - H2_DRIVER(DbTypeEnum.H2, "org.h2.Driver", "h2-2.1.214.jar",""), - - /** - * ADB MySQL - */ - ADB_POSTGRESQL_DRIVER(DbTypeEnum.ADB_POSTGRESQL, "org.postgresql.Driver", "",""), - - /** - * ClickHouse - */ - CLICKHOUSE_DRIVER(DbTypeEnum.CLICKHOUSE, "com.clickhouse.jdbc.ClickHouseDriver", "clickhouse-jdbc-0.3.2-patch8-http.jar",""), - - /** - * OceanBase - */ - OCEANBASE_DRIVER(DbTypeEnum.OCEANBASE, "com.oceanbase.jdbc.Driver", "oceanbase-client-2.4.2.jar",""), - - /** - * DB2 - */ - DB2_DRIVER(DbTypeEnum.DB2, "com.ibm.db2.jcc.DB2Driver", "db2jcc4_4.26.14.jar",""), - - /** - * MMARIADB - */ - MARIADB_DRIVER(DbTypeEnum.MARIADB, "org.mariadb.jdbc.Driver", "mariadb-java-client-3.0.8.jar",""), - - - - /** - * DM_DRIVER - */ - DM_DRIVER(DbTypeEnum.DM, "dm.jdbc.driver.DmDriver", "DmJdbcDriver18-8.1.2.141.jar",""), - - - /** - * PRESTO_DRIVER - */ - PRESTO_DRIVER(DbTypeEnum.PRESTO, "com.facebook.presto.jdbc.PrestoDriver", "presto-jdbc-0.245.1.jar",""), - - /** - * KINGBASE_DRIVER - * com.kingbase8.Driver - */ - KINGBASE_DRIVER(DbTypeEnum.KINGBASE, "com.kingbase8.Driver", "kingbase8-8.6.0.jar",""), - - /** - * HIVE_DRIVER - * org.apache.hive.jdbc.HiveDriver - */ - HIVE_DRIVER(DbTypeEnum.HIVE, "org.apache.hive.jdbc.HiveDriver", "hive-jdbc-3.1.2-standalone.jar",""), - - /** - * REDIS_DRIVER - */ - REDIS_DRIVER(DbTypeEnum.REDIS, "jdbc.RedisDriver", "redis-jdbc-driver-1.3.jar",""), - /** - * MONGODB_DRIVER - * com.dbschema.MongoJdbcDriver - */ - MONGODB_DRIVER(DbTypeEnum.MONGODB, "com.dbschema.MongoJdbcDriver", "mongo-jdbc-standalone-1.18.jar",""); - - final DbTypeEnum dbTypeEnum; - - final String driverClass; - - final String jar; - - final String jdbc; - - DriverTypeEnum(DbTypeEnum dbTypeEnum, String driverClass, String jar, String jdbc) { - this.dbTypeEnum = dbTypeEnum; - this.driverClass = driverClass; - this.jar = jar; - this.jdbc = jdbc; - } - - public static DriverTypeEnum getDriver(DbTypeEnum dbTypeEnum, String jdbc) { - for (DriverTypeEnum driverTypeEnum : DriverTypeEnum.values()) { - if (driverTypeEnum.dbTypeEnum.equals(dbTypeEnum)) { - if (StringUtils.isBlank(jdbc) || driverTypeEnum.jdbc.equals(jdbc)) { - return driverTypeEnum; - } - } - } - return null; - } - - /** - * Getter method for property dbTypeEnum. - * - * @return property value of dbTypeEnum - */ - public DbTypeEnum getDbTypeEnum() { - return dbTypeEnum; - } - - /** - * Getter method for property jar. - * - * @return property value of jar - */ - public String getJar() { - return jar; - } - - /** - * Getter method for property driverClass. - * - * @return property value of driverClass - */ - public String getDriverClass() { - return driverClass; - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/IndexTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/IndexTypeEnum.java deleted file mode 100644 index 2bef949d2..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/IndexTypeEnum.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; - -import lombok.Getter; - -/** - * 索引类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum IndexTypeEnum implements BaseEnum { - /** - * 主键 - */ - PRIMARY_KEY("主键"), - - /** - * 普通索引 - */ - NORMAL("普通索引"), - - /** - * 唯一索引 - */ - UNIQUE("唯一索引"), - ; - - final String description; - - IndexTypeEnum(String description) { - this.description = description; - } - - @Override - public String getCode() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/SqlTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/SqlTypeEnum.java deleted file mode 100644 index 0b3022bb5..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/enums/SqlTypeEnum.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.chat2db.server.domain.support.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; - -import lombok.Getter; - -/** - * sq类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum SqlTypeEnum implements BaseEnum { - /** - * 查询语句 - */ - SELECT("查询语句"), - - /** - * 未知 - */ - UNKNOWN("未知"), - - ; - - final String description; - - SqlTypeEnum(String description) { - this.description = description; - } - - @Override - public String getCode() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Cell.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Cell.java deleted file mode 100644 index 58b7ac650..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Cell.java +++ /dev/null @@ -1,49 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import java.math.BigDecimal; - -import ai.chat2db.server.domain.support.enums.CellTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 单元格类型 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Cell { - - /** - * 单元格类型 - * - * @see CellTypeEnum - */ - private String type; - - /** - * 字符串数据 - */ - private String stringValue; - - /** - * 数字 - */ - private BigDecimal bigDecimalValue; - - /** - * 日期数据 - */ - private Long dateValue; - - /** - * 二进制流 - */ - private byte[] byteValue; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/CreateTableSql.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/CreateTableSql.java deleted file mode 100644 index 842da93e6..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/CreateTableSql.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : CreateTableSql.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class CreateTableSql { - - public String tableName; - - public String sql; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DataSourceConnect.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DataSourceConnect.java deleted file mode 100644 index 5e320f481..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DataSourceConnect.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 数据库连接对象 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceConnect { - - /** - * 是否成功标志位 - */ - private Boolean success; - - /** - * 失败消息提示 - * 只有失败的情况下会有 - */ - private String message; - - /** - * 描述 - */ - private String description; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Database.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Database.java deleted file mode 100644 index 9c8778dcc..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Database.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 数据库 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Database { - /** - * 数据名字 - */ - private String name; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DriverEntry.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DriverEntry.java deleted file mode 100644 index 3ba6cfcf5..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/DriverEntry.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import java.sql.Driver; - -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : DriverEntry.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DriverEntry { - - private DriverTypeEnum driverTypeEnum; - - private Driver driver; - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ExecuteResult.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ExecuteResult.java deleted file mode 100644 index 8181c487b..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ExecuteResult.java +++ /dev/null @@ -1,88 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import java.util.List; - -import ai.chat2db.server.domain.support.enums.SqlTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 执行结果 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class ExecuteResult { - - /** - * 是否成功标志位 - */ - private Boolean success; - - /** - * 失败消息提示 - * 只有失败的情况下会有 - */ - private String message; - - /** - * 执行的sql - */ - private String sql; - - /** - * 描述 - */ - private String description; - - /** - * 修改行数 查询sql不会返回 - */ - private Integer updateCount; - - /** - * 展示头的列表 - */ - private List
headerList; - - /** - * 数据的列表 - */ - private List> dataList; - - /** - * sql 类型 - * - * @see SqlTypeEnum - */ - private String sqlType; - - /** - * 是否存在下一页 - * 只有select语句才有 - */ - private Boolean hasNextPage; - - /** - * 分页编码 - * 只有select语句才有 - */ - private Integer pageNo; - - /** - * 分页大小 - * 只有select语句才有 - */ - private Integer pageSize; - - /** - * 执行持续时间 - */ - private Long duration; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Function.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Function.java deleted file mode 100644 index f6537d1b2..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Function.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : Function.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Function { - //FUNCTION_CAT String => function catalog (may be null) - //FUNCTION_SCHEM String => function schema (may be null) - //FUNCTION_NAME String => function name. This is the name used to invoke the function - //REMARKS String => explanatory comment on the function - //FUNCTION_TYPE short => kind of function: - //functionResultUnknown - Cannot determine if a return value or table will be returned - //functionNoTable- Does not return a table - //functionReturnsTable - Returns a table - //SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. This is a user specified, or DBMS generated, name that may be different then the FUNCTION_NAME for example with overload functions - // - - private String databaseName; - - private String schemaName; - - private String functionName; - - private String remarks; - - private Short functionType; - - private String specificName; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Header.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Header.java deleted file mode 100644 index 07ba7a74e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Header.java +++ /dev/null @@ -1,30 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import ai.chat2db.server.domain.support.enums.DataTypeEnum; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 单元格头 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Header { - /** - * 单元格类型 - * - * @see DataTypeEnum - */ - private String dataType; - - /** - * 展示的名字 - */ - private String name; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/KeyValue.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/KeyValue.java deleted file mode 100644 index fba54da70..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/KeyValue.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import java.util.List; -import java.util.Map; - -import com.google.common.collect.Maps; -import lombok.Data; -import org.apache.commons.collections4.CollectionUtils; - -/** - * @author jipengfei - * @version : KeyValue.java - */ -@Data -public class KeyValue { - /** - * 属性名 - */ - private String key; - - /** - * 属性值 - */ - private Object value; - - public static Map toMap(List keyValues) { - if (CollectionUtils.isEmpty(keyValues)) { - return Maps.newHashMap(); - } else { - Map map = Maps.newHashMap(); - keyValues.forEach(keyValue -> map.put(keyValue.getKey(), String.valueOf(keyValue.getValue()))); - return map; - } - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Procedure.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Procedure.java deleted file mode 100644 index f676d6d29..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Procedure.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : Procedure.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Procedure { - //PROCEDURE_CAT String => procedure catalog (may be null) - //PROCEDURE_SCHEM String => procedure schema (may be null) - //PROCEDURE_NAME String => procedure name - //REMARKS String => explanatory comment on the procedure - //PROCEDURE_TYPE short => kind of procedure: - //procedureResultUnknown - Cannot determine if a return value will be returned - //procedureNoResult - Does not return a return value - //procedureReturnsResult - Returns a return value - //SPECIFIC_NAME String => the name which uniquely identifies this procedure within its schema. This is a user specified, or DBMS generated, name that may be different then the PROCEDURE_NAME for example with overload procedures - // - - private String databaseName; - - private String schemaName; - - private String procedureName; - - private String remarks; - - private Short procedureType; - - private String specificName; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSHInfo.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSHInfo.java deleted file mode 100644 index 6750c8abe..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSHInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import lombok.Data; - -/** - * @author jipengfei - * @version : SSHInfo.java - */ -@Data -public class SSHInfo { - - /** - * 是否使用ssh - */ - private boolean use; - - /** - * ssh主机名 - */ - private String hostName; - - /** - * ssh端口 - */ - private String port; - - /** - * ssh用户名 - */ - private String userName; - - /** - * ssh本地端口 - */ - private String localPort; - - /** - * ssh认证类型 - */ - private String authenticationType; - - /** - * ssh密码 - */ - private String password; - - /** - * ssh密钥文件 - */ - private String keyFile; - - /** - * ssh密钥文件密码 - */ - private String passphrase; - - /** - * ssh跳板机目标主机 - */ - private String rHost; - - /** - * ssh跳板目标端口 - */ - private String rPort; - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSLInfo.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSLInfo.java deleted file mode 100644 index 0a2f88029..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/SSLInfo.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -/** - * @author jipengfei - * @version : SSLInfo.java - */ -public class SSLInfo { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Schema.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Schema.java deleted file mode 100644 index eadccf52e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Schema.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : TableSchema.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Schema { - /** - * 数据名字 - */ - private String name; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ShowDatabaseResult.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ShowDatabaseResult.java deleted file mode 100644 index e4687c112..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/ShowDatabaseResult.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author jipengfei - * @version : ShowDatabaseResult.java - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class ShowDatabaseResult { - String database; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Sql.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Sql.java deleted file mode 100644 index 668d9d22a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Sql.java +++ /dev/null @@ -1,24 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * sql对象 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Sql { - - /** - * sql - */ - private String sql; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Table.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Table.java deleted file mode 100644 index f2891225c..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Table.java +++ /dev/null @@ -1,65 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import java.util.List; - -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 表信息 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class Table { - - /** - * 表名 - */ - private String name; - - /** - * 描述 - */ - private String comment; - - /** - * DB 名 - */ - private String schemaName; - - /** - * 列列表 - */ - private List columnList; - - /** - * 索引列表 - */ - private List indexList; - - /** - * DB类型 - */ - private DbTypeEnum dbType; - - /** - * 数据库名 - */ - private String databaseName; - - /** - * 表类型 - */ - private String type; - - -} - diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableColumn.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableColumn.java deleted file mode 100644 index a306ea662..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableColumn.java +++ /dev/null @@ -1,145 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 列信息 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TableColumn { - /** - * 旧的列名,在修改列的时候需要这个参数 - * 在返回的时候oldName=name - */ - private String oldName; - - /** - * 列名 - */ - private String name; - - /** - * 表名 - */ - private String tableName; - - /** - * 列的类型 - * 比如 varchar(100) ,double(10,6) - */ - private String columnType; - - /** - * 列的数据类型 - * 比如 varchar ,double - */ - private Integer dataType; - - - /** - * 默认值 - */ - private String defaultValue; - - /** - * 是否自增 - * 为空 代表没有值 数据库的实际语义是 false - */ - private Boolean autoIncrement; - - /** - * 注释 - */ - private String comment; - - /** - * 是否主键 - */ - private Boolean primaryKey; - - /** - * 空间名 - */ - private String schemaName; - - /** - * 数据库名 - */ - private String databaseName; - - /** - * Data source dependent type name, for a UDT the type name is fully qualified - */ - private String typeName; - - /** - * column size. - */ - private Integer columnSize; - - /** - * is not used. - */ - private Integer bufferLength; - - /** - * the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable. - */ - private Integer decimalDigits; - - /** - * Radix (typically either 10 or 2) - */ - private Integer numPrecRadix; - - /** - * is NULL allowed. - * columnNoNulls - might not allow NULL values - * columnNullable - definitely allows NULL values - * columnNullableUnknown - nullability unknown - */ - private Integer nullableInt; - - /** - * unused - */ - private Integer sqlDataType; - - - /** - * unused - */ - private Integer sqlDatetimeSub; - - /** - * for char types the maximum number of bytes in the column - */ - private Integer charOctetLength; - - /** - * index of column in table (starting at 1) - */ - private Integer ordinalPosition; - - /** - * ISO rules are used to determine the nullability for a column. - */ - private Boolean nullable; - - /** - * String => Indicates whether this is a generated column - * * YES --- if this a generated column - * * NO --- if this not a generated column - */ - private Boolean generatedColumn; - - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndex.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndex.java deleted file mode 100644 index a0e69ad51..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndex.java +++ /dev/null @@ -1,64 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import java.util.List; - -import ai.chat2db.server.domain.support.enums.IndexTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 索引信息 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TableIndex { - - /** - * 索引名称 - */ - private String name; - - /** - * 表名 - */ - private String tableName; - - /** - * 索引类型 - * - * @see IndexTypeEnum - */ - private String type; - - /** - * 是否唯一 - */ - private Boolean unique; - - /** - * 注释 - */ - private String comment; - - /** - * 索引所属schema - */ - private String schemaName; - - /** - * 数据库名 - */ - private String databaseName; - - /** - * 索引包含的列 - */ - private List columnList; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndexColumn.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndexColumn.java deleted file mode 100644 index 8ec9261b2..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/TableIndexColumn.java +++ /dev/null @@ -1,102 +0,0 @@ -package ai.chat2db.server.domain.support.model; - -import ai.chat2db.server.domain.support.enums.CollationEnum; -import ai.chat2db.server.domain.support.enums.IndexTypeEnum; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 列信息 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TableIndexColumn { - - /** - * 索引名称 - */ - private String indexName; - - /** - * 表名 - */ - private String tableName; - - /** - * 索引类型 - * - * @see IndexTypeEnum - */ - private String type; - - /** - * 注释 - */ - private String comment; - - /** - * 列名 - */ - private String columnName; - - /** - * 顺序 - */ - private Short ordinalPosition; - - /** - * 排序 - * - * @see CollationEnum - */ - private String collation; - - - /** - * 索引所属schema - */ - private String schemaName; - - /** - * 数据库名 - */ - private String databaseName; - - /** - * 是否唯一 - */ - private Boolean nonUnique; - - /** - * index catalog (may be null); null when TYPE is tableIndexStatistic - */ - private String indexQualifier; - - /** - * ASC_OR_DESC String => column sort sequence, "A" => ascending, "D" => descending, may be null if sort sequence is not supported; null when TYPE is tableIndexStatistic - */ - private String ascOrDesc; - - /** - * CARDINALITY long => When TYPE is tableIndexStatistic, then this is the number of rows in the table; otherwise, it is the number of unique values in the index. - */ - private Long cardinality; - - /** - * When TYPE is tableIndexStatistic then this is the number of pages used for the table, otherwise it is the number of pages used for the current index. - */ - private Long pages; - - /** - * Filter condition, if any. (may be null) - */ - private String filterCondition; -} - diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Trigger.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Trigger.java deleted file mode 100644 index e3a21ef0b..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/model/Trigger.java +++ /dev/null @@ -1,12 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.model; - -/** - * @author jipengfei - * @version : Trigger.java - */ -public class Trigger { -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/ConnectInfo.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/ConnectInfo.java deleted file mode 100644 index ff07cb976..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/ConnectInfo.java +++ /dev/null @@ -1,598 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.sql; - -import java.sql.Connection; -import java.time.LocalDateTime; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Objects; - -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.model.KeyValue; -import ai.chat2db.server.domain.support.model.SSHInfo; -import ai.chat2db.server.domain.support.model.SSLInfo; - -import com.jcraft.jsch.Session; -import org.springframework.util.ObjectUtils; - -/** - * @author jipengfei - * @version : ConnectInfo.java - */ -public class ConnectInfo { - /** - * 别名 - */ - private String alias; - /** - * 数据连接ID - */ - private Long dataSourceId; - - - /** - * 创建时间 - */ - private LocalDateTime gmtCreate; - - /** - * 修改时间 - */ - private LocalDateTime gmtModified; - /** - * database - */ - private String databaseName; - - /** - * 控制台ID - */ - private Long consoleId; - - /** - * 数据库URL - */ - private String url; - - /** - * 用户名 - */ - private String user; - - /** - * 密码 - */ - private String password; - - /** - * console独立占有连接 - */ - private Boolean consoleOwn = Boolean.FALSE; - - /** - * 数据库类型 - */ - private DbTypeEnum dbType; - - private Integer port; - - /** - * - */ - private String urlWithOutDatabase; - - /** - * host - */ - private String host; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - - - public Connection connection; - - /** - * Getter method for property session. - * - * @return property value of session - */ - public Session getSession() { - return session; - } - - /** - * Setter method for property session. - * - * @param session value to be assigned to property session - */ - public void setSession(Session session) { - this.session = session; - } - - public Session session; - - - /** - * Getter method for property extendInfo. - * - * @return property value of extendInfo - */ - public LinkedHashMap getExtendMap() { - if (ObjectUtils.isEmpty(extendInfo)) { - return new LinkedHashMap<>(); - } - LinkedHashMap map = new LinkedHashMap<>(); - for (KeyValue keyValue : extendInfo) { - map.put(keyValue.getKey(),keyValue.getValue()); - } - return map; - } - - - public void setDatabase(String database) { - this.databaseName = database; - //if (!ObjectUtils.isEmpty(this.urlWithOutDatabase) && !ObjectUtils.isEmpty(this.databaseName)) { - // this.url = this.urlWithOutDatabase + "/" + database; - //} - } - - public String key() { - return this.dataSourceId + "_" + this.databaseName; - } - - public void setUrl(String url) { - this.url = url; - //if (this.dbType != DbTypeEnum.MYSQL && this.dbType != DbTypeEnum.POSTGRESQL) { - // return; - //} - //if (!ObjectUtils.isEmpty(url)) { - // //jdbc:postgresql://localhost:5432/postgres - // String[] array = getUrl().split(":"); - // if (array.length == 4) { - // String str = array[3]; - // boolean isDigit = true; - // StringBuffer sb = new StringBuffer(); - // StringBuffer sb1 = new StringBuffer(); - // for (int i = 0; i < str.length(); i++) { - // char c = str.charAt(i); - // if (isDigit == true) { - // if (!Character.isDigit(c)) { - // isDigit = false; - // } else { - // sb1.append(c); - // } - // } else { - // sb.append(c); - // } - // } - // String s = sb.toString(); - // if (!ObjectUtils.isEmpty(s)) { - // this.databaseName = s; - // } - // this.port = Integer.parseInt(sb1.toString()); - // this.urlWithOutDatabase = array[0] + ":" + array[1] + ":" + array[2] + ":" + port; - // } - //} - } - - @Override - public boolean equals(Object o) { - if (this == o) {return true;} - if (!(o instanceof ConnectInfo)) {return false;} - ConnectInfo that = (ConnectInfo)o; - return Objects.equals(dataSourceId, that.dataSourceId) - && Objects.equals(gmtModified, that.gmtModified) - ; - } - - @Override - public int hashCode() { - return Objects.hash(dataSourceId, consoleId, databaseName); - } - - /** - * Getter method for property dataSourceId. - * - * @return property value of dataSourceId - */ - public Long getDataSourceId() { - return dataSourceId; - } - - /** - * Setter method for property dataSourceId. - * - * @param dataSourceId value to be assigned to property dataSourceId - */ - public void setDataSourceId(Long dataSourceId) { - this.dataSourceId = dataSourceId; - } - - /** - * Getter method for property databaseName. - * - * @return property value of databaseName - */ - public String getDatabaseName() { - return databaseName; - } - - /** - * Setter method for property databaseName. - * - * @param databaseName value to be assigned to property databaseName - */ - public void setDatabaseName(String databaseName) { - this.databaseName = databaseName; - } - - /** - * Getter method for property consoleId. - * - * @return property value of consoleId - */ - public Long getConsoleId() { - return consoleId; - } - - /** - * Setter method for property consoleId. - * - * @param consoleId value to be assigned to property consoleId - */ - public void setConsoleId(Long consoleId) { - this.consoleId = consoleId; - } - - /** - * Getter method for property url. - * - * @return property value of url - */ - public String getUrl() { - return url; - } - - /** - * Getter method for property user. - * - * @return property value of user - */ - public String getUser() { - return user; - } - - /** - * Setter method for property user. - * - * @param user value to be assigned to property user - */ - public void setUser(String user) { - this.user = user; - } - - /** - * Getter method for property password. - * - * @return property value of password - */ - public String getPassword() { - return password; - } - - /** - * Setter method for property password. - * - * @param password value to be assigned to property password - */ - public void setPassword(String password) { - this.password = password; - } - - /** - * Getter method for property consoleOwn. - * - * @return property value of consoleOwn - */ - public Boolean getConsoleOwn() { - return consoleOwn; - } - - /** - * Setter method for property consoleOwn. - * - * @param consoleOwn value to be assigned to property consoleOwn - */ - public void setConsoleOwn(Boolean consoleOwn) { - this.consoleOwn = consoleOwn; - } - - /** - * Getter method for property dbType. - * - * @return property value of dbType - */ - public DbTypeEnum getDbType() { - return dbType; - } - - /** - * Setter method for property dbType. - * - * @param dbType value to be assigned to property dbType - */ - public void setDbType(DbTypeEnum dbType) { - this.dbType = dbType; - } - - /** - * Getter method for property port. - * - * @return property value of port - */ - public Integer getPort() { - return port; - } - - /** - * Setter method for property port. - * - * @param port value to be assigned to property port - */ - public void setPort(Integer port) { - this.port = port; - } - - /** - * Getter method for property urlWithOutDatabase. - * - * @return property value of urlWithOutDatabase - */ - public String getUrlWithOutDatabase() { - return urlWithOutDatabase; - } - - /** - * Setter method for property urlWithOutDatabase. - * - * @param urlWithOutDatabase value to be assigned to property urlWithOutDatabase - */ - public void setUrlWithOutDatabase(String urlWithOutDatabase) { - this.urlWithOutDatabase = urlWithOutDatabase; - } - - /** - * Getter method for property host. - * - * @return property value of host - */ - public String getHost() { - return host; - } - - /** - * Setter method for property host. - * - * @param host value to be assigned to property host - */ - public void setHost(String host) { - this.host = host; - } - - /** - * Getter method for property ssh. - * - * @return property value of ssh - */ - public SSHInfo getSsh() { - return ssh; - } - - /** - * Setter method for property ssh. - * - * @param ssh value to be assigned to property ssh - */ - public void setSsh(SSHInfo ssh) { - this.ssh = ssh; - } - - /** - * Getter method for property ssl. - * - * @return property value of ssl - */ - public SSLInfo getSsl() { - return ssl; - } - - /** - * Setter method for property ssl. - * - * @param ssl value to be assigned to property ssl - */ - public void setSsl(SSLInfo ssl) { - this.ssl = ssl; - } - - /** - * Getter method for property sid. - * - * @return property value of sid - */ - public String getSid() { - return sid; - } - - /** - * Setter method for property sid. - * - * @param sid value to be assigned to property sid - */ - public void setSid(String sid) { - this.sid = sid; - } - - /** - * Getter method for property driver. - * - * @return property value of driver - */ - public String getDriver() { - return driver; - } - - /** - * Setter method for property driver. - * - * @param driver value to be assigned to property driver - */ - public void setDriver(String driver) { - this.driver = driver; - } - - /** - * Getter method for property jdbc. - * - * @return property value of jdbc - */ - public String getJdbc() { - return jdbc; - } - - /** - * Setter method for property jdbc. - * - * @param jdbc value to be assigned to property jdbc - */ - public void setJdbc(String jdbc) { - this.jdbc = jdbc; - } - - /** - * Getter method for property extendInfo. - * - * @return property value of extendInfo - */ - public List getExtendInfo() { - return extendInfo; - } - - - - /** - * Setter method for property extendInfo. - * - * @param extendInfo value to be assigned to property extendInfo - */ - public void setExtendInfo(List extendInfo) { - this.extendInfo = extendInfo; - } - - /** - * Getter method for property connection. - * - * @return property value of connection - */ - public Connection getConnection() { - return connection; - } - - /** - * Setter method for property connection. - * - * @param connection value to be assigned to property connection - */ - public void setConnection(Connection connection) { - this.connection = connection; - } - - - /** - * Getter method for property alias. - * - * @return property value of alias - */ - public String getAlias() { - return alias; - } - - /** - * Setter method for property alias. - * - * @param alias value to be assigned to property alias - */ - public void setAlias(String alias) { - this.alias = alias; - } - - /** - * Getter method for property gmtCreate. - * - * @return property value of gmtCreate - */ - public LocalDateTime getGmtCreate() { - return gmtCreate; - } - - /** - * Setter method for property gmtCreate. - * - * @param gmtCreate value to be assigned to property gmtCreate - */ - public void setGmtCreate(LocalDateTime gmtCreate) { - this.gmtCreate = gmtCreate; - } - - - /** - * Getter method for property gmtModified. - * - * @return property value of gmtModified - */ - public LocalDateTime getGmtModified() { - return gmtModified; - } - - /** - * Setter method for property gmtModified. - * - * @param gmtModified value to be assigned to property gmtModified - */ - public void setGmtModified(LocalDateTime gmtModified) { - this.gmtModified = gmtModified; - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/DbhubContext.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/DbhubContext.java deleted file mode 100644 index 3afde0cd1..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/DbhubContext.java +++ /dev/null @@ -1,157 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.sql; - -import java.sql.Connection; -import java.sql.SQLException; -import java.util.List; -import java.util.Map; - -import ai.chat2db.server.domain.support.dialect.MetaSchema; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; -import ai.chat2db.server.domain.support.model.SSHInfo; - -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : DbhubContext.java - */ -@Slf4j -public class DbhubContext { - - private static final ThreadLocal CONNECT_INFO_THREAD_LOCAL = new ThreadLocal<>(); - - public static List JDBC_JAR_DOWNLOAD_URL_LIST; - - /** - * 获取当前线程的ContentContext - * - * @return - */ - public static ConnectInfo getConnectInfo() { - return CONNECT_INFO_THREAD_LOCAL.get(); - } - - public static MetaSchema getMetaSchema() { - return getConnectInfo().getDbType().metaSchema(); - } - - public static Connection getConnection() { - return getConnectInfo().getConnection(); - } - - /** - * 设置context - * - * @param info - */ - public static void putContext(ConnectInfo info) { - ConnectInfo connectInfo = CONNECT_INFO_THREAD_LOCAL.get(); - CONNECT_INFO_THREAD_LOCAL.set(info); - if (connectInfo == null) { - setConnectInfoThreadLocal(info); - if (StringUtils.isNotBlank(info.getDatabaseName())) { - SQLExecutor.getInstance().connectDatabase(info.getDatabaseName()); - } - } - } - - private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { - Session session = null; - Connection connection = null; - SSHInfo ssh = connectInfo.getSsh(); - String url = connectInfo.getUrl(); - String host = connectInfo.getHost(); - String port = connectInfo.getPort() + ""; - try { - session = getSession(ssh); - if (session != null) { - url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); - } - connection = getConnect(url, host, port, connectInfo.getUser(), - connectInfo.getPassword(), connectInfo.getDbType(), - connectInfo.getJdbc(), ssh, connectInfo.getExtendMap()); - } catch (Exception e1) { - log.error("getConnect error", e1); - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - log.error("session close error", e); - } - } - if (session != null) { - try { - session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); - session.disconnect(); - } catch (JSchException e) { - log.error("session close error", e); - } - } - throw new RuntimeException("getConnect error", e1); - } - connectInfo.setSession(session); - connectInfo.setConnection(connection); - } - - /** - * 测试数据库连接 - * - * @param url 数据库连接 - * @param userName 用户名 - * @param password 密码 - * @param dbType 数据库类型 - * @return - */ - private static Connection getConnect(String url, String host, String port, - String userName, String password, DbTypeEnum dbType, - String jdbc, SSHInfo ssh, Map properties) throws SQLException { - // 创建连接 - return IDriverManager.getConnection(url, userName, password, - DriverTypeEnum.getDriver(dbType, jdbc), properties); - - } - - private static Session getSession(SSHInfo ssh) { - Session session = null; - if (ssh!=null && ssh.isUse()) { - session = SSHManager.getSSHSession(ssh); - } - return session; - } - - /** - * 设置context - */ - public static void removeContext() { - ConnectInfo connectInfo = CONNECT_INFO_THREAD_LOCAL.get(); - if (connectInfo != null) { - Connection connection = connectInfo.getConnection(); - try { - if (connection != null && !connection.isClosed()) { - connection.close(); - } - } catch (SQLException e) { - log.error("close connection error", e); - } - Session session = connectInfo.getSession(); - if (session != null) { - try { - session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); - session.disconnect(); - } catch (JSchException e) { - log.error("close session error", e); - } - } - CONNECT_INFO_THREAD_LOCAL.remove(); - } - } - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/IDriverManager.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/IDriverManager.java deleted file mode 100644 index 423fdfba9..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/IDriverManager.java +++ /dev/null @@ -1,201 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.sql; - -import java.io.File; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.sql.Connection; -import java.sql.Driver; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.Map; -import java.util.Properties; -import java.util.concurrent.ConcurrentHashMap; - -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; -import ai.chat2db.server.domain.support.model.DriverEntry; -import ai.chat2db.server.domain.support.util.JdbcJarUtils; -import com.alibaba.fastjson2.JSON; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static ai.chat2db.server.domain.support.util.JdbcJarUtils.getFullPath; - -/** - * @author jipengfei - * @version : IsolationDriverManager.java - */ -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(); - - public static Connection getConnection(String url, DriverTypeEnum driver) throws SQLException { - Properties info = new Properties(); - return getConnection(url, info, driver); - } - - public static Connection getConnection(String url, String user, String password, DriverTypeEnum driverTypeEnum) - throws SQLException { - Properties info = new Properties(); - if (user != null) { - info.put("user", user); - } - - if (password != null) { - info.put("password", password); - } - - return getConnection(url, info, driverTypeEnum); - } - - public static Connection getConnection(String url, String user, String password, DriverTypeEnum driverTypeEnum, - Map properties) - throws SQLException { - Properties info = new Properties(); - if (StringUtils.isNotEmpty(user)) { - info.put("user", user); - } - - if (StringUtils.isNotEmpty(password)) { - info.put("password", password); - } - info.putAll(properties); - return getConnection(url, info, driverTypeEnum); - } - - public static Connection getConnection(String url, Properties info, DriverTypeEnum driverTypeEnum) - throws SQLException { - if (url == null) { - throw new SQLException("The url cannot be null", "08001"); - } - DriverManager.println("DriverManager.getConnection(\"" + url + "\")"); - SQLException reason = null; - DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driverTypeEnum); - if (driverEntry == null) { - driverEntry = getJDBCDriver(driverTypeEnum); - } - try { - Connection con = driverEntry.getDriver().connect(url, info); - if (con != null) { - DriverManager.println("getConnection returning " + driverEntry.getDriver().getClass().getName()); - return con; - } - } catch (SQLException var7) { - Connection con = tryConnectionAgain(driverTypeEnum.getDbTypeEnum() ,driverEntry, url, info); - if (con != null) { - return con; - } else { - throw var7; - } - } - - if (reason != null) { - DriverManager.println("getConnection failed: " + reason); - throw reason; - } else { - DriverManager.println("getConnection: no suitable driver found for " + url); - throw new SQLException("No suitable driver found for " + url, "08001"); - } - } - - private static Connection tryConnectionAgain(DbTypeEnum dbTypeEnum, DriverEntry driverEntry, String url, - Properties info) throws SQLException { - if (DbTypeEnum.MYSQL.equals(dbTypeEnum)) { - if (!info.containsKey("useSSL")) { - info.put("useSSL", "false"); - } - return driverEntry.getDriver().connect(url, info); - } - return null; - } - - private static DriverEntry getJDBCDriver(DriverTypeEnum driverTypeEnum) - throws SQLException { - synchronized (driverTypeEnum) { - try { - if (DRIVER_ENTRY_MAP.containsKey(driverTypeEnum)) { - return DRIVER_ENTRY_MAP.get(driverTypeEnum); - } - ClassLoader cl = getClassLoader(driverTypeEnum); - Driver driver = (Driver)cl.loadClass(driverTypeEnum.getDriverClass()).newInstance(); - DriverEntry driverEntry = DriverEntry.builder().driverTypeEnum(driverTypeEnum).driver(driver).build(); - DRIVER_ENTRY_MAP.put(driverTypeEnum, driverEntry); - return driverEntry; - } catch (Exception e) { - log.error("getJDBCDriver error", e); - throw new SQLException("getJDBCDriver error", "08001"); - } - } - - } - - public static ClassLoader getClassLoader(DriverTypeEnum driverTypeEnum) throws MalformedURLException { - String jarPath = driverTypeEnum.getJar(); - if (CLASS_LOADER_MAP.containsKey(jarPath)) { - return CLASS_LOADER_MAP.get(jarPath); - } else { - synchronized (jarPath) { - if (CLASS_LOADER_MAP.containsKey(jarPath)) { - return CLASS_LOADER_MAP.get(jarPath); - } - String[] jarPaths = jarPath.split(","); - URL[] urls = new URL[jarPaths.length]; - for (int i = 0; i < jarPaths.length; i++) { - File driverFile = new File(getFullPath(jarPaths[i])); - urls[i] = driverFile.toURI().toURL(); - } - //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); - - URLClassLoader cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); - log.info("ClassLoader class:{}", cl.hashCode()); - log.info("ClassLoader URLs:{}", JSON.toJSONString(cl.getURLs())); - - try { - cl.loadClass(driverTypeEnum.getDriverClass()); - } catch (Exception e) { - //如果报错删除目录重试一次 - for (int i = 0; i < jarPaths.length; i++) { - File driverFile = new File(JdbcJarUtils.getNewFullPath(jarPaths[i])); - urls[i] = driverFile.toURI().toURL(); - } - //urls[jarPaths.length] = new File(JdbcJarUtils.getFullPath("HikariCP-4.0.3.jar")).toURI().toURL(); - cl = new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); - - } - CLASS_LOADER_MAP.put(jarPath, cl); - return cl; - } - } - } - - //private static List loadClass(String jarPath, ClassLoader classLoader) throws IOException { - // Long s1 = System.currentTimeMillis(); - // JarFile jarFile = new JarFile(getFullPath(jarPath)); - // Enumeration entries = jarFile.entries(); - // List classes = new ArrayList(); - // while (entries.hasMoreElements()) { - // JarEntry jarEntry = entries.nextElement(); - // if (jarEntry.getName().endsWith(".class") && !jarEntry.getName().contains("$")) { - // String className = jarEntry.getName().substring(0, jarEntry.getName().length() - 6).replaceAll("/", - // "."); - // try { - // classes.add(classLoader.loadClass(className)); - // // log.info("loadClass:{}", className); - // } catch (Throwable var7) { - // //log.error("getClasses error "+className, var7); - // } - // } - // } - // log.info("loadClass cost:{}", System.currentTimeMillis() - s1); - // return classes; - //} - -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SQLExecutor.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SQLExecutor.java deleted file mode 100644 index 73247e857..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SQLExecutor.java +++ /dev/null @@ -1,407 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.sql; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.ResultSetMetaData; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -import ai.chat2db.server.domain.support.model.ExecuteResult; -import ai.chat2db.server.domain.support.model.Header; -import ai.chat2db.server.domain.support.model.Procedure; -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.model.TableColumn; -import ai.chat2db.server.domain.support.model.TableIndex; -import ai.chat2db.server.domain.support.model.TableIndexColumn; - -import cn.hutool.core.date.TimeInterval; -import com.google.common.collect.Lists; -import lombok.extern.slf4j.Slf4j; -import org.springframework.jdbc.support.JdbcUtils; -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import static ai.chat2db.server.domain.support.util.ResultSetUtils.buildColumn; -import static ai.chat2db.server.domain.support.util.ResultSetUtils.buildFunction; -import static ai.chat2db.server.domain.support.util.ResultSetUtils.buildProcedure; -import static ai.chat2db.server.domain.support.util.ResultSetUtils.buildTable; -import static ai.chat2db.server.domain.support.util.ResultSetUtils.buildTableIndexColumn; - -/** - * Dbhub 统一数据库连接管理 - * TODO 长时间不用连接可以关闭,待优化 - * - * @author jipengfei - * @version : DbhubDataSource.java - */ -@Slf4j -public class SQLExecutor { - /** - * 全局单例 - */ - private static final SQLExecutor INSTANCE = new SQLExecutor(); - - private SQLExecutor() { - } - - public static SQLExecutor getInstance() { - return INSTANCE; - } - - public Connection getConnection() throws SQLException { - return DbhubContext.getConnection(); - } - - public void close() { - } - - /** - * 执行sql - * - * @param sql - * @param function - * @return - */ - - public R executeSql(String sql, Function function) { - if (StringUtils.isEmpty(sql)) { - return null; - } - log.info("execute:{}", sql); - Statement stmt = null; - try { - stmt = getConnection().createStatement(); - boolean query = stmt.execute(sql); - // 代表是查询 - if (query) { - ResultSet rs = null; - try { - rs = stmt.getResultSet(); - return function.apply(rs); - } finally { - if (rs != null) { - rs.close(); - } - } - } - } catch (SQLException e) { - throw new RuntimeException(e); - } finally { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - } - return null; - } - - /** - * 执行sql - * - * @param sql - * @return - * @throws SQLException - */ - public ExecuteResult execute(final String sql, Connection connection) throws SQLException { - Assert.notNull(sql, "SQL must not be null"); - log.info("execute:{}", sql); - - ExecuteResult executeResult = ExecuteResult.builder().sql(sql).success(Boolean.TRUE).build(); - Statement stmt = null; - try { - TimeInterval timeInterval=new TimeInterval(); - stmt = connection.createStatement(); - boolean query = stmt.execute(sql.replaceFirst(";", "")); - executeResult.setDescription("执行成功"); - // 代表是查询 - if (query) { - ResultSet rs = null; - try { - rs = stmt.getResultSet(); - // 获取有几列 - ResultSetMetaData resultSetMetaData = rs.getMetaData(); - int col = resultSetMetaData.getColumnCount(); - - // 获取header信息 - List
headerList = Lists.newArrayListWithExpectedSize(col); - executeResult.setHeaderList(headerList); - for (int i = 1; i <= col; i++) { - headerList.add(Header.builder() - .dataType(ai.chat2db.server.domain.support.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(resultSetMetaData.getColumnName(i)) - .build()); - } - - // 获取数据信息 - List> dataList = Lists.newArrayList(); - executeResult.setDataList(dataList); - - while (rs.next()) { - List row = Lists.newArrayListWithExpectedSize(col); - dataList.add(row); - for (int i = 1; i <= col; i++) { - row.add(ai.chat2db.server.domain.support.util.JdbcUtils.getResultSetValue(rs, i)); - } - } - executeResult.setDuration(timeInterval.interval()); - return executeResult; - } finally { - JdbcUtils.closeResultSet(rs); - } - } else { - // 修改或者其他 - executeResult.setUpdateCount(stmt.getUpdateCount()); - } - } finally { - JdbcUtils.closeStatement(stmt); - } - return executeResult; - } - - /** - * 执行sql - * - * @param sql - * @return - * @throws SQLException - */ - public ExecuteResult execute(String sql) throws SQLException { - return execute(sql, getConnection()); - } - - public void connectDatabase(String database) { - if (StringUtils.isEmpty(database)) { - return; - } - ConnectInfo info = DbhubContext.getConnectInfo(); - switch (info.getDbType()) { - case MYSQL -> { - try { - execute("use `" + database + "`;"); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - case SQLSERVER -> { - try { - execute("use " + database + ";"); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - case ORACLE, H2, SQLITE -> { - - } - case POSTGRESQL -> { - //close(); - info.setDatabase(database); - try { - getConnection(); - } catch (SQLException e) { - throw new RuntimeException(e); - } - - } - case REDIS -> { - try { - execute("select " + database); - } catch (SQLException e) { - throw new RuntimeException(e); - } - } - default -> { - - } - } - } - - /** - * 获取所有的数据库 - * - * @return - */ - public List databases() { - List tables = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getCatalogs(); - if (resultSet != null) { - while (resultSet.next()) { - tables.add(resultSet.getString("TABLE_CAT")); - } - } - } catch (SQLException e) { - close(); - throw new RuntimeException(e); - } - return tables; - } - - /** - * 获取所有的schema - * - * @param databaseName - * @param schemaName - * @return - */ - public List schemas(String databaseName, String schemaName) { - List schemaList = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getSchemas(databaseName, schemaName); - if (resultSet != null) { - while (resultSet.next()) { - schemaList.add(resultSet.getString("TABLE_SCHEM")); - } - } - } catch (SQLException e) { - close(); - throw new RuntimeException(e); - } - return schemaList; - } - - /** - * 获取所有的数据库表 - * - * @param databaseName - * @param schemaName - * @param tableName - * @param types - * @return - */ - public List
tables(String databaseName, String schemaName, String tableName, String types[]) { - List
tables = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getTables(databaseName, schemaName, tableName, - types); - if (resultSet != null) { - while (resultSet.next()) { - tables.add(buildTable(resultSet)); - } - } - } catch (SQLException e) { - close(); - throw new RuntimeException(e); - } - return tables; - } - - /** - * 获取所有的数据库表列 - * - * @param databaseName - * @param schemaName - * @param tableName - * @param columnName - * @return - */ - public List columns(String databaseName, String schemaName, String tableName, String columnName) { - List tableColumns = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getColumns(databaseName, schemaName, tableName, - columnName); - if (resultSet != null) { - while (resultSet.next()) { - tableColumns.add(buildColumn(resultSet)); - } - } - } catch (Exception e) { - close(); - throw new RuntimeException(e); - } - return tableColumns; - } - - /** - * 获取所有的数据库表索引 - * - * @param databaseName - * @param schemaName - * @param tableName - * @return - */ - public List indexes(String databaseName, String schemaName, String tableName) { - List tableIndices = Lists.newArrayList(); - try { - List tableIndexColumns = Lists.newArrayList(); - ResultSet resultSet = getConnection().getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, - false); - - while (resultSet != null && resultSet.next()) { - tableIndexColumns.add(buildTableIndexColumn(resultSet)); - } - - tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( - Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() - .stream().forEach(entry -> { - TableIndex tableIndex = new TableIndex(); - TableIndexColumn column = entry.getValue().get(0); - tableIndex.setName(entry.getKey()); - tableIndex.setTableName(column.getTableName()); - tableIndex.setSchemaName(column.getSchemaName()); - tableIndex.setDatabaseName(column.getDatabaseName()); - tableIndex.setUnique(!column.getNonUnique()); - tableIndex.setColumnList(entry.getValue()); - tableIndices.add(tableIndex); - }); - } catch (SQLException e) { - close(); - throw new RuntimeException(e); - } - return tableIndices; - } - - /** - * 获取所有的函数 - * - * @param databaseName - * @param schemaName - * @return - */ - public List functions(String databaseName, - String schemaName) { - List functions = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getFunctions(databaseName, schemaName, null); - while (resultSet != null && resultSet.next()) { - functions.add(buildFunction(resultSet)); - } - } catch (Exception e) { - close(); - throw new RuntimeException(e); - } - return functions; - } - - /** - * 获取所有的存储过程 - * - * @param databaseName - * @param schemaName - * @return - */ - public List procedures(String databaseName, String schemaName) { - List procedures = Lists.newArrayList(); - try { - ResultSet resultSet = getConnection().getMetaData().getProcedures(databaseName, schemaName, null); - while (resultSet != null && resultSet.next()) { - procedures.add(buildProcedure(resultSet)); - } - } catch (Exception e) { - close(); - throw new RuntimeException(e); - } - return procedures; - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SSHManager.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SSHManager.java deleted file mode 100644 index 0a1c3b4b8..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/sql/SSHManager.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.sql; - -import java.util.concurrent.ConcurrentHashMap; - -import ai.chat2db.server.domain.support.model.SSHInfo; - -import com.jcraft.jsch.JSch; -import com.jcraft.jsch.Session; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : SSHSessionManager.java - */ -@Slf4j -public class SSHManager { - - private static final ConcurrentHashMap SSH_SESSION_MAP = new ConcurrentHashMap(); - - public static Session getSSHSession(SSHInfo sshInfo) { - Session session = SSH_SESSION_MAP.get(sshInfo); - if (session != null && session.isConnected()) { - return session; - } else { - return createSession(sshInfo); - } - } - - private static Session createSession(SSHInfo ssh) { - synchronized (ssh) { - Session session = SSH_SESSION_MAP.get(ssh); - if (session != null && session.isConnected()) { - return session; - } - try { - JSch jSch = new JSch(); - session = jSch.getSession(ssh.getUserName(), ssh.getHostName(), Integer.parseInt(ssh.getPort())); - session.setPassword(ssh.getPassword()); - session.setConfig("StrictHostKeyChecking", "no"); - session.connect(); - SSH_SESSION_MAP.put(ssh, session); - } catch (Exception e) { - throw new RuntimeException("create ssh session error", e); - } - - if (StringUtils.isNotBlank(ssh.getLocalPort()) && StringUtils.isNotBlank(ssh.getRHost()) - && StringUtils.isNotBlank(ssh.getRPort())) { - try { - int port1 = session.setPortForwardingL(Integer.parseInt(ssh.getLocalPort()), ssh.getRHost(), - Integer.parseInt(ssh.getRPort())); - } catch (Exception e) { - if (session != null && session.isConnected()) { - session.disconnect(); - SSH_SESSION_MAP.remove(ssh); - } - throw new RuntimeException(ssh.getLocalPort() + " port is used,please change to another port ", e); - } - } - return session; - } - } - - public static void close() { - SSH_SESSION_MAP.forEach((k, v) -> { - if (v != null && v.isConnected()) { - try { - v.delPortForwardingL(Integer.parseInt(k.getLocalPort())); - } catch (Exception e) { - log.error("delPortForwardingL error", e); - } - try { - v.disconnect(); - } catch (Exception e) { - log.error("disconnect error", e); - } - } - }); - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JSchUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JSchUtils.java deleted file mode 100644 index 27ae4c96a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JSchUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -///** -// * alibaba.com Inc. -// * Copyright (c) 2004-2023 All Rights Reserved. -// */ -//package ai.chat2db.server.domain.support.util; -// -//import ai.chat2db.server.domain.support.model.SSHInfo; -// -//import com.jcraft.jsch.JSch; -//import com.jcraft.jsch.JSchException; -//import com.jcraft.jsch.Session; -// -///** -// * @author jipengfei -// * @version : JSchUtil.java -// */ -//public class JSchUtils { -// -// public static Session getSession(SSHInfo ssh, String host, String port) throws JSchException { -// JSch jSch = new JSch(); -// Session session = jSch.getSession(ssh.getUserName(), ssh.getHostName(), -// Integer.parseInt(ssh.getPort())); -// session.setPassword(ssh.getPassword()); -// session.setConfig("StrictHostKeyChecking", "no"); -// session.connect(); -// int port1 = session.setPortForwardingL(Integer.parseInt(ssh.getLocalPort()), host, -// Integer.parseInt(port)); -// return session; -// } -// -// public static Session getSession(SSHInfo ssh) throws JSchException { -// JSch jSch = new JSch(); -// Session session = jSch.getSession(ssh.getUserName(), ssh.getHostName(), -// Integer.parseInt(ssh.getPort())); -// session.setPassword(ssh.getPassword()); -// session.setConfig("StrictHostKeyChecking", "no"); -// session.connect(); -// return session; -// } -//} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcJarUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcJarUtils.java deleted file mode 100644 index 22dde2794..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcJarUtils.java +++ /dev/null @@ -1,152 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.util; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.concurrent.Executors; - -import ai.chat2db.server.domain.support.sql.DbhubContext; - -import okhttp3.Call; -import okhttp3.Callback; -import okhttp3.Dispatcher; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; - -/** - * @author jipengfei - * @version : JdbcJarUtils.java - */ -public class JdbcJarUtils { - - private static final OkHttpClient async_client = new OkHttpClient.Builder() - .dispatcher(new Dispatcher(Executors.newFixedThreadPool(20))) // 设定线程池大小 - .build(); - - private static final OkHttpClient client = new OkHttpClient(); - - private static final String PATH = System.getProperty("user.home") + File.separator + ".chat2db" + File.separator - + "jdbc-lib" + File.separator; - - static { - File file = new File(PATH); - if (!file.exists()) { - file.mkdirs(); - } - } - - public static void asyncDownload(List urls) throws Exception { - for (String url : urls) { - String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); - File file = new File(outputPath); - if (file.exists()) { - System.out.println("File already exists: " + outputPath); - continue; - } - asyncDownload(url); - } - } - - public static void asyncDownload(String url) throws Exception { - String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); - File file = new File(outputPath); - if (file.exists()) { - file.delete(); - } - Request request = new Request.Builder() - .url(url) - .build(); - async_client.newCall(request).enqueue(new Callback() { - @Override - public void onFailure(Call call, IOException e) { - } - - @Override - public void onResponse(Call call, Response response) throws IOException { - if (!response.isSuccessful()) { - throw new IOException("Unexpected code " + response); - } - try (InputStream is = response.body().byteStream(); - FileOutputStream fos = new FileOutputStream(outputPath)) { - byte[] buffer = new byte[2048]; - int length; - while ((length = is.read(buffer)) != -1) { - fos.write(buffer, 0, length); - } - fos.flush(); - } - System.out.println("File downloaded: " + outputPath); - } - }); - } - - public static void download(String url) throws IOException { - String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); - File file = new File(outputPath); - if (file.exists()) { - file.delete(); - } - Request request = new Request.Builder() - .url(url) - .build(); - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) { - throw new IOException("Unexpected code " + response); - } - try (InputStream is = response.body().byteStream(); - FileOutputStream fos = new FileOutputStream(outputPath)) { - - byte[] buffer = new byte[2048]; - int length; - while ((length = is.read(buffer)) != -1) { - fos.write(buffer, 0, length); - } - fos.flush(); - } - System.out.println("File downloaded: " + outputPath); - } - } - - public static String getNewFullPath(String jarPath) { - String path = PATH + jarPath; - File file = new File(path); - if (file.exists()) { - file.delete(); - } - return getFullPath(jarPath); - } - - public static String getFullPath(String jarPath) { - String path = PATH + jarPath; - File file = new File(path); - if (!file.exists()) { - String url = getDownloadUrl(jarPath); - try { - download(url); - } catch (IOException e) { - try { - download(url); - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - } - return path; - } - - private static String getDownloadUrl(String jarPath) { - for (String path : DbhubContext.JDBC_JAR_DOWNLOAD_URL_LIST) { - if (path.contains(jarPath)) { - return path.trim(); - } - } - return null; - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java deleted file mode 100644 index 2d3e278b9..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/JdbcUtils.java +++ /dev/null @@ -1,263 +0,0 @@ -package ai.chat2db.server.domain.support.util; - -import java.sql.Blob; -import java.sql.Clob; -import java.sql.Connection; -import java.sql.Date; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Timestamp; -import java.sql.Types; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.time.format.DateTimeFormatter; -import java.util.Locale; -import java.util.Map; - -import com.alibaba.druid.DbType; - -import ai.chat2db.server.domain.support.enums.DataTypeEnum; -import ai.chat2db.server.domain.support.enums.DbTypeEnum; -import ai.chat2db.server.domain.support.enums.DriverTypeEnum; -import ai.chat2db.server.domain.support.model.DataSourceConnect; -import ai.chat2db.server.domain.support.model.SSHInfo; -import ai.chat2db.server.domain.support.sql.IDriverManager; -import ai.chat2db.server.domain.support.sql.SSHManager; -import cn.hutool.core.date.DateUtil; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; -import lombok.extern.slf4j.Slf4j; - -/** - * jdbc工具类 - * - * @author Jiaju Zhuang - */ -@Slf4j -public class JdbcUtils { - - private static final String DEFAULT_DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; - private static final DateTimeFormatter DEFAULT_DATETIME_FORMAT = DateTimeFormatter - .ofPattern(DEFAULT_DATETIME_PATTERN, Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - - private static final DateTimeFormatter DEFAULT_DATE_FORMAT = DateTimeFormatter - .ofPattern("yyyy-MM-dd", Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - private static final DateTimeFormatter DEFAULT_DATETIME_TZ_FORMAT = DateTimeFormatter - .ofPattern("yyyy-MM-dd HH:mm:ss.SSS Z", Locale.getDefault()) - .withZone(ZoneId.systemDefault()); - - /** - * 获取德鲁伊的的数据库类型 - * - * @param dbType - * @return - */ - public static DbType parse2DruidDbType(DbTypeEnum dbType) { - if (dbType == null) { - return null; - } - try { - return DbType.valueOf(dbType.getCode().toLowerCase()); - } catch (Exception e) { - return null; - } - } - - /** - * 解析字段的类型 - * - * @param typeName - * @param type - * @return - */ - public static DataTypeEnum resolveDataType(String typeName, int type) { - switch (getTypeByTypeName(typeName, type)) { - case Types.BOOLEAN: - return DataTypeEnum.BOOLEAN; - case Types.CHAR: - case Types.VARCHAR: - case Types.NVARCHAR: - case Types.LONGVARCHAR: - case Types.LONGNVARCHAR: - return DataTypeEnum.STRING; - case Types.BIGINT: - case Types.DECIMAL: - case Types.DOUBLE: - case Types.FLOAT: - case Types.INTEGER: - case Types.NUMERIC: - case Types.REAL: - case Types.SMALLINT: - return DataTypeEnum.NUMERIC; - case Types.BIT: - case Types.TINYINT: - if (typeName.toLowerCase().contains("bool")) { - // Declared as numeric but actually it's a boolean - return DataTypeEnum.BOOLEAN; - } - return DataTypeEnum.NUMERIC; - case Types.DATE: - case Types.TIME: - case Types.TIME_WITH_TIMEZONE: - case Types.TIMESTAMP: - case Types.TIMESTAMP_WITH_TIMEZONE: - return DataTypeEnum.DATETIME; - case Types.BINARY: - case Types.VARBINARY: - case Types.LONGVARBINARY: - return DataTypeEnum.BINARY; - case Types.BLOB: - case Types.CLOB: - case Types.NCLOB: - case Types.SQLXML: - return DataTypeEnum.CONTENT; - case Types.STRUCT: - return DataTypeEnum.STRUCT; - case Types.ARRAY: - return DataTypeEnum.ARRAY; - case Types.ROWID: - return DataTypeEnum.ROWID; - case Types.REF: - return DataTypeEnum.REFERENCE; - case Types.OTHER: - return DataTypeEnum.OBJECT; - default: - return DataTypeEnum.UNKNOWN; - } - } - - private static int getTypeByTypeName(String typeName, int type) { - // [JDBC: SQLite driver uses VARCHAR value type for all LOBs] - if (type == Types.OTHER || type == Types.VARCHAR) { - if ("BLOB".equalsIgnoreCase(typeName)) { - return Types.BLOB; - } else if ("CLOB".equalsIgnoreCase(typeName)) { - return Types.CLOB; - } else if ("NCLOB".equalsIgnoreCase(typeName)) { - return Types.NCLOB; - } - } else if (type == Types.BIT) { - // Workaround for MySQL (and maybe others) when TINYINT(1) == BOOLEAN - if ("TINYINT".equalsIgnoreCase(typeName)) { - return Types.TINYINT; - } - } - return type; - } - - /** - * 获取一个返回值 - * - * @param rs - * @param index - * @return - * @throws SQLException - */ - public static String getResultSetValue(ResultSet rs, int index) throws SQLException { - Object obj = rs.getObject(index); - if (obj == null) { - return null; - } - - if (obj instanceof Blob blob) { - return "(BLOB " + blob.length() + ")"; - } - if (obj instanceof Clob clob) { - return "(CLOB " + clob.length() + ")"; - } - if (obj instanceof Timestamp timestamp) { - return DateUtil.format(timestamp, DEFAULT_DATETIME_FORMAT); - } - - String className = obj.getClass().getName(); - if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) { - return DateUtil.format(rs.getTimestamp(index), DEFAULT_DATETIME_TZ_FORMAT); - } - if (className.startsWith("oracle.sql.DATE")) { - String metaDataClassName = rs.getMetaData().getColumnClassName(index); - if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { - return DateUtil.format(rs.getTimestamp(index), DEFAULT_DATETIME_FORMAT); - } else { - return DateUtil.format(rs.getDate(index), DEFAULT_DATETIME_FORMAT); - } - } - if (obj instanceof Date date) { - if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) { - return DateUtil.format(rs.getDate(index), DEFAULT_DATETIME_FORMAT); - } -// return DateUtil.format(date, DEFAULT_DATETIME_FORMAT); - return null; - } - if (obj instanceof LocalDateTime localDateTime) { - return localDateTime.toString(); - } - if (obj instanceof LocalDate localDate) { - return localDate.toString(); - } - if (obj instanceof Number) { - return obj.toString(); - } - return obj.toString(); - } - - /** - * 测试数据库连接 - * - * @param url 数据库连接 - * @param userName 用户名 - * @param password 密码 - * @param dbType 数据库类型 - * @return - */ - public static DataSourceConnect testConnect(String url, String host, String port, - String userName, String password, DbTypeEnum dbType, - String jdbc, SSHInfo ssh, Map properties) { - DataSourceConnect dataSourceConnect = DataSourceConnect.builder() - .success(Boolean.TRUE) - .build(); - Session session = null; - Connection connection = null; - // 加载驱动 - try { - if (ssh.isUse()) { - session = SSHManager.getSSHSession(ssh); - url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); - } - // 创建连接 - connection = IDriverManager.getConnection(url, userName, password, - DriverTypeEnum.getDriver(dbType, jdbc), properties); - } catch (Exception e) { - log.error("connection fail:", e); - dataSourceConnect.setSuccess(Boolean.FALSE); - // 获取最后一个异常的信息给前端 - Throwable t = e; - while (t.getCause() != null) { - t = t.getCause(); - } - dataSourceConnect.setMessage(t.getMessage()); - return dataSourceConnect; - } finally { - if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - // ignore - } - } - if (session != null) { - try { - session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); - session.disconnect(); - - } catch (JSchException e) { - } - } - } - dataSourceConnect.setDescription("成功"); - return dataSourceConnect; - } - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/ResultSetUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/ResultSetUtils.java deleted file mode 100644 index 4607674a4..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/ResultSetUtils.java +++ /dev/null @@ -1,101 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.util; - -import java.sql.ResultSet; -import java.sql.SQLException; - -import ai.chat2db.server.domain.support.model.Procedure; -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.model.TableColumn; -import ai.chat2db.server.domain.support.model.TableIndexColumn; - -/** - * @author jipengfei - * @version : ResultSetUtils.java - */ -public class ResultSetUtils { - - public static ai.chat2db.server.domain.support.model.Function buildFunction(ResultSet resultSet) { - ai.chat2db.server.domain.support.model.Function function - = new ai.chat2db.server.domain.support.model.Function(); - try { - function.setDatabaseName(resultSet.getString("FUNCTION_CAT")); - function.setSchemaName(resultSet.getString("FUNCTION_SCHEM")); - function.setFunctionName(resultSet.getString("FUNCTION_NAME")); - function.setRemarks(resultSet.getString("REMARKS")); - function.setFunctionType(resultSet.getShort("FUNCTION_TYPE")); - function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return function; - } - - public static Procedure buildProcedure(ResultSet resultSet) { - Procedure procedure = new Procedure(); - try { - procedure.setDatabaseName(resultSet.getString("PROCEDURE_CAT")); - procedure.setSchemaName(resultSet.getString("PROCEDURE_SCHEM")); - procedure.setProcedureName(resultSet.getString("PROCEDURE_NAME")); - procedure.setRemarks(resultSet.getString("REMARKS")); - procedure.setProcedureType(resultSet.getShort("PROCEDURE_TYPE")); - procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - } catch (SQLException e) { - throw new RuntimeException(e); - } - return procedure; - } - - public static TableIndexColumn buildTableIndexColumn(ResultSet resultSet) throws SQLException { - TableIndexColumn tableIndexColumn = new TableIndexColumn(); - tableIndexColumn.setColumnName(resultSet.getString("COLUMN_NAME")); - tableIndexColumn.setIndexName(resultSet.getString("INDEX_NAME")); - tableIndexColumn.setAscOrDesc(resultSet.getString("ASC_OR_DESC")); - tableIndexColumn.setCardinality(resultSet.getLong("CARDINALITY")); - tableIndexColumn.setPages(resultSet.getLong("PAGES")); - tableIndexColumn.setFilterCondition(resultSet.getString("FILTER_CONDITION")); - tableIndexColumn.setIndexQualifier(resultSet.getString("INDEX_QUALIFIER")); - // tableIndexColumn.setIndexType(resultSet.getShort("TYPE")); - tableIndexColumn.setNonUnique(resultSet.getBoolean("NON_UNIQUE")); - tableIndexColumn.setOrdinalPosition(resultSet.getShort("ORDINAL_POSITION")); - tableIndexColumn.setDatabaseName(resultSet.getString("TABLE_CAT")); - tableIndexColumn.setSchemaName(resultSet.getString("TABLE_SCHEM")); - tableIndexColumn.setTableName(resultSet.getString("TABLE_NAME")); - return tableIndexColumn; - } - - public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { - TableColumn tableColumn = new TableColumn(); - tableColumn.setDatabaseName(resultSet.getString("TABLE_CAT")); - tableColumn.setSchemaName(resultSet.getString("TABLE_SCHEM")); - tableColumn.setTableName(resultSet.getString("TABLE_NAME")); - tableColumn.setName(resultSet.getString("COLUMN_NAME")); - tableColumn.setComment(resultSet.getString("REMARKS")); - tableColumn.setDefaultValue(resultSet.getString("COLUMN_DEF")); - tableColumn.setTypeName(resultSet.getString("TYPE_NAME")); - tableColumn.setColumnSize(resultSet.getInt("COLUMN_SIZE")); - tableColumn.setDataType(resultSet.getInt("DATA_TYPE")); - tableColumn.setNullable(resultSet.getInt("NULLABLE") == 1); - tableColumn.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); - tableColumn.setAutoIncrement("YES".equals(resultSet.getString("IS_AUTOINCREMENT"))); - //tableColumn.setGeneratedColumn("YES".equals(resultSet.getString("IS_GENERATEDCOLUMN"))); - tableColumn.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); - tableColumn.setDecimalDigits(resultSet.getInt("DECIMAL_DIGITS")); - tableColumn.setNumPrecRadix(resultSet.getInt("NUM_PREC_RADIX")); - tableColumn.setCharOctetLength(resultSet.getInt("CHAR_OCTET_LENGTH")); - return tableColumn; - } - - public static Table buildTable(ResultSet resultSet) throws SQLException { - Table table = new Table(); - table.setName(resultSet.getString("TABLE_NAME")); - table.setComment(resultSet.getString("REMARKS")); - table.setDatabaseName(resultSet.getString("TABLE_CAT")); - table.setSchemaName(resultSet.getString("TABLE_SCHEM")); - table.setType(resultSet.getString("TABLE_TYPE")); - return table; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/SqlUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/SqlUtils.java deleted file mode 100644 index 742d2b352..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/ai/chat2db/server/domain/support/util/SqlUtils.java +++ /dev/null @@ -1,436 +0,0 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.server.domain.support.util; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import ai.chat2db.server.domain.support.enums.CollationEnum; -import ai.chat2db.server.domain.support.enums.IndexTypeEnum; -import ai.chat2db.server.domain.support.model.Sql; -import ai.chat2db.server.domain.support.model.Table; -import ai.chat2db.server.domain.support.model.TableColumn; -import ai.chat2db.server.domain.support.model.TableIndex; -import ai.chat2db.server.domain.support.model.TableIndexColumn; -import ai.chat2db.server.tools.common.util.EasyBooleanUtils; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import com.alibaba.druid.DbType; -import com.alibaba.druid.sql.ast.SQLDataTypeImpl; -import com.alibaba.druid.sql.ast.expr.SQLCharExpr; -import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddColumn; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddConstraint; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropColumnItem; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropPrimaryKey; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement; -import com.alibaba.druid.sql.ast.statement.SQLAssignItem; -import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition; -import com.alibaba.druid.sql.ast.statement.SQLColumnPrimaryKey; -import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement; -import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement; -import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; -import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint; -import com.alibaba.druid.sql.ast.statement.SQLNullConstraint; -import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; -import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey; -import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique; -import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableChangeColumn; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableModifyColumn; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex; - -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; - -/** - * @author jipengfei - * @version : SqlUtils.java - */ -public class SqlUtils { - - public static List buildSql(Table oldTable, Table newTable) { - List sqlList = new ArrayList<>(); - // 创建表 - if (oldTable == null) { - MySqlCreateTableStatement mySqlCreateTableStatement = new MySqlCreateTableStatement(); - mySqlCreateTableStatement.setDbType(DbType.mysql); - mySqlCreateTableStatement.setTableName(newTable.getName()); - if (!Objects.isNull(newTable.getComment())) { - mySqlCreateTableStatement.setComment(new MySqlCharExpr(newTable.getComment())); - } - List columnList = newTable.getColumnList(); - if (!CollectionUtils.isEmpty(columnList)) { - for (TableColumn tableColumn : columnList) { - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - mySqlCreateTableStatement.addColumn(sqlColumnDefinition); - sqlColumnDefinition.setName(tableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(tableColumn.getColumnType())); - if (BooleanUtils.isNotFalse(tableColumn.getNullable())) { - sqlColumnDefinition.addConstraint(new SQLNullConstraint()); - } else { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(tableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(tableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(tableColumn.getAutoIncrement())); - if (!Objects.isNull(tableColumn.getComment())) { - sqlColumnDefinition.setComment(tableColumn.getComment()); - } - if (BooleanUtils.isTrue(tableColumn.getPrimaryKey())) { - sqlColumnDefinition.addConstraint(new SQLColumnPrimaryKey()); - } - } - //// 主键 - //List primaryKeyColumnList = EasyCollectionUtils.stream(columnList) - // .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - // .collect(Collectors.toList()); - //if (!CollectionUtils.isEmpty(primaryKeyColumnList)) { - // MySqlPrimaryKey mySqlPrimaryKey = new MySqlPrimaryKey(); - // mySqlCreateTableStatement.getTableElementList().add(mySqlPrimaryKey); - // for (TableColumn tableColumn : primaryKeyColumnList) { - // mySqlPrimaryKey.addColumn(new SQLIdentifierExpr(tableColumn.getName())); - // } - //} - } - - // 索引 - List indexList = newTable.getIndexList(); - if (!CollectionUtils.isEmpty(indexList)) { - for (TableIndex tableIndex : indexList) { - if (IndexTypeEnum.UNIQUE.getCode().equals(tableIndex.getType())) { - MySqlUnique mySqlUnique = new MySqlUnique(); - mySqlCreateTableStatement.getTableElementList().add(mySqlUnique); - mySqlUnique.setName(tableIndex.getName()); - mySqlUnique.setComment(new SQLCharExpr(tableIndex.getComment())); - mySqlUnique.getIndexDefinition().setType("unique"); - if (!CollectionUtils.isEmpty(tableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : tableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - mySqlUnique.addColumn(sqlSelectOrderByItem); - } - } - } else { - MySqlTableIndex mySqlTableIndex = new MySqlTableIndex(); - mySqlCreateTableStatement.getTableElementList().add(mySqlTableIndex); - mySqlTableIndex.setName(tableIndex.getName()); - mySqlTableIndex.setComment(new SQLCharExpr(tableIndex.getComment())); - if (!CollectionUtils.isEmpty(tableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : tableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - mySqlTableIndex.addColumn(sqlSelectOrderByItem); - } - } - } - } - } - - sqlList.add(Sql.builder().sql(mySqlCreateTableStatement + ";").build()); - return sqlList; - } - - // 修改表结构 - // 修改表名字 - if (!StringUtils.equals(oldTable.getName(), newTable.getName())) { - MySqlRenameTableStatement mySqlRenameTableStatement = new MySqlRenameTableStatement(); - mySqlRenameTableStatement.setDbType(DbType.mysql); - Item item = new Item(); - item.setName(new SQLIdentifierExpr(oldTable.getName())); - item.setTo(new SQLIdentifierExpr(newTable.getName())); - mySqlRenameTableStatement.addItem(item); - sqlList.add(Sql.builder().sql(mySqlRenameTableStatement + ";").build()); - } - // 修改注释 - if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) { - SQLAlterTableStatement sqlAlterTableStatement = new SQLAlterTableStatement(); - sqlAlterTableStatement.setDbType(DbType.mysql); - SQLAssignItem sqlAssignItem = new SQLAssignItem(); - sqlAssignItem.setTarget(new SQLIdentifierExpr("COMMENT")); - sqlAssignItem.setValue(new MySqlCharExpr(newTable.getComment())); - sqlAlterTableStatement.getTableOptions().add(sqlAssignItem); - sqlList.add(Sql.builder().sql(sqlAlterTableStatement + ";").build()); - } - // 修改字段 - modifyColumn(sqlList, oldTable, newTable); - - // 修改索引 - modifyIndex(sqlList, oldTable, newTable); - return sqlList; - } - - private static void modifyColumn(List sqlList, Table oldTable, Table newTable) { - Map oldColumnMap = EasyCollectionUtils.toIdentityMap(oldTable.getColumnList(), - tableColumn -> { - if (tableColumn.getOldName() != null) { - return tableColumn.getOldName(); - } - return tableColumn.getName(); - }); - Map newColumnMap = EasyCollectionUtils.toIdentityMap(newTable.getColumnList(), - tableColumn -> { - if (tableColumn.getOldName() != null) { - return tableColumn.getOldName(); - } - return tableColumn.getName(); - }); - - SQLAlterTableStatement sqlAlterTableStatement = new SQLAlterTableStatement(); - sqlAlterTableStatement.setDbType(DbType.mysql); - sqlAlterTableStatement.setTableSource(new SQLIdentifierExpr(newTable.getName())); - - newColumnMap.forEach((newTableColumnName, newTableColumn) -> { - TableColumn oldTableColumn = oldColumnMap.get(newTableColumnName); - // 代表新增字段 - if (oldTableColumn == null) { - - SQLAlterTableAddColumn sqlAlterTableAddColumn = new SQLAlterTableAddColumn(); - sqlAlterTableStatement.addItem(sqlAlterTableAddColumn); - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - sqlAlterTableAddColumn.addColumn(sqlColumnDefinition); - sqlColumnDefinition.setName(newTableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (BooleanUtils.isNotTrue(newTableColumn.getNullable())) { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(newTableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(newTableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(newTableColumn.getAutoIncrement())); - if (!Objects.isNull(newTableColumn.getComment())) { - sqlColumnDefinition.setComment(newTableColumn.getComment()); - } - return; - } - // 代表可能修改字段 或者没变 - boolean hasChange = !StringUtils.equals(oldTableColumn.getName(), newTableColumn.getName()) - || !StringUtils.equals(oldTableColumn.getColumnType(), newTableColumn.getColumnType()) - || !EasyBooleanUtils.equals(oldTableColumn.getNullable(), newTableColumn.getNullable(), Boolean.TRUE) - || !StringUtils.equals(oldTableColumn.getDefaultValue(), newTableColumn.getDefaultValue()) - || !EasyBooleanUtils.equals(oldTableColumn.getAutoIncrement(), newTableColumn.getAutoIncrement(), - Boolean.FALSE) - || !StringUtils.equals(oldTableColumn.getComment(), newTableColumn.getComment()); - - // 没有修改字段 - if (!hasChange) { - return; - } - - // 修改字段包含字段名 - if (!StringUtils.equals(oldTableColumn.getName(), newTableColumn.getName())) { - MySqlAlterTableChangeColumn mySqlAlterTableChangeColumn = new MySqlAlterTableChangeColumn(); - sqlAlterTableStatement.addItem(mySqlAlterTableChangeColumn); - mySqlAlterTableChangeColumn.setColumnName(new SQLIdentifierExpr(newTableColumn.getOldName())); - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - mySqlAlterTableChangeColumn.setNewColumnDefinition(sqlColumnDefinition); - sqlColumnDefinition.setName(newTableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (BooleanUtils.isNotTrue(newTableColumn.getNullable())) { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(newTableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(newTableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(newTableColumn.getAutoIncrement())); - if (!Objects.isNull(newTableColumn.getComment())) { - sqlColumnDefinition.setComment(newTableColumn.getComment()); - } - } else { - // 修改字段不包括字段名 - MySqlAlterTableModifyColumn mySqlAlterTableModifyColumn = new MySqlAlterTableModifyColumn(); - sqlAlterTableStatement.addItem(mySqlAlterTableModifyColumn); - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - mySqlAlterTableModifyColumn.setNewColumnDefinition(sqlColumnDefinition); - sqlColumnDefinition.setName(newTableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (BooleanUtils.isNotTrue(newTableColumn.getNullable())) { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(newTableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(newTableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(newTableColumn.getAutoIncrement())); - if (!Objects.isNull(newTableColumn.getComment())) { - sqlColumnDefinition.setComment(newTableColumn.getComment()); - } - } - }); - - oldColumnMap.forEach((oldTableColumnName, oldTableColumn) -> { - TableColumn newTableColumn = newColumnMap.get(oldTableColumnName); - // 代表删除字段 - if (newTableColumn == null) { - SQLAlterTableDropColumnItem sqlAlterTableDropColumnItem = new SQLAlterTableDropColumnItem(); - sqlAlterTableStatement.addItem(sqlAlterTableDropColumnItem); - sqlAlterTableDropColumnItem.addColumn(new SQLIdentifierExpr(oldTableColumn.getName())); - } - }); - - // 比较主键是否有修改 - // 主键 - Set oldPrimaryKeySet = EasyCollectionUtils.stream(oldTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .collect(Collectors.toSet()); - Set newPrimaryKeySet = EasyCollectionUtils.stream(newTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .collect(Collectors.toSet()); - boolean primaryKeyChange = oldPrimaryKeySet.stream() - .anyMatch(oldPrimaryKey -> !newPrimaryKeySet.contains(oldPrimaryKey)) - || newPrimaryKeySet.stream() - .anyMatch(newPrimaryKey -> !oldPrimaryKeySet.contains(newPrimaryKey)); - if (primaryKeyChange) { - sqlAlterTableStatement.addItem(new SQLAlterTableDropPrimaryKey()); - SQLAlterTableAddConstraint sqlAlterTableAddConstraint = new SQLAlterTableAddConstraint(); - sqlAlterTableStatement.addItem(sqlAlterTableAddConstraint); - MySqlPrimaryKey mySqlPrimaryKey = new MySqlPrimaryKey(); - sqlAlterTableAddConstraint.setConstraint(mySqlPrimaryKey); - mySqlPrimaryKey.setIndexType("PRIMARY"); - // 排序 - EasyCollectionUtils.stream(newTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .forEach(tableColumnName -> mySqlPrimaryKey.addColumn( - new SQLSelectOrderByItem(new SQLIdentifierExpr(tableColumnName)))); - } - - if (CollectionUtils.isNotEmpty(sqlAlterTableStatement.getItems())) { - sqlList.add(Sql.builder().sql(sqlAlterTableStatement + ";").build()); - } - } - - private static void modifyIndex(List sqlList, Table oldTable, Table newTable) { - Map oldIndexMap = EasyCollectionUtils.toIdentityMap(oldTable.getIndexList(), - TableIndex::getName); - Map newIndexMap = EasyCollectionUtils.toIdentityMap(newTable.getIndexList(), - TableIndex::getName); - newIndexMap.forEach((newTableIndexName, newTableIndex) -> { - TableIndex oldTableIndex = oldIndexMap.get(newTableIndexName); - // 代表新增索引 - if (oldTableIndex == null) { - SQLCreateIndexStatement sqlCreateIndexStatement = new SQLCreateIndexStatement(); - sqlCreateIndexStatement.setTable(new SQLExprTableSource(newTable.getName())); - sqlCreateIndexStatement.setName(new SQLIdentifierExpr(newTableIndex.getName())); - if (!Objects.isNull(newTableIndex.getComment())) { - sqlCreateIndexStatement.setComment(new SQLCharExpr(newTableIndex.getComment())); - } - if (!CollectionUtils.isEmpty(newTableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : newTableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - sqlCreateIndexStatement.getColumns().add(sqlSelectOrderByItem); - } - } - sqlList.add(Sql.builder().sql(sqlCreateIndexStatement + ";").build()); - return; - } - // 代表可能修改索引 或者没变 - boolean hasChange = !StringUtils.equals(oldTableIndex.getName(), newTableIndex.getName()) - || !StringUtils.equals(oldTableIndex.getComment(), newTableIndex.getComment()) - || !Objects.equals(oldTableIndex.getUnique(), newTableIndex.getUnique()); - if (!hasChange) { - Map oldTableIndexColumnMap = EasyCollectionUtils.toIdentityMap( - oldTableIndex.getColumnList(), TableIndexColumn::getColumnName); - Map newTableIndexColumnMap = EasyCollectionUtils.toIdentityMap( - newTableIndex.getColumnList(), TableIndexColumn::getColumnName); - hasChange = oldTableIndexColumnMap.entrySet() - .stream() - .anyMatch(oldTableIndexColumnEntry -> { - TableIndexColumn newTableIndexColumn = newTableIndexColumnMap.get( - oldTableIndexColumnEntry.getKey()); - if (newTableIndexColumn == null) { - return true; - } - TableIndexColumn oldTableIndexColumn = oldTableIndexColumnEntry.getValue(); - return !StringUtils.equals(oldTableIndexColumn.getColumnName(), - newTableIndexColumn.getColumnName()) - || !CollationEnum.equals(oldTableIndexColumn.getCollation(), - newTableIndexColumn.getCollation()); - }) - || newTableIndexColumnMap.entrySet() - .stream() - .anyMatch(newTableIndexColumnEntry -> { - TableIndexColumn oldTableIndexColumn = oldTableIndexColumnMap.get( - newTableIndexColumnEntry.getKey()); - return oldTableIndexColumn == null; - }); - } - - // 没有修改索引 - if (!hasChange) { - return; - } - // 先删除 - SQLDropIndexStatement sqlDropIndexStatement = new SQLDropIndexStatement(); - sqlDropIndexStatement.setDbType(DbType.mysql); - sqlDropIndexStatement.setTableName(new SQLExprTableSource(newTable.getName())); - sqlDropIndexStatement.setIndexName(new SQLIdentifierExpr(newTableIndex.getName())); - sqlList.add(Sql.builder().sql(sqlDropIndexStatement + ";").build()); - - // 再新增 - SQLCreateIndexStatement sqlCreateIndexStatement = new SQLCreateIndexStatement(); - sqlCreateIndexStatement.setTable(new SQLExprTableSource(newTable.getName())); - sqlCreateIndexStatement.setName(new SQLIdentifierExpr(newTableIndex.getName())); - if (!Objects.isNull(newTableIndex.getComment())) { - sqlCreateIndexStatement.setComment(new SQLCharExpr(newTableIndex.getComment())); - } - if (!CollectionUtils.isEmpty(newTableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : newTableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - sqlCreateIndexStatement.getColumns().add(sqlSelectOrderByItem); - } - } - sqlList.add(Sql.builder().sql(sqlCreateIndexStatement + ";").build()); - }); - - oldIndexMap.forEach((oldTableIndexName, oldTableIndex) -> { - TableIndex newTableIndex = newIndexMap.get(oldTableIndexName); - // 代表删除索引 - if (newTableIndex == null) { - SQLDropIndexStatement sqlDropIndexStatement = new SQLDropIndexStatement(); - sqlDropIndexStatement.setDbType(DbType.mysql); - sqlDropIndexStatement.setTableName(new SQLExprTableSource(newTable.getName())); - sqlDropIndexStatement.setIndexName(new SQLIdentifierExpr(oldTableIndex.getName())); - sqlList.add(Sql.builder().sql(sqlDropIndexStatement + ";").build()); - } - }); - } - - public static String formatSQLString(Object para) { - return para != null ? " '" + para + "' " : null; - } -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/com/alibaba/druid/sql/parser/DbhubSQLParserUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/com/alibaba/druid/sql/parser/DbhubSQLParserUtils.java deleted file mode 100644 index 1729df124..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-support/src/main/java/com/alibaba/druid/sql/parser/DbhubSQLParserUtils.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.alibaba.druid.sql.parser; - -import ai.chat2db.server.domain.support.enums.DbTypeEnum; - -/** - * 临时的sql 解析工具类 - * 已经让druid改了 但是没上线 - * - * @author 是仪 - */ -public class DbhubSQLParserUtils extends SQLParserUtils { - - - public static String format(DbTypeEnum dbType, String tableName) { - if (DbTypeEnum.MYSQL.equals(dbType)) { - return "`" + tableName + "`"; - } else if (DbTypeEnum.ORACLE.equals(dbType)) { - return "\"" + tableName + "\""; - } else if (DbTypeEnum.POSTGRESQL.equals(dbType)) { - return "\"" + tableName + "\""; - } else if (DbTypeEnum.SQLITE.equals(dbType)) { - return "\"" + tableName + "\""; - } else if (DbTypeEnum.SQLSERVER.equals(dbType)) { - return "[" + tableName + "]"; - } else if (DbTypeEnum.H2.equals(dbType)) { - return "\"" + tableName + "\""; - } else { - return "\"" + tableName + "\""; - } - } - - public static String format(String dbType, String tableName) { - if (DbTypeEnum.MYSQL.getCode().equalsIgnoreCase(dbType)) { - return "`" + tableName + "`"; - } else if (DbTypeEnum.ORACLE.getCode().equalsIgnoreCase(dbType)) { - return "\"" + tableName + "\""; - } else if (DbTypeEnum.POSTGRESQL.getCode().equalsIgnoreCase(dbType)) { - return "\"" + tableName + "\""; - } else if (DbTypeEnum.SQLITE.getCode().equalsIgnoreCase(dbType)) { - return "\"" + tableName + "\""; - } else if (DbTypeEnum.SQLSERVER.getCode().equalsIgnoreCase(dbType)) { - return "[" + tableName + "]"; - } else if (DbTypeEnum.H2.getCode().equalsIgnoreCase(dbType)) { - return "\"" + tableName + "\""; - } else { - return "\"" + tableName + "\""; - } - } -} diff --git a/chat2db-server/chat2db-server-domain/pom.xml b/chat2db-server/chat2db-server-domain/pom.xml index fd4be348a..87702c06d 100644 --- a/chat2db-server/chat2db-server-domain/pom.xml +++ b/chat2db-server/chat2db-server-domain/pom.xml @@ -16,7 +16,6 @@ chat2db-server-domain-api chat2db-server-domain-core chat2db-server-domain-repository - chat2db-server-domain-support \ No newline at end of file From 314a993af4d37bd4fac2f8e1e761bd79a7c7c7f8 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 21:38:41 +0800 Subject: [PATCH 0206/1069] release docker --- .github/workflows/pushdocker.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index f4d371b8e..d5fbdc42a 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -35,19 +35,21 @@ jobs: run: | echo "current version: ${{ steps.chat2db_version.outputs.substring }}" - # 安装nodejs + # 安装node - name: Install Node.js uses: actions/setup-node@main with: node-version: 16 + cache: "yarn" + cache-dependency-path: chat2db-client/yarn.lock # 构建静态文件信息 - - name: Npm install & build & copy + - name: Yarn install & build & copy run: | cd chat2db-client - npm install - npm run build:desktop - mv dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front + yarn install + yarn run build:web:prod --appVersion=${{ steps.chat2db_version.outputs.substring }} + cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 安装java - name: Install Java and Maven From eb98a0b7d08cab56b981806c89afe48605113894 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 21:45:14 +0800 Subject: [PATCH 0207/1069] release docker --- .github/workflows/pushdocker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index d5fbdc42a..4b1efa267 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -6,7 +6,6 @@ name: Push To Docker #on: # release: # types: [ published ] -on: [ push, pull_request ] # Workflow's jobs jobs: @@ -27,7 +26,8 @@ jobs: id: chat2db_version uses: bhowell2/github-substring-action@1.0.1 with: - value: 2.0.0 + value: ${{ github.ref }} + index_of_str: "refs/tags/v" # 输出基础信息 From d8ddad6d3c99f1ccf62d4e64fad0cd22e096f52b Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 21:50:29 +0800 Subject: [PATCH 0208/1069] release 2.0.0 --- README.md | 8 ++++---- README_CN.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 79f4e6372..18c4a34d2 100644 --- a/README.md +++ b/README.md @@ -47,10 +47,10 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 | Description | Download | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB%20Setup%201.0.11.exe](https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB%20Setup%201.0.11.exe) | -| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11-arm64.dmg](https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11-arm64.dmg) | -| MacOS X64 | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11.dmg](https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/1.0.11/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/1.0.11/chat2db-server-start.jar) | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe) | +| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg) | +| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg) | +| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar) | ## 🚀 Supported databases | Databases | Status | diff --git a/README_CN.md b/README_CN.md index f3d09b131..cef529360 100644 --- a/README_CN.md +++ b/README_CN.md @@ -50,10 +50,10 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 ## ⏬ 下载安装 | 描述 | 下载地址 | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB%20Setup%201.0.11.exe](https://oss-chat2db.alibaba.com/release/1.0.10/Chat2DB%20Setup%201.0.10.exe) | -| MacOS ARM64 (Apple芯片) | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11-arm64.dmg](https://oss-chat2db.alibaba.com/release/1.0.10/Chat2DB-1.0.10-arm64.dmg) | -| MacOS X64 (Intel芯片) | [https://oss-chat2db.alibaba.com/release/1.0.11/Chat2DB-1.0.11.dmg](https://oss-chat2db.alibaba.com/release/1.0.10/Chat2DB-1.0.10.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/1.0.11/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/1.0.10/chat2db-server-start.jar) | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe) | +| MacOS ARM64 (Apple芯片) | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg) | +| MacOS X64 (Intel芯片) | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg) | +| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar) | ## 🚀 支持的数据库 | 数据库 | 支持计划 | From e1a3266860d23fb5b7efec2ae30d87a085b4fbe4 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 2 Jul 2023 22:03:09 +0800 Subject: [PATCH 0209/1069] release 2.0.0 --- .github/workflows/pushdocker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index 4b1efa267..aa092c2b9 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -3,9 +3,9 @@ name: Push To Docker # Workflow's trigger -#on: -# release: -# types: [ published ] +on: + release: + types: [ published ] # Workflow's jobs jobs: From faf0d1b9646890c1806b4aa7237f255c1085614b Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 2 Jul 2023 22:08:38 +0800 Subject: [PATCH 0210/1069] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 18c4a34d2..8b01c804d 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,10 @@ Languages: English | [中文](README_CN.md) + ## DEMO -https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f +https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb7572465a2e ## 📖 Introduction From 1107cbdfc857056c80cdfe1d49833a45056ba3d1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 2 Jul 2023 22:19:10 +0800 Subject: [PATCH 0211/1069] =?UTF-8?q?fix:=E6=97=A7=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E4=B8=8D=E5=85=BC=E5=AE=B9=E7=9A=84LocalStorage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/layouts/index.tsx | 5 ++++- chat2db-client/src/utils/index.ts | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 7e6ecf893..b7ea168ac 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useLayoutEffect } from 'react'; +import i18n from '@/i18n'; import { Outlet } from 'umi'; import { ConfigProvider, theme, notification } from 'antd'; import { useState } from 'react'; @@ -18,7 +19,7 @@ import { ThemeType, PrimaryColorType, LangType } from '@/constants/'; import { InjectThemeVar } from '@/theme'; import styles from './index.less'; import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorage'; -import i18n from '@/i18n'; +import { clearOlderLocalStorage } from '@/utils'; declare global { interface Window { @@ -34,6 +35,8 @@ declare global { const __ENV: string; } +clearOlderLocalStorage(); + window._Lang = getLang(); const { getDesignToken, useToken } = theme; diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index c5303fa76..817b4b578 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -154,6 +154,8 @@ export function findObjListValue(list: T[], key: K, value: return flag } + +// 处理console的保存和删除操作 export function handelLocalStorageSavedConsole(id: number, type: 'save' | 'delete', text?: string) { const saved = localStorage.getItem(`timing-auto-save-console-v1`); @@ -172,7 +174,7 @@ export function handelLocalStorageSavedConsole(id: number, type: 'save' | 'delet } - +// 获取保存的console export function readLocalStorageSavedConsoleText(id: number) { const saved = localStorage.getItem(`timing-auto-save-console-v1`); let savedObj: any = {} @@ -182,3 +184,11 @@ export function readLocalStorageSavedConsoleText(id: number) { return savedObj[id] || '' } +// 清理就版本不兼容的LocalStorage +export function clearOlderLocalStorage() { + if (localStorage.getItem('app-local-storage-versions') !== 'v2') { + localStorage.clear(); + localStorage.setItem('app-local-storage-versions', 'v2') + } +} + From 27bd63b6f9e45db4e07640357b4fc554f074d2c5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 3 Jul 2023 20:22:03 +0800 Subject: [PATCH 0212/1069] feat: improve --- chat2db-client/src/layouts/index.tsx | 78 +++++++++++-------- .../src/layouts/init/registerMessage.ts | 9 +++ .../src/layouts/init/registerNotification.ts | 9 +++ chat2db-client/src/models/ai.ts | 8 ++ .../src/pages/main/dashboard/index.less | 2 +- chat2db-client/src/pages/main/index.less | 6 ++ chat2db-client/src/pages/main/index.tsx | 37 +++++---- chat2db-client/src/typings/main.ts | 3 +- chat2db-client/src/utils/index.ts | 76 +++++++++--------- 9 files changed, 144 insertions(+), 84 deletions(-) create mode 100644 chat2db-client/src/layouts/init/registerMessage.ts create mode 100644 chat2db-client/src/layouts/init/registerNotification.ts diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index b7ea168ac..b649f6a20 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -20,6 +20,8 @@ import { InjectThemeVar } from '@/theme'; import styles from './index.less'; import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorage'; import { clearOlderLocalStorage } from '@/utils'; +import registerMessage from './init/registerMessage'; +import registerNotification from './init/registerNotification'; declare global { interface Window { @@ -35,7 +37,13 @@ declare global { const __ENV: string; } -clearOlderLocalStorage(); +const initConfig = () => { + registerMessage(); + registerNotification(); + clearOlderLocalStorage(); +}; + +initConfig(); window._Lang = getLang(); @@ -140,17 +148,20 @@ function AppContainer() { setServiceFail(false); let flag = 0; const time = setInterval(() => { - miscService.testService().then(() => { - clearInterval(time); - // if (__ENV === 'desktop') { - // window.location.href = 'http://127.0.0.1:10824/' - // } - setStartSchedule(2); - flag++; - }).catch(error => { - setStartSchedule(1); - flag++; - }); + miscService + .testService() + .then(() => { + clearInterval(time); + // if (__ENV === 'desktop') { + // window.location.href = 'http://127.0.0.1:10824/' + // } + setStartSchedule(2); + flag++; + }) + .catch((error) => { + setStartSchedule(1); + flag++; + }); if (flag > restartCount) { setServiceFail(true); clearInterval(time); @@ -165,28 +176,33 @@ function AppContainer() { {/* 待启动状态 */} {startSchedule === 0 &&
} {/* 服务启动中 */} - {startSchedule === 1 &&
-
- { - !serviceFail &&
- + {startSchedule === 1 && ( +
+
+ {!serviceFail && ( +
+ +
+ )} +
+
- } -
- + {serviceFail && ( + <> +
+ {i18n('common.text.contactUs')}-github: + + github + +
+
+ {i18n('common.text.tryToRestart')} +
+ + )}
- {serviceFail && ( - <> -
- {i18n('common.text.contactUs')}-github:github -
-
- {i18n('common.text.tryToRestart')} -
- - )}
-
} + )} {/* 服务启动完成 */} {startSchedule === 2 && }
diff --git a/chat2db-client/src/layouts/init/registerMessage.ts b/chat2db-client/src/layouts/init/registerMessage.ts new file mode 100644 index 000000000..d5696ce5a --- /dev/null +++ b/chat2db-client/src/layouts/init/registerMessage.ts @@ -0,0 +1,9 @@ +import { notification } from 'antd'; + +export default () => { + notification.config({ + placement: 'topRight', + maxCount: 2, + duration: 3, + }); +}; diff --git a/chat2db-client/src/layouts/init/registerNotification.ts b/chat2db-client/src/layouts/init/registerNotification.ts new file mode 100644 index 000000000..d5696ce5a --- /dev/null +++ b/chat2db-client/src/layouts/init/registerNotification.ts @@ -0,0 +1,9 @@ +import { notification } from 'antd'; + +export default () => { + notification.config({ + placement: 'topRight', + maxCount: 2, + duration: 3, + }); +}; diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index 85a3258c9..c5fb9db41 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -47,6 +47,14 @@ const AIModel: IAIModelType = { }, effects: { *fetchRemainingUse({ payload }, { put }) { + const { key } = payload; + if (!key) { + yield put({ + type: 'setRemainUse', + payload: undefined, + }); + return; + } const res = (yield aiService.getRemainingUse(payload)) as IRemainingUse; yield put({ type: 'setRemainUse', diff --git a/chat2db-client/src/pages/main/dashboard/index.less b/chat2db-client/src/pages/main/dashboard/index.less index 3696cbdca..4b956cbec 100644 --- a/chat2db-client/src/pages/main/dashboard/index.less +++ b/chat2db-client/src/pages/main/dashboard/index.less @@ -20,7 +20,7 @@ flex-direction: column; height: 100%; background-color: var(--color-bg-elevated); - padding: 10px 20px 0px; + padding: 10px 8px 0px; box-sizing: border-box; min-width: 200px; } diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index a81f57901..ade82da96 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -92,6 +92,12 @@ margin-bottom: 20px; } +.QuestionIcon { + font-size: 20px; + margin-bottom: 8px; + cursor: pointer; +} + .layoutRight { flex: 1; overflow: hidden; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index ced8d0caa..1e81e0930 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -1,6 +1,4 @@ -import React, { useEffect, useState, PropsWithChildren } from 'react'; -import i18n from '@/i18n'; -import { Button, message } from 'antd'; +import React, { useEffect, useState, PropsWithChildren, lazy, Suspense } from 'react'; import { history, connect } from 'umi'; import classnames from 'classnames'; import Setting from '@/blocks/Setting'; @@ -10,43 +8,48 @@ import { IMainPageType } from '@/models/mainPage'; import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; import { findObjListValue } from '@/utils'; +import { INavItem } from '@/typings/main'; import TestVersion from '@/components/TestVersion'; -import DataSource from './connection'; +import Connection from './connection'; import Workspace from './workspace'; import Dashboard from './dashboard'; -import Chat from './chat'; -import sqlService, { MetaSchemaVO } from '@/service/sql'; import styles from './index.less'; -import { INavItem } from '@/typings/main'; + const navConfig: INavItem[] = [ { key: 'workspace', icon: '\ue616', iconFontSize: 16, + isLoad: false, component: , }, { key: 'dashboard', icon: '\ue629', iconFontSize: 24, + isLoad: false, component: , }, { key: 'connections', icon: '\ue622', iconFontSize: 20, - component: , + isLoad: false, + component: , }, { key: 'github', icon: '\ue885', iconFontSize: 26, + isLoad: false, openBrowser: 'https://github.com/chat2db/Chat2DB/', }, ]; const initPageIndex = navConfig.findIndex((t) => `${t.key}` === localStorage.getItem('curPage')); +const activeIndex = initPageIndex > -1 ? initPageIndex : 2; +navConfig[activeIndex].isLoad = true; interface IProps { mainModel: IMainPageType['state']; @@ -59,10 +62,11 @@ function MainPage(props: IProps) { const { mainModel, workspaceModel, connectionModel, dispatch } = props; const { curPage } = mainModel; const { curConnection } = connectionModel; - const [activeNav, setActiveNav] = useState(navConfig[initPageIndex > -1 ? initPageIndex : 2]); + const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); useEffect(() => { // activeNav 发生变化,同步到全局状态管理 + activeNav.isLoad = true; dispatch({ type: 'mainPage/updateCurPage', payload: activeNav.key, @@ -82,7 +86,7 @@ function MainPage(props: IProps) { const newActiveNav = navConfig[findObjListValue(navConfig, 'key', curPage)]; setActiveNav(newActiveNav); } - localStorage.setItem('curPage', curPage) + localStorage.setItem('curPage', curPage); }, [curPage]); useEffect(() => { @@ -110,8 +114,6 @@ function MainPage(props: IProps) { function switchingNav(item: INavItem) { if (item.openBrowser) { window.open(item.openBrowser, '_blank'); - // shell.openExternal(item.openBrowser); - console.log('new-window========>', item.openBrowser); } else { setActiveNav(item); } @@ -120,7 +122,7 @@ function MainPage(props: IProps) { return (
- { }} className={styles.brandLogo} /> + {}} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( @@ -138,6 +140,13 @@ function MainPage(props: IProps) { })}
+ { + window.open('https://github.com/alibaba/ali-dbhub/wiki'); + }} + />
@@ -145,7 +154,7 @@ function MainPage(props: IProps) { {navConfig.map((item) => { return ( ); })} diff --git a/chat2db-client/src/typings/main.ts b/chat2db-client/src/typings/main.ts index 1d64200ac..5f5e1a408 100644 --- a/chat2db-client/src/typings/main.ts +++ b/chat2db-client/src/typings/main.ts @@ -6,4 +6,5 @@ export interface INavItem { component?: React.ReactNode; openBrowser?: string; iconFontSize?: number; -} \ No newline at end of file + isLoad: boolean; +} diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 817b4b578..17ff439e0 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -1,10 +1,9 @@ import { ThemeType } from '@/constants'; import { ITreeNode } from '@/typings'; -import lodash from 'lodash' +import lodash from 'lodash'; export function getOsTheme() { - return window.matchMedia && - window.matchMedia('(prefers-color-scheme: dark)').matches + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? ThemeType.Dark : ThemeType.Light; } @@ -13,9 +12,7 @@ export function deepClone(target: any) { const map = new WeakMap(); function isObject(target: any) { - return ( - (typeof target === 'object' && target) || typeof target === 'function' - ); + return (typeof target === 'object' && target) || typeof target === 'function'; } function clone(data: any) { @@ -75,11 +72,7 @@ export function deepClone(target: any) { } // 模糊匹配树并且高亮 -export function approximateTreeNode( - treeData: ITreeNode[], - target: string, - isDelete = true, -) { +export function approximateTreeNode(treeData: ITreeNode[], target: string, isDelete = true) { if (target) { const newTree: ITreeNode[] = lodash.cloneDeep(treeData || []); newTree.map((item, index) => { @@ -90,10 +83,7 @@ export function approximateTreeNode( if (item.name?.toUpperCase()?.indexOf(target?.toUpperCase()) == -1 && isDelete) { delete newTree[index]; } else { - item.name = item.name?.replace( - target, - `${target}`, - ); + item.name = item.name?.replace(target, `${target}`); } }); return newTree.filter((i) => i); @@ -122,10 +112,7 @@ export function approximateList( delete newData[index]; } else { // @ts-ignore' - item[keyName] = item[keyName]?.replace( - target, - `${target}`, - ); + item[keyName] = item[keyName]?.replace(target, `${target}`); } }); return newData.filter((i) => i); @@ -136,9 +123,7 @@ export function approximateList( // 获取var变量的值 export const callVar = (css: string) => { - return getComputedStyle(document.documentElement) - .getPropertyValue(css) - .trim(); + return getComputedStyle(document.documentElement).getPropertyValue(css).trim(); }; // 给我一个 obj[], 和 obj的 key 和 value,给你返index @@ -147,48 +132,65 @@ export function findObjListValue(list: T[], key: K, value: list.forEach((t: T, index) => { Object.keys(t).forEach((j: K) => { if (j === key && t[j] === value) { - flag = index + flag = index; } - }) - }) - return flag + }); + }); + return flag; } - // 处理console的保存和删除操作 export function handelLocalStorageSavedConsole(id: number, type: 'save' | 'delete', text?: string) { - const saved = localStorage.getItem(`timing-auto-save-console-v1`); - let savedObj: any = {} + let savedObj: any = {}; if (saved) { - savedObj = JSON.parse(saved) + savedObj = JSON.parse(saved); } if (type === 'save') { savedObj[id] = text || ''; } else if (type === 'delete') { - delete savedObj[id] + delete savedObj[id]; } - localStorage.setItem(`timing-auto-save-console-v1`, JSON.stringify(savedObj)) - + localStorage.setItem(`timing-auto-save-console-v1`, JSON.stringify(savedObj)); } // 获取保存的console export function readLocalStorageSavedConsoleText(id: number) { const saved = localStorage.getItem(`timing-auto-save-console-v1`); - let savedObj: any = {} + let savedObj: any = {}; if (saved) { - savedObj = JSON.parse(saved) + savedObj = JSON.parse(saved); } - return savedObj[id] || '' + return savedObj[id] || ''; } // 清理就版本不兼容的LocalStorage export function clearOlderLocalStorage() { if (localStorage.getItem('app-local-storage-versions') !== 'v2') { localStorage.clear(); - localStorage.setItem('app-local-storage-versions', 'v2') + localStorage.setItem('app-local-storage-versions', 'v2'); } } +export function isVersionHigher(version: string, currentVersion: string): boolean { + // 按照 . 分割版本号 + const versionParts = version.split('.'); + const currentVersionParts = currentVersion.split('.'); + + // 按照从左到右的顺序比较每一位的大小 + for (let i = 0; i < versionParts.length; i++) { + const part = parseInt(versionParts[i]); + const currentPart = parseInt(currentVersionParts[i] || '0'); + + if (part > currentPart) { + return true; + } else if (part < currentPart) { + return false; + } + } + + // 如果两个版本号完全相等,则返回false + return false; +} From 336ad421ddab0f71acc4b1742bf997e835b19f1c Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 3 Jul 2023 21:17:02 +0800 Subject: [PATCH 0213/1069] fix getConnection bug --- .../main/java/ai/chat2db/spi/util/JdbcJarUtils.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java index 3d721d955..f28c91f7b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java @@ -11,8 +11,6 @@ import java.util.List; import java.util.concurrent.Executors; - -import ai.chat2db.spi.sql.Chat2DBContext; import okhttp3.Call; import okhttp3.Callback; import okhttp3.Dispatcher; @@ -141,12 +139,8 @@ public static String getFullPath(String jarPath) { return path; } + public static final String DOWNLOAD_URL_HOST = "https://oss-chat2db.alibaba.com/lib/"; private static String getDownloadUrl(String jarPath) { - for (String path : Chat2DBContext.JDBC_JAR_DOWNLOAD_URL_LIST) { - if (path.contains(jarPath)) { - return path.trim(); - } - } - return null; + return DOWNLOAD_URL_HOST+jarPath; } } From e92728cc4b53ad4ed92a0202ab8e96b3e077bd33 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 3 Jul 2023 21:27:22 +0800 Subject: [PATCH 0214/1069] change url --- chat2db-client/src/pages/main/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 1e81e0930..2ab49b478 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -144,7 +144,7 @@ function MainPage(props: IProps) { code="" className={styles.QuestionIcon} onClick={() => { - window.open('https://github.com/alibaba/ali-dbhub/wiki'); + window.open('https://github.com/chat2db/Chat2DB/wiki'); }} /> From bb54f4d6b50958a2b82e5c48d8c22a96242e99d3 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 3 Jul 2023 21:29:36 +0800 Subject: [PATCH 0215/1069] fix getConnection bug --- .../java/ai/chat2db/plugin/redis/builder/DBConfigBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 4ea0e7f5c..e7166fa6b 100644 --- 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 @@ -10,7 +10,7 @@ public static DBConfig buildDBConfig() { dbConfig.setName("Redis"); dbConfig.setDbType("REDIS"); DriverConfig driverConfig = new DriverConfig(); - driverConfig.setJdbcDriver("sredis-jdbc-driver-1.3.jar"); + driverConfig.setJdbcDriver("redis-jdbc-driver-1.3.jar"); driverConfig.setJdbcDriverClass("jdbc.RedisDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/redis-jdbc-driver-1.3.jar")); driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); From 71acf4e4410f3a180f548e4aa5e7e39c9f9c5b51 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 21:32:28 +0800 Subject: [PATCH 0216/1069] When the sql execution is abnormal, the error is reported separately --- .../domain/core/impl/DlTemplateServiceImpl.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 4a0178b04..1e758b1b7 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -11,6 +11,7 @@ import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.parser.ParserException; import com.alibaba.druid.sql.parser.SQLParserUtils; import ai.chat2db.server.domain.api.param.DlCountParam; @@ -67,7 +68,19 @@ public ListResult execute(DlExecuteParam param) { String sqlType = SqlTypeEnum.UNKNOWN.getCode(); // 解析sql分页 - SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + SQLStatement sqlStatement; + try { + sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + } catch (ParserException e) { + log.warn("解析sql失败:{}", sql, e); + ExecuteResult executeResult = ExecuteResult.builder() + .success(Boolean.FALSE) + .sql(sql) + .message(e.getMessage()) + .build(); + result.add(executeResult); + continue; + } // 是否需要代码帮忙分页 boolean autoLimit = false; if (sqlStatement instanceof SQLSelectStatement) { From 37e265f7b1b047f8a68c791f1ed68c64c1e4b68a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 21:45:05 +0800 Subject: [PATCH 0217/1069] Modify remarks --- chat2db-server/.gitignore | 1 - .../server/domain/api/model/Config.java | 7 +---- .../api/param/DatabaseOperationParam.java | 5 +--- .../api/param/SchemaOperationParam.java | 5 +--- .../domain/api/param/SchemaQueryParam.java | 5 +--- .../domain/api/param/SystemConfigParam.java | 5 +--- .../domain/api/service/ConfigService.java | 5 +--- .../core/converter/ConfigConverter.java | 5 +--- .../domain/core/impl/ConfigServiceImpl.java | 5 +--- .../repository/entity/SystemConfigDO.java | 5 +--- .../repository/mapper/SystemConfigMapper.java | 5 +--- .../start/config/config/JarDownloadTask.java | 5 +--- .../data/service/ConfigServiceTest.java | 5 +--- .../dialect/SQLITEDialectProperties.java | 5 +--- .../dialect/SQLServerDialectProperties.java | 5 +--- .../ai/chat2db/server/test/temp/TempTest.java | 28 ------------------- .../web/api/aspect/ConnectionInfoAspect.java | 5 +--- .../web/api/aspect/ConnectionInfoHandler.java | 5 +--- .../web/api/controller/PageController.java | 5 +--- .../ai/azure/client/AzureOpenAIClient.java | 5 +--- .../controller/ai/response/ChatChoice.java | 5 +--- .../ai/response/ChatCompletionResponse.java | 5 +--- .../ai/rest/client/RestAIClient.java | 5 +--- .../controller/config/ConfigController.java | 5 +--- .../config/request/AISystemConfigRequest.java | 5 +--- .../config/request/SystemConfigRequest.java | 5 +--- .../source/converter/SSHWebConverter.java | 5 +--- .../request/DataSourceBaseRequestInfo.java | 5 +--- .../request/DataSourceConsoleRequestInfo.java | 5 +--- .../data/source/request/SSHTestRequest.java | 5 +--- .../rdb/request/SchemaQueryRequest.java | 5 +--- .../rdb/request/TableQueryRequest.java | 5 +--- .../rdb/request/UpdateDatabaseRequest.java | 5 +--- .../rdb/request/UpdateSchemaRequest.java | 5 +--- .../web/api/controller/rdb/vo/SchemaVO.java | 5 +--- .../api/controller/user/UserController.java | 5 +--- .../user/converter/UserWebConverter.java | 5 +--- .../user/request/UserCreateRequest.java | 5 +--- .../user/request/UserQueryRequest.java | 5 +--- .../user/request/UserUpdateRequest.java | 5 +--- .../web/api/controller/user/vo/UserVO.java | 5 +--- .../web/api/util/ApplicationContextUtil.java | 5 +--- .../server/web/api/util/OpenAIClient.java | 5 +--- .../main/java/ai/chat2db/spi/DBManage.java | 5 +--- .../main/java/ai/chat2db/spi/MetaData.java | 5 +--- .../src/main/java/ai/chat2db/spi/Plugin.java | 5 +--- .../java/ai/chat2db/spi/config/DBConfig.java | 5 +--- .../ai/chat2db/spi/config/DriverConfig.java | 5 +--- .../ai/chat2db/spi/jdbc/DefaultDBManage.java | 5 +--- .../chat2db/spi/jdbc/DefaultMetaService.java | 5 +--- .../ai/chat2db/spi/model/CreateTableSql.java | 5 +--- .../ai/chat2db/spi/model/DriverEntry.java | 5 +--- .../java/ai/chat2db/spi/model/Function.java | 5 +--- .../java/ai/chat2db/spi/model/KeyValue.java | 5 +--- .../java/ai/chat2db/spi/model/Procedure.java | 5 +--- .../java/ai/chat2db/spi/model/SSHInfo.java | 5 +--- .../java/ai/chat2db/spi/model/SSLInfo.java | 5 +--- .../java/ai/chat2db/spi/model/Schema.java | 5 +--- .../chat2db/spi/model/ShowDatabaseResult.java | 5 +--- .../java/ai/chat2db/spi/model/Trigger.java | 5 +--- .../ai/chat2db/spi/sql/Chat2DBContext.java | 5 +--- .../java/ai/chat2db/spi/sql/ConnectInfo.java | 5 +--- .../ai/chat2db/spi/sql/IDriverManager.java | 6 +--- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 4 --- .../java/ai/chat2db/spi/sql/SSHManager.java | 5 +--- .../ai/chat2db/spi/util/JdbcJarUtils.java | 5 +--- .../ai/chat2db/spi/util/ResultSetUtils.java | 5 +--- .../java/ai/chat2db/spi/util/SqlUtils.java | 21 +++++++------- chat2db-server/pom.xml | 4 +-- 69 files changed, 77 insertions(+), 304 deletions(-) diff --git a/chat2db-server/.gitignore b/chat2db-server/.gitignore index 48b222b26..d8f57a826 100644 --- a/chat2db-server/.gitignore +++ b/chat2db-server/.gitignore @@ -1,5 +1,4 @@ target/ -!.mvn/com.alibaba.grow.common.wrapper/maven-com.alibaba.grow.common.wrapper.jar rebel.xml rebel-remote.xml .gradle diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Config.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Config.java index 6d4353729..8af6543a5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Config.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Config.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.api.model; import java.io.Serial; @@ -9,8 +6,6 @@ import java.time.LocalDateTime; import lombok.Data; -import lombok.Getter; -import lombok.Setter; /** * @author jipengfei diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseOperationParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseOperationParam.java index a00bcd5bc..5fb094306 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseOperationParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseOperationParam.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.api.param; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaOperationParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaOperationParam.java index 68bda444c..66a57450a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaOperationParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaOperationParam.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.api.param; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java index c61df050f..fae9d8067 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.api.param; import jakarta.validation.constraints.NotNull; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SystemConfigParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SystemConfigParam.java index 6323e79dc..6a18bb948 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SystemConfigParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SystemConfigParam.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.api.param; import java.io.Serial; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java index fab43f606..acab8df0c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ConfigService.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.api.service; import jakarta.validation.constraints.NotNull; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ConfigConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ConfigConverter.java index 26f0a5df8..02c3cbb62 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ConfigConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ConfigConverter.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.core.converter; import ai.chat2db.server.domain.api.model.Config; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java index 616340038..0729756e5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConfigServiceImpl.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.core.impl; import java.time.LocalDateTime; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/SystemConfigDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/SystemConfigDO.java index b0924d08b..f861419a9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/SystemConfigDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/SystemConfigDO.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.repository.entity; import java.io.Serializable; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/SystemConfigMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/SystemConfigMapper.java index ecc8055dd..d53d9c25f 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/SystemConfigMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/SystemConfigMapper.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.SystemConfigDO; diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/JarDownloadTask.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/JarDownloadTask.java index 2dcd250b0..bb32e7e88 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/JarDownloadTask.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/JarDownloadTask.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.start.config.config; import ai.chat2db.spi.sql.Chat2DBContext; diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ConfigServiceTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ConfigServiceTest.java index 1af18db32..5ad725e54 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ConfigServiceTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/ConfigServiceTest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.test.domain.data.service; import ai.chat2db.server.domain.api.param.SystemConfigParam; diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLITEDialectProperties.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLITEDialectProperties.java index 59bf4ece7..56abb6c8b 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLITEDialectProperties.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLITEDialectProperties.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.test.domain.data.service.dialect; import cn.hutool.core.date.DatePattern; diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLServerDialectProperties.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLServerDialectProperties.java index 8fbd2bca6..94bc9ffa6 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLServerDialectProperties.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/domain/data/service/dialect/SQLServerDialectProperties.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.test.domain.data.service.dialect; import cn.hutool.core.date.DatePattern; diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/TempTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/TempTest.java index 0e0928685..4d3dfb48a 100644 --- a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/TempTest.java +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/TempTest.java @@ -1,37 +1,9 @@ package ai.chat2db.server.test.temp; -import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; -import okhttp3.ConnectionSpec; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import org.junit.jupiter.api.Test; - -import javax.net.ssl.SSLContext; -import java.util.Arrays; @Slf4j public class TempTest { - @Test - public void test() throws Exception { -System.setProperty("jdk.tls.disabledAlgorithms","SLv3"); - log.info("pp:{}", JSON.toJSONString(SSLContext.getDefault().getSupportedSSLParameters().getProtocols())); - - OkHttpClient client = new OkHttpClient.Builder() - //添加TLSv1、TLSv1.1、TLSv1.2、TLSv1.3支持 - .connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS)) - .build(); - Request request = new Request.Builder() - .url("https://test-oss-grow2.alibaba.com/latest-mac.yml") - .build(); - - String re = client.newCall(request).execute().body().string(); - - log.info("re:{}", re); -// -// String str = Forest.get("https://test-oss-grow2.alibaba.com/latest-mac.yml").executeAsString(); -// log.info("re:{}", str); - } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoAspect.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoAspect.java index a832bda0d..cb1668c0f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoAspect.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoAspect.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.server.web.api.aspect; import java.lang.annotation.Documented; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index 472538d17..cef6ec81b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.server.web.api.aspect; import ai.chat2db.server.domain.api.model.DataSource; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/PageController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/PageController.java index 80885652a..67ce7a18f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/PageController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/PageController.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller; import lombok.extern.slf4j.Slf4j; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java index 0c841d317..b1dd7031c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/client/AzureOpenAIClient.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.ai.azure.client; import ai.chat2db.server.domain.api.model.Config; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatChoice.java index 90b342e6c..ec4b16b4d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatChoice.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatChoice.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.ai.response; import java.io.Serial; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatCompletionResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatCompletionResponse.java index f2e2e3de1..06cb46330 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatCompletionResponse.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/response/ChatCompletionResponse.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.ai.response; import java.io.Serial; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java index b65d9e483..8140be3ea 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/client/RestAIClient.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.ai.rest.client; import ai.chat2db.server.domain.api.model.Config; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index c4d699c6f..11d10193c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.config; import java.util.Objects; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java index fcc34d886..7b71c33c2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/AISystemConfigRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.config.request; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/SystemConfigRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/SystemConfigRequest.java index 0a4670106..d550b7b2f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/SystemConfigRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/request/SystemConfigRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.config.request; import lombok.Data; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/SSHWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/SSHWebConverter.java index 678cec92b..6d6046818 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/SSHWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/SSHWebConverter.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.data.source.converter; import ai.chat2db.spi.model.SSHInfo; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequestInfo.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequestInfo.java index c6cb93c03..25b68ca85 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequestInfo.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequestInfo.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.data.source.request; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceConsoleRequestInfo.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceConsoleRequestInfo.java index f3205b20f..eee14f92a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceConsoleRequestInfo.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceConsoleRequestInfo.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.data.source.request; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/SSHTestRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/SSHTestRequest.java index 5a1dfeb43..e3003d465 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/SSHTestRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/SSHTestRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.data.source.request; import lombok.Data; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaQueryRequest.java index 11c815192..dbe483593 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaQueryRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableQueryRequest.java index 4cb54afaf..2a1ddf038 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableQueryRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.rdb.request; import java.io.Serial; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateDatabaseRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateDatabaseRequest.java index 9b13c6257..cc908906f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateDatabaseRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateDatabaseRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateSchemaRequest.java index c88fe84c0..dc2f68626 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateSchemaRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/UpdateSchemaRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.rdb.request; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java index c975dcca5..841363514 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/SchemaVO.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.rdb.vo; import lombok.Data; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java index d929df883..8ba60057e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.user; import ai.chat2db.server.domain.api.model.User; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/converter/UserWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/converter/UserWebConverter.java index 48e444a1d..5d3c5b07e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/converter/UserWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/converter/UserWebConverter.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.user.converter; import java.util.List; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserCreateRequest.java index c77a3be15..3f4fc9492 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserCreateRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.user.request; import java.io.Serial; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserQueryRequest.java index b39edb5a8..f27bb8036 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserQueryRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.user.request; import java.io.Serial; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserUpdateRequest.java index d3562d07b..2f779ea92 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/request/UserUpdateRequest.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.user.request; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/vo/UserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/vo/UserVO.java index 5c55bbd02..5950f1cf9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/vo/UserVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/vo/UserVO.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.controller.user.vo; import java.io.Serializable; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/ApplicationContextUtil.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/ApplicationContextUtil.java index 01690995a..ea306df69 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/ApplicationContextUtil.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/ApplicationContextUtil.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.util; import org.springframework.beans.BeansException; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java index 0344e9b0a..3a076074c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.server.web.api.util; import java.net.InetSocketAddress; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java index 3116bb609..5291fb659 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi; import jakarta.validation.constraints.NotEmpty; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 7d8ee0fe6..56c7ed3dd 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi; import java.util.List; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/Plugin.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/Plugin.java index 1b380e2fe..538796f95 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/Plugin.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/Plugin.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi; import ai.chat2db.spi.config.DBConfig; 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 499b17067..aa997c9d8 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,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.config; import lombok.Data; 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 e782ab359..9f17f3ab6 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 @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.config; import java.util.List; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java index d21b64d11..e77fcb762 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.jdbc; import java.sql.SQLException; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index b54fe2288..8813918a5 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.jdbc; import java.lang.reflect.InvocationTargetException; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/CreateTableSql.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/CreateTableSql.java index 1f7d3cafe..4916e7768 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/CreateTableSql.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/CreateTableSql.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.spi.model; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DriverEntry.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DriverEntry.java index d997ce5bd..5c03706f1 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DriverEntry.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DriverEntry.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; import java.sql.Driver; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java index 6e88f1524..eab8fb5f0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; import lombok.AllArgsConstructor; 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 5355a0ae8..907b41f48 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 @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; import java.util.List; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java index 3c0ecce1f..2592e3b89 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java index 9af1c56b7..76654f17a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSHInfo.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; import java.util.Objects; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java index fda32d996..beb6d78f7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; /** diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java index ad61fde57..52e0d1329 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ShowDatabaseResult.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ShowDatabaseResult.java index c9ba6adb5..bbcc7d2e7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ShowDatabaseResult.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ShowDatabaseResult.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.spi.model; import lombok.AllArgsConstructor; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java index b3fb03967..5df92a714 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.model; /** 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 a12a60206..2f9740ae9 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 @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ + package ai.chat2db.spi.sql; import java.sql.Connection; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java index 66de3a0f4..182d631a0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.sql; import java.sql.Connection; 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 e627cc7a8..28ef96048 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,9 +1,5 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ -package ai.chat2db.spi.sql; +package ai.chat2db.spi.sql; import java.io.File; import java.net.MalformedURLException; import java.net.URL; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 030e13d68..4321dc564 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -1,7 +1,3 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2022 All Rights Reserved. - */ package ai.chat2db.spi.sql; import java.sql.Connection; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java index 798ae16a6..38d638047 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.sql; import java.util.concurrent.ConcurrentHashMap; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java index f28c91f7b..9b0e7d6dd 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.util; import java.io.File; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java index 941da4a7b..4053b1abd 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.util; import java.sql.ResultSet; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index a6d3fc632..2d39bbfb0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -1,7 +1,4 @@ -/** - * alibaba.com Inc. - * Copyright (c) 2004-2023 All Rights Reserved. - */ + package ai.chat2db.spi.util; import java.util.ArrayList; @@ -11,12 +8,6 @@ import java.util.Set; import java.util.stream.Collectors; -import ai.chat2db.server.tools.common.util.EasyBooleanUtils; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import ai.chat2db.spi.enums.CollationEnum; -import ai.chat2db.spi.enums.IndexTypeEnum; -import ai.chat2db.spi.model.*; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; import com.alibaba.druid.sql.ast.expr.SQLCharExpr; @@ -45,6 +36,16 @@ import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex; +import ai.chat2db.server.tools.common.util.EasyBooleanUtils; +import ai.chat2db.server.tools.common.util.EasyCollectionUtils; +import ai.chat2db.server.tools.common.util.EasyEnumUtils; +import ai.chat2db.spi.enums.CollationEnum; +import ai.chat2db.spi.enums.IndexTypeEnum; +import ai.chat2db.spi.model.Sql; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 094b8ad9d..34be59153 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -326,10 +326,10 @@ 3.1.2 - /com/alibaba/dbhub/server/test/**/*.java + /ai/chat2db/server/test/**/*.java - /com/alibaba/dbhub/server/test/temp/**/*.java + /ai/chat2db/server/test/temp/**/*.java true From 2e1eb8bf095abe389150a414ba278e0b65d348f1 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 3 Jul 2023 21:33:58 +0800 Subject: [PATCH 0218/1069] fix: improve code --- chat2db-client/src/layouts/init/registerMessage.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/layouts/init/registerMessage.ts b/chat2db-client/src/layouts/init/registerMessage.ts index d5696ce5a..e3b64f1c0 100644 --- a/chat2db-client/src/layouts/init/registerMessage.ts +++ b/chat2db-client/src/layouts/init/registerMessage.ts @@ -1,9 +1,8 @@ -import { notification } from 'antd'; +import { message } from 'antd'; export default () => { - notification.config({ - placement: 'topRight', - maxCount: 2, + message.config({ + maxCount: 1, duration: 3, }); }; From efb8863398c033144ad211c3aea068d5e94d6b84 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 3 Jul 2023 21:45:20 +0800 Subject: [PATCH 0219/1069] fix: modfiy text --- chat2db-client/src/main/menu.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index 6f4a7b8a3..c4812c2f7 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -14,7 +14,8 @@ const registerAppMenu = () => { title: '关于Chat2DB', message: `关于Chat2DB v${app.getVersion()}`, detail: - '一款由阿里巴巴开源免费的多数据库客户端工具,支持windows、mac本地安装,也支持服务器端部署,web网页访问。', + // An intelligent database client and smart BI reporting tool with integrated AI capabilities. + '一个集成AI能力的智能数据库客户端和智能BI报表工具。', icon: './logo/icon.png', }); }, From f4b229d71b221bac12700955ed08b71117432d36 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 3 Jul 2023 21:46:29 +0800 Subject: [PATCH 0220/1069] fix:some bug --- .../src/blocks/Setting/About/index.tsx | 2 +- chat2db-client/src/blocks/Setting/index.tsx | 3 ++ .../src/components/SearchResult/index.less | 8 +++++ .../src/components/SearchResult/index.tsx | 2 +- .../src/components/TestVersion/index.tsx | 7 +++-- chat2db-client/src/models/ai.ts | 25 +++++++++------- chat2db-client/src/pages/main/index.less | 5 +++- chat2db-client/src/pages/main/index.tsx | 30 ++----------------- .../components/WorkspaceLeft/index.tsx | 14 +++++++++ .../src/pages/main/workspace/index.tsx | 1 + 10 files changed, 54 insertions(+), 43 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index 8cf4ddf20..ea040413c 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -15,7 +15,7 @@ export default function AboutUs() { {i18n('setting.text.currentEnv')}:{__ENV}
- {i18n('setting.text.currentVersion')}:v{'2.0.0' || __APP_VERSION__} build + {i18n('setting.text.currentVersion')}:v{'2.0.1' || __APP_VERSION__} build {__BUILD_TIME__}
diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 1417d2e5f..e94cc2d83 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -12,6 +12,8 @@ import { IAIState } from '@/models/ai'; import styles from './index.less'; import configService, { IChatGPTConfig } from '@/service/config'; import { AiSqlSourceType } from '@/typings/ai'; +import TestVersion from '@/components/TestVersion'; + interface IProps { className?: string; text?: string; @@ -112,6 +114,7 @@ function Setting(props: IProps) { )}
+ (function SearchResult({ className, manageResultDataL ); } else { return - + ; } }); diff --git a/chat2db-client/src/components/TestVersion/index.tsx b/chat2db-client/src/components/TestVersion/index.tsx index 89871d85d..fc52aa84a 100644 --- a/chat2db-client/src/components/TestVersion/index.tsx +++ b/chat2db-client/src/components/TestVersion/index.tsx @@ -4,6 +4,7 @@ import classnames from 'classnames'; import { notification, Space, Button } from 'antd'; import outSideService from '@/service/outside'; import i18n from '@/i18n'; +import { isVersionHigher } from '@/utils'; interface IProps { className?: string; @@ -26,9 +27,9 @@ export default memo(function TestVersion(props) { if (time < nowTime) { outSideService.checkVersion().then(res => { localStorage.setItem('app-gateway-params', JSON.stringify(res)) - openNotification(res); const time = new Date().getTime() + 2 * 60 * 60 * 1000; localStorage.setItem('update-hint-time', time.toString()) + openNotification(res); }) } } @@ -43,7 +44,6 @@ export default memo(function TestVersion(props) { localStorage.setItem('update-hint-time', time.toString()); } - function go(responseText: any) { window.open(responseText.downloadLink) notificationApi.destroy(); @@ -51,7 +51,8 @@ export default memo(function TestVersion(props) { const openNotification = (responseText: any) => { try { - if (responseText.version !== '2.0.0') { + const needToBeUpdated = isVersionHigher(responseText.version, '2.0.1'); + if (needToBeUpdated) { const key = `open${Date.now()}`; const btn = ( diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index c5fb9db41..7e23f0e80 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -46,20 +46,25 @@ const AIModel: IAIModelType = { }, }, effects: { - *fetchRemainingUse({ payload }, { put }) { - const { key } = payload; - if (!key) { + * fetchRemainingUse({ payload }, { put }) { + try { + const { key } = payload; + if (!key) { + yield put({ + type: 'setRemainUse', + payload: undefined, + }); + return; + } + const res = (yield aiService.getRemainingUse(payload)) as IRemainingUse; yield put({ type: 'setRemainUse', - payload: undefined, + payload: res, }); - return; } - const res = (yield aiService.getRemainingUse(payload)) as IRemainingUse; - yield put({ - type: 'setRemainUse', - payload: res, - }); + catch { + + } }, }, }; diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index ade82da96..24ffc70fd 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -92,10 +92,13 @@ margin-bottom: 20px; } -.QuestionIcon { +.questionIcon { font-size: 20px; margin-bottom: 8px; cursor: pointer; + &:hover{ + color: var(--color-primary); + } } .layoutRight { diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 2ab49b478..47e1c564d 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -9,7 +9,6 @@ import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; import { findObjListValue } from '@/utils'; import { INavItem } from '@/typings/main'; -import TestVersion from '@/components/TestVersion'; import Connection from './connection'; import Workspace from './workspace'; import Dashboard from './dashboard'; @@ -89,28 +88,6 @@ function MainPage(props: IProps) { localStorage.setItem('curPage', curPage); }, [curPage]); - useEffect(() => { - if (curConnection?.id) { - // sqlService.getDatabaseSchemaList({ dataSourceId: curConnection.id }).then(res => [ - // dispatch({ - // type: 'workspace/setDatabaseAndSchema', - // payload: res, - // }) - // ]).catch(() => { - // dispatch({ - // type: 'workspace/setDatabaseAndSchema', - // payload: {}, - // }) - // }) - dispatch({ - type: 'workspace/fetchDatabaseAndSchema', - payload: { - dataSourceId: curConnection.id, - }, - }); - } - }, [curConnection]); - function switchingNav(item: INavItem) { if (item.openBrowser) { window.open(item.openBrowser, '_blank'); @@ -122,7 +99,7 @@ function MainPage(props: IProps) { return (
- {}} className={styles.brandLogo} /> + { }} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( @@ -142,9 +119,9 @@ function MainPage(props: IProps) {
    { - window.open('https://github.com/chat2db/Chat2DB/wiki'); + window.open('https://github.com/chat2db/chat2db/wiki'); }} /> @@ -159,7 +136,6 @@ function MainPage(props: IProps) { ); })}
    -
); } 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 435693018..bb92c1c74 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -140,7 +140,21 @@ const RenderSelectDatabase = dvaModel(function (props: IProps) { const { curConnection } = connectionModel; const [currentSelectedName, setCurrentSelectedName] = useState(''); + useEffect(() => { + if (curConnection?.id) { + dispatch({ + type: 'workspace/fetchDatabaseAndSchema', + payload: { + dataSourceId: curConnection.id, + }, + }); + } + }, [curConnection]); + const cascaderOptions = useMemo(() => { + if (!databaseAndSchema) { + return + } const res = handleDatabaseAndSchema(databaseAndSchema); if (!curWorkspaceParams?.dataSourceId || curWorkspaceParams?.dataSourceId !== curConnection?.id) { // 如果databaseAndSchema 发生切变 并且没选中确切的database时,需要默认选中第一个 diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 90eb50608..f94064a97 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -12,6 +12,7 @@ export default memo(function workspace(props) { const draggableRef = useRef(); return ( +
From 2cca776f5c4e1466a1005e06f998a2fe53e6c207 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 21:46:31 +0800 Subject: [PATCH 0221/1069] changelog --- CHANGELOG.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fe5329549..8f6d9c92b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,23 @@ +# 2.0.1 +## What's Changed +* 🔥An intelligent solution that perfectly integrates SQL queries, AI assistant, and data analysis. +## 更新内容 +* 🔥SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 + + # 2.0.0 -* 🎁 SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 -* 🎁 数据源连接和管理进阶为专注模式的全新体验设计与实现 -* 🎁 AI对话SQL升级为极简模式的全新交互设计与实现 -* 🎁 客户端AI体验重大升级,响应更多用户的诉求 -* 🎁 集成更多AI模型 -* 🎁 客户端双语支持 +## What's Changed +* 🔥An intelligent solution that perfectly integrates SQL queries, AI assistant, and data analysis. +* 🔥New focused mode experience for advanced datasource management. +* AI integration of more LLM. +* Bilingual in Chinese and English support for client. +## 更新内容 +* 🔥SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 +* 🔥数据源连接和管理进阶为专注模式的全新体验设计与实现 +* 🔥AI对话SQL升级为极简模式的全新交互设计与实现 +* 客户端AI体验重大升级,响应更多用户的诉求 +* 集成更多AI模型 +* 客户端双语支持 * SQL查询基础功能优化 * Issue问题修复 From 40e83652a23af9eaa05d6f4807515d87b125761f Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 21:50:24 +0800 Subject: [PATCH 0222/1069] changelog --- CHANGELOG.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f6d9c92b..78d6d39db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # 2.0.1 -## What's Changed -* 🔥An intelligent solution that perfectly integrates SQL queries, AI assistant, and data analysis. -## 更新内容 -* 🔥SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 - +## ⭐ New Features +* dd +## 🐞 Bug Fixes +* xx +## ⭐ 新特性 +* dd +## 🐞 问题修复 +* xx # 2.0.0 ## What's Changed From bd29f05eb361a8d016a822263d892385e94a1110 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 21:51:22 +0800 Subject: [PATCH 0223/1069] changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78d6d39db..7f62b0ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ ## ⭐ 新特性 * dd ## 🐞 问题修复 -* xx +* 修复一次性执行多条SQL会提示异常的BUG # 2.0.0 ## What's Changed From aa9ec1cb93adb0757404043fb7c2ca43a3d027ca Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 21:52:26 +0800 Subject: [PATCH 0224/1069] changelog --- CHANGELOG.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f62b0ed2..1d5f3086a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,11 +25,11 @@ * Issue问题修复 # 1.0.11 -* fixed: SQL有特殊字符时AI功能无法正常使用 [Issue #291](https://github.com/alibaba/Chat2DB/issues/291) +* fixed: SQL有特殊字符时AI功能无法正常使用 * 增减版本信息检测 # 1.0.10 -* fixed: 格式化SQL异常[Issue #266](https://github.com/alibaba/Chat2DB/issues/266) +* fixed: 格式化SQL异常 * 优化AI网络连接异常提示 * 自定义AI添加本地样例 * Support OceanBase Presto DB2 Redis MongoDB Hive KingBase @@ -37,24 +37,24 @@ # 1.0.9 * 修复Open Ai 无法连接的问题 -* 支持国产达梦数据库 [Issue #148](https://github.com/alibaba/Chat2DB/issues/147) -* 支持自定义OPEN AI API_HOST [Issue #173](https://github.com/alibaba/Chat2DB/issues/173) +* 支持国产达梦数据库 +* 支持自定义OPEN AI API_HOST * 🔥 支持自定义AI接口 * 支持主题色跟随系统 # 1.0.6 -* 修复Oracle数据库字符集问题 [Issue #205](https://github.com/alibaba/Chat2DB/issues/205) [Issue #181](https://github.com/alibaba/Chat2DB/issues/181) [Issue #182](https://github.com/alibaba/Chat2DB/issues/182) +* 修复Oracle数据库字符集问题 * 修复mac安装提示的安全问题 # 1.0.5 * 🔥 优化Apple芯片的启动速度 -* 修复Windows端数据库连接问题 [Issue #150](https://github.com/alibaba/Chat2DB/issues/150) [Issue #147](https://github.com/alibaba/Chat2DB/issues/147) -* 修改database不生效 [Issue #141](https://github.com/alibaba/Chat2DB/issues/141) -* NullPointerException [Issue #136](https://github.com/alibaba/Chat2DB/issues/136) +* 修复Windows端数据库连接问题 +* 修改database不生效 +* NullPointerException # 1.0.4 -* 修复ClickHouse jdbc问题 [Issue #135](https://github.com/alibaba/Chat2DB/issues/135) -* 修复连接池管理的NPE [Issue #136](https://github.com/alibaba/Chat2DB/issues/136) +* 修复ClickHouse jdbc问题 +* 修复连接池管理的NPE * 修复前端编辑数据源报错问题 * 增加数据库默认属性配置 @@ -62,8 +62,8 @@ * 🔥 支持SSH连接数据库 * 🎉 支持客户端查看日志 * 🎉 支持在Console中聊天对话 -* 支持在客户端内设置OPENAI代理 [Issue #84](https://github.com/alibaba/Chat2DB/issues/84) -* 已经启动过应用不会再重复启动 [Issue #96](https://github.com/alibaba/Chat2DB/issues/96) +* 支持在客户端内设置OPENAI代理 +* 已经启动过应用不会再重复启动 # 1.0.1 * 修复oracle连接配置编辑、以及连接查询问题 From 70c0a77607d93c426349710dead7ab9e20f9714a Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 3 Jul 2023 21:56:54 +0800 Subject: [PATCH 0225/1069] fix getConnection bug --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5f3086a..d4972085a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,14 @@ ## ⭐ New Features * dd ## 🐞 Bug Fixes -* xx +* Fix getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) +* ## ⭐ 新特性 * dd ## 🐞 问题修复 * 修复一次性执行多条SQL会提示异常的BUG +* 修复 getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) + # 2.0.0 ## What's Changed From e3b3062877259b6620e79ff59e4da2bf1bbb9eaa Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 3 Jul 2023 22:15:36 +0800 Subject: [PATCH 0226/1069] feat: improve style --- chat2db-client/src/blocks/Setting/index.less | 4 ++++ chat2db-client/src/pages/main/index.less | 4 +++- chat2db-client/src/pages/main/index.tsx | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index 2d538b0bc..338904b86 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -6,6 +6,10 @@ .settingIcon { color: var(--custom-color-icon); + &:hover { + background-color: var(--color-primary-bg-hover); + color: var(--color-primary); + } } .setText { diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 24ffc70fd..24286c770 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -96,7 +96,9 @@ font-size: 20px; margin-bottom: 8px; cursor: pointer; - &:hover{ + color: var(--custom-color-icon); + &:hover { + background-color: var(--color-primary-bg-hover); color: var(--color-primary); } } diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 47e1c564d..63e5c8347 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -99,7 +99,7 @@ function MainPage(props: IProps) { return (
- { }} className={styles.brandLogo} /> + {}} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( From 66aadf212a0f8c9a080dce3680025508e0f4462e Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 3 Jul 2023 22:17:42 +0800 Subject: [PATCH 0227/1069] fix getColumn bug --- .../ai/chat2db/spi/util/ResultSetUtils.java | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java index 4053b1abd..b11b10dfe 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java @@ -16,12 +16,12 @@ public static ai.chat2db.spi.model.Function buildFunction(ResultSet resultSet) { ai.chat2db.spi.model.Function function = new ai.chat2db.spi.model.Function(); try { - function.setDatabaseName(resultSet.getString("FUNCTION_CAT")); - function.setSchemaName(resultSet.getString("FUNCTION_SCHEM")); - function.setFunctionName(resultSet.getString("FUNCTION_NAME")); - function.setRemarks(resultSet.getString("REMARKS")); + function.setDatabaseName(getString(resultSet,"FUNCTION_CAT")); + function.setSchemaName(getString(resultSet,"FUNCTION_SCHEM")); + function.setFunctionName(getString(resultSet,"FUNCTION_NAME")); + function.setRemarks(getString(resultSet,"REMARKS")); function.setFunctionType(resultSet.getShort("FUNCTION_TYPE")); - function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + function.setSpecificName(getString(resultSet,"SPECIFIC_NAME")); } catch (SQLException e) { throw new RuntimeException(e); } @@ -31,12 +31,12 @@ public static ai.chat2db.spi.model.Function buildFunction(ResultSet resultSet) { public static Procedure buildProcedure(ResultSet resultSet) { Procedure procedure = new Procedure(); try { - procedure.setDatabaseName(resultSet.getString("PROCEDURE_CAT")); - procedure.setSchemaName(resultSet.getString("PROCEDURE_SCHEM")); - procedure.setProcedureName(resultSet.getString("PROCEDURE_NAME")); - procedure.setRemarks(resultSet.getString("REMARKS")); + procedure.setDatabaseName(getString(resultSet,"PROCEDURE_CAT")); + procedure.setSchemaName(getString(resultSet,"PROCEDURE_SCHEM")); + procedure.setProcedureName(getString(resultSet,"PROCEDURE_NAME")); + procedure.setRemarks(getString(resultSet,"REMARKS")); procedure.setProcedureType(resultSet.getShort("PROCEDURE_TYPE")); - procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + procedure.setSpecificName(getString(resultSet,"SPECIFIC_NAME")); } catch (SQLException e) { throw new RuntimeException(e); } @@ -45,37 +45,37 @@ public static Procedure buildProcedure(ResultSet resultSet) { public static TableIndexColumn buildTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); - tableIndexColumn.setColumnName(resultSet.getString("COLUMN_NAME")); - tableIndexColumn.setIndexName(resultSet.getString("INDEX_NAME")); - tableIndexColumn.setAscOrDesc(resultSet.getString("ASC_OR_DESC")); + tableIndexColumn.setColumnName(getString(resultSet,"COLUMN_NAME")); + tableIndexColumn.setIndexName(getString(resultSet,"INDEX_NAME")); + tableIndexColumn.setAscOrDesc(getString(resultSet,"ASC_OR_DESC")); tableIndexColumn.setCardinality(resultSet.getLong("CARDINALITY")); tableIndexColumn.setPages(resultSet.getLong("PAGES")); - tableIndexColumn.setFilterCondition(resultSet.getString("FILTER_CONDITION")); - tableIndexColumn.setIndexQualifier(resultSet.getString("INDEX_QUALIFIER")); + tableIndexColumn.setFilterCondition(getString(resultSet,"FILTER_CONDITION")); + tableIndexColumn.setIndexQualifier(getString(resultSet,"INDEX_QUALIFIER")); // tableIndexColumn.setIndexType(resultSet.getShort("TYPE")); tableIndexColumn.setNonUnique(resultSet.getBoolean("NON_UNIQUE")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("ORDINAL_POSITION")); - tableIndexColumn.setDatabaseName(resultSet.getString("TABLE_CAT")); - tableIndexColumn.setSchemaName(resultSet.getString("TABLE_SCHEM")); - tableIndexColumn.setTableName(resultSet.getString("TABLE_NAME")); + tableIndexColumn.setDatabaseName(getString(resultSet,"TABLE_CAT")); + tableIndexColumn.setSchemaName(getString(resultSet,"TABLE_SCHEM")); + tableIndexColumn.setTableName(getString(resultSet,"TABLE_NAME")); return tableIndexColumn; } public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { TableColumn tableColumn = new TableColumn(); - tableColumn.setDatabaseName(resultSet.getString("TABLE_CAT")); - tableColumn.setSchemaName(resultSet.getString("TABLE_SCHEM")); - tableColumn.setTableName(resultSet.getString("TABLE_NAME")); - tableColumn.setName(resultSet.getString("COLUMN_NAME")); - tableColumn.setComment(resultSet.getString("REMARKS")); - tableColumn.setDefaultValue(resultSet.getString("COLUMN_DEF")); - tableColumn.setTypeName(resultSet.getString("TYPE_NAME")); + tableColumn.setDatabaseName(getString(resultSet,"TABLE_CAT")); + tableColumn.setSchemaName(getString(resultSet,"TABLE_SCHEM")); + tableColumn.setTableName(getString(resultSet,"TABLE_NAME")); + tableColumn.setName(getString(resultSet,"COLUMN_NAME")); + tableColumn.setComment(getString(resultSet,"REMARKS")); + tableColumn.setDefaultValue(getString(resultSet,"COLUMN_DEF")); + tableColumn.setTypeName(getString(resultSet,"TYPE_NAME")); tableColumn.setColumnSize(resultSet.getInt("COLUMN_SIZE")); tableColumn.setDataType(resultSet.getInt("DATA_TYPE")); tableColumn.setNullable(resultSet.getInt("NULLABLE") == 1); tableColumn.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); - tableColumn.setAutoIncrement("YES".equals(resultSet.getString("IS_AUTOINCREMENT"))); - //tableColumn.setGeneratedColumn("YES".equals(resultSet.getString("IS_GENERATEDCOLUMN"))); + //tableColumn.setAutoIncrement("YES".equals(getString,resultSet,"IS_AUTOINCREMENT"))); + //tableColumn.setGeneratedColumn("YES".equals(getString,resultSet,"IS_GENERATEDCOLUMN"))); tableColumn.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); tableColumn.setDecimalDigits(resultSet.getInt("DECIMAL_DIGITS")); tableColumn.setNumPrecRadix(resultSet.getInt("NUM_PREC_RADIX")); @@ -85,11 +85,22 @@ public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { public static Table buildTable(ResultSet resultSet) throws SQLException { Table table = new Table(); - table.setName(resultSet.getString("TABLE_NAME")); - table.setComment(resultSet.getString("REMARKS")); - table.setDatabaseName(resultSet.getString("TABLE_CAT")); - table.setSchemaName(resultSet.getString("TABLE_SCHEM")); - table.setType(resultSet.getString("TABLE_TYPE")); + table.setName(getString(resultSet,"TABLE_NAME")); + table.setComment(getString(resultSet,"REMARKS")); + table.setDatabaseName(getString(resultSet,"TABLE_CAT")); + table.setSchemaName(getString(resultSet,"TABLE_SCHEM")); + table.setType(getString(resultSet,"TABLE_TYPE")); return table; } + + private static String getString(ResultSet resultSet,String name){ + if(resultSet == null){ + return null; + } + try { + return resultSet.getString(name); + }catch (Exception e){ + return null; + } + } } \ No newline at end of file From 4639597a2a7cd1b3539c2226d5ba8d145a71fc29 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 3 Jul 2023 22:21:32 +0800 Subject: [PATCH 0228/1069] fix getColumn bug --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4972085a..ead99359e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,14 @@ * dd ## 🐞 Bug Fixes * Fix getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) -* +* Fixing the Hive connection and then viewing columns results in an error. [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) + ## ⭐ 新特性 * dd ## 🐞 问题修复 * 修复一次性执行多条SQL会提示异常的BUG * 修复 getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) - +* 修复hive方式连接,然后查看columns报错 [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) # 2.0.0 ## What's Changed From ca4378f1e037dfd0d810445cf417bfcb4e899759 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 22:32:44 +0800 Subject: [PATCH 0229/1069] changelog --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ead99359e..0404bed89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,9 @@ # 2.0.1 -## ⭐ New Features -* dd ## 🐞 Bug Fixes +* Fix bug where executing multiple SQL statements at once will prompt for exceptions * Fix getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) * Fixing the Hive connection and then viewing columns results in an error. [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) -## ⭐ 新特性 -* dd ## 🐞 问题修复 * 修复一次性执行多条SQL会提示异常的BUG * 修复 getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) From 873da605b1815ecba4695d0f23a97b89ccba063e Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Mon, 3 Jul 2023 22:41:22 +0800 Subject: [PATCH 0230/1069] release 2.0.1 --- README.md | 8 ++++---- README_CN.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 8b01c804d..8420d0b10 100644 --- a/README.md +++ b/README.md @@ -48,10 +48,10 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 | Description | Download | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe) | -| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg) | -| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar) | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | +| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | +| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | +| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | ## 🚀 Supported databases | Databases | Status | diff --git a/README_CN.md b/README_CN.md index cef529360..a967ee365 100644 --- a/README_CN.md +++ b/README_CN.md @@ -50,10 +50,10 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 ## ⏬ 下载安装 | 描述 | 下载地址 | |-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB%20Setup%202.0.0.exe) | -| MacOS ARM64 (Apple芯片) | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0-arm64.dmg) | -| MacOS X64 (Intel芯片) | [https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg](https://oss-chat2db.alibaba.com/release/2.0.0/Chat2DB-2.0.0.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.0/chat2db-server-start.jar) | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | +| MacOS ARM64 (Apple芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | +| MacOS X64 (Intel芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | +| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | ## 🚀 支持的数据库 | 数据库 | 支持计划 | From 55e5e05ee96581739ffa8068dcba0584e9b29c98 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 3 Jul 2023 22:41:28 +0800 Subject: [PATCH 0231/1069] fix getColumn bug --- .../java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java index fe21e6edf..bbaad43ff 100644 --- a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java @@ -23,7 +23,7 @@ public static DBConfig buildDBConfig() { driverConfig2.setJdbcDriver("mysql-connector-java-5.1.47.jar"); driverConfig2.setJdbcDriverClass("com.mysql.jdbc.Driver"); driverConfig2.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/mysql-connector-java-5.1.47.jar")); - driverConfig2.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); + driverConfig2.setName(driverConfig2.getJdbcDriver() + ":" + driverConfig2.getJdbcDriverClass()); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig,driverConfig2)); return dbConfig; From e0c189e36789627b8734ff605048cdf8dc15a631 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 5 Jul 2023 21:38:32 +0800 Subject: [PATCH 0232/1069] =?UTF-8?q?feat:=20=E8=87=AA=E5=8A=A8=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E7=89=88=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 4 +- .github/workflows/release_test.yml | 4 +- .vscode/settings.json | 14 ++--- chat2db-client/.umirc.ts | 10 +++- chat2db-client/package.json | 4 +- .../src/blocks/Setting/AiSetting/index.tsx | 2 +- .../src/pages/main/workspace/context.ts | 53 ------------------- .../src/pages/main/workspace/index.tsx | 25 ++++++++- 8 files changed, 46 insertions(+), 70 deletions(-) delete mode 100644 chat2db-client/src/pages/main/workspace/context.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2c6c04a7a..840a8727b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,7 +120,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --appVersion=${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -135,7 +135,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop --appVersion=${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:desktop --app_version=${{ steps.chat2db_version.outputs.substring }} # windows - name: Build/release Electron app for Windows diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 4a661a62a..2b59a9a49 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -106,7 +106,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --appVersion=1.0.${{ github.run_id }} --appPort=10822 + yarn run build:web:prod --app_version=1.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -121,7 +121,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop --appVersion=1.0.${{ github.run_id }} --appPort=10822 + yarn run build:web:desktop --app_version=1.0.${{ github.run_id }} --app_port=10822 # windows - name: Build/release Electron app for Windows diff --git a/.vscode/settings.json b/.vscode/settings.json index 8e235023f..7de422d83 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,26 +1,28 @@ { "cSpell.words": [ + "AZUREAI", + "Dserver", + "Dspring", + "Mddhhmmss", + "OPENAI", + "RESTAI", + "Sercurity", "ahooks", "antd", "asar", - "AZUREAI", "cascader", "datasource", - "Dserver", - "Dspring", "echart", "echarts", "favicons", "iconfont", "monaco", "nsis", - "OPENAI", "pgsql", "remaininguses", - "RESTAI", - "Sercurity", "sortablejs", "togglefullscreen", + "umijs", "wechat", "wireframe" ] diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 1cd98fb6c..0fd1c5b0f 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -41,11 +41,17 @@ export default defineConfig({ changeOrigin: true, }, }, - headScripts: ['if (window.myAPI) { window.myAPI.startServerForSpawn() }'], + headScripts: [ + `if (localStorage.getItem('app-local-storage-versions') !== 'v2') { + localStorage.clear(); + localStorage.setItem('app-local-storage-versions', 'v2'); + }`, + `if (window.myAPI) { window.myAPI.startServerForSpawn() }` + ], favicons: ['logo.ico'], define: { __ENV: process.env.UMI_ENV, __BUILD_TIME__: formatDate(new Date(), 'yyyyMMddhhmmss'), - __APP_VERSION__: process.env.APP_VERSION || '0.0.0', + __APP_VERSION__: process.env.npm_config_app_version || '0.0.0', }, }); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 29b71462c..90c001a41 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -15,8 +15,8 @@ "build:main:prod": "cross-env NODE_ENV=production electron-builder", "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:web": "umi build", - "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", - "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_appVersion} cross-env APP_PORT=${npm_config_appPort} umi build", + "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", + "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", "postinstall": "umi setup", "lint": "umi lint", "start": "concurrently \"npm run start:web\" \"npm run start:main\"", diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index d7021c6b9..8fb65f004 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -111,7 +111,7 @@ export default function SettingAI(props: IProps) { }} />
-
HTTP Proxy Prot
+
HTTP Proxy Port
{ - const { type, payload } = action; - - switch (type) { - case workspaceActionType.CURRENT_WORKSPACE_DATA: - return changeCurrentWorkspaceData(preState, payload); - case workspaceActionType.DBLCLICK_TREE_NODE: - return { - ...preState, - dblclickTreeNodeData: payload - } - } -} - -function changeCurrentWorkspaceData(preState: IState, payload: any) { - setCurrentWorkspaceDatabase(payload); - return { - ...preState, - currentWorkspaceData: payload, - } -} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index f94064a97..3b164bf92 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,18 +1,36 @@ import React, { memo, useEffect, useRef, useState, useReducer, useContext } from 'react'; +import { connect } from 'umi'; +import { Spin } from 'antd' import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; +import { IConnectionModelType } from '@/models/connection'; +import { IWorkspaceModelType } from '@/models/workspace'; +import LoadingContent from '@/components/Loading/LoadingContent' interface IProps { className?: string; + workspaceModel: IWorkspaceModelType['state'] + connectionModel: IConnectionModelType['state'] } -export default memo(function workspace(props) { +const dvaModel = connect( + ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ + connectionModel: connection, + workspaceModel: workspace, + }), +); + + +const workspace = memo((props) =>{ const draggableRef = useRef(); + const {workspaceModel,connectionModel} = props; + const { curWorkspaceParams } = workspaceModel; + const { curConnection } = connectionModel; return ( - + //
@@ -21,5 +39,8 @@ export default memo(function workspace(props) {
+ //
); }); + +export default dvaModel(workspace) \ No newline at end of file From 515b2ee75548bd40143dc9506d5a55270e473b85 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 5 Jul 2023 21:41:35 +0800 Subject: [PATCH 0233/1069] azure ai bug fix --- .../server/web/api/controller/ai/ChatController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index b0d910a11..6e056e958 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -357,13 +357,13 @@ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sse prompt.length() / TOKEN_CONVERT_CHAR_LENGTH); throw new ParamBusinessException(); } - String messageContext = (String)LocalCache.CACHE.get(uid); - List messages = new ArrayList<>(); - if (StrUtil.isNotBlank(messageContext)) { - messages = JSONUtil.toList(messageContext, ChatMessage.class); + List messages = (List)LocalCache.CACHE.get(uid); + if (CollectionUtils.isNotEmpty(messages)) { if (messages.size() >= contextLength) { messages = messages.subList(1, contextLength); } + } else { + messages = Lists.newArrayList(); } ChatMessage currentMessage = new ChatMessage(ChatRole.USER).setContent(prompt); messages.add(currentMessage); @@ -387,7 +387,7 @@ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sse ); AzureOpenAIEventSourceListener sourceListener = new AzureOpenAIEventSourceListener(sseEmitter); AzureOpenAIClient.getInstance().streamCompletions(messages, sourceListener); - LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT); + LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); return sseEmitter; } From 77ea09c939b9d7ca4ddff8b7d318cc3b6287d31a Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 5 Jul 2023 21:51:54 +0800 Subject: [PATCH 0234/1069] azure ai bug fix --- .../ai/azure/client/AzureOpenAiStreamClient.java | 2 +- .../ai/listener/AzureOpenAIEventSourceListener.java | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) 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 426582d42..44805f099 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 @@ -84,7 +84,7 @@ public void streamCompletions(List chatMessages, EventSourceListene text = message.getContent(); } } - if (StringUtils.isNotBlank(text)) { + if (Objects.nonNull(text)) { eventSourceListener.onEvent(null, "[DATA]", null, text); } CompletionsUsage usage = chatCompletions.getUsage(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java index 1b05a023d..4f744e170 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java @@ -54,13 +54,11 @@ public void onEvent(EventSource eventSource, String id, String type, String data return; } Message message = new Message(); - if (StringUtils.isNotBlank(data)) { - message.setContent(data); - sseEmitter.send(SseEmitter.event() - .id(null) - .data(message) - .reconnectTime(3000)); - } + message.setContent(data); + sseEmitter.send(SseEmitter.event() + .id(null) + .data(message) + .reconnectTime(3000)); } @Override From 3e5f6eac722e68cf39a492877ba99bcbc4d0d919 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 5 Jul 2023 22:24:28 +0800 Subject: [PATCH 0235/1069] =?UTF-8?q?fix:=20build=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E4=B8=BA=E7=94=A8=E6=88=B7=E6=89=80=E5=9C=A8=E6=97=B6=E5=8C=BA?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.prod.ts | 1 - chat2db-client/.umirc.ts | 7 ++++--- chat2db-client/.vscode/settings.json | 8 ++++++++ .../src/blocks/Setting/About/index.less | 2 +- .../src/blocks/Setting/About/index.tsx | 7 ++++--- .../src/components/Iconfont/index.tsx | 2 +- .../src/components/TestVersion/index.tsx | 2 +- chat2db-client/src/layouts/index.tsx | 5 +++-- chat2db-client/src/service/base.ts | 4 ++-- chat2db-client/src/utils/date.ts | 18 +++++++++++++++--- chat2db-client/src/utils/index.ts | 1 + 11 files changed, 40 insertions(+), 17 deletions(-) diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 6ac580b59..ef4ac1f3e 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -1,4 +1,3 @@ -import { formatDate } from './src/utils/date'; import { defineConfig } from 'umi'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); // const UMI_PublicPath = process.env.UMI_PublicPath || './static/front/'; diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 0fd1c5b0f..74a0c2a84 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -1,4 +1,4 @@ -import { formatDate } from './src/utils/date'; +import { transitionTimezoneTimestamp } from './src/utils/date'; import { defineConfig } from 'umi'; import { getLang } from '@/utils/localStorage'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); @@ -50,8 +50,9 @@ export default defineConfig({ ], favicons: ['logo.ico'], define: { - __ENV: process.env.UMI_ENV, - __BUILD_TIME__: formatDate(new Date(), 'yyyyMMddhhmmss'), + __ENV__: process.env.UMI_ENV, + __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), __APP_VERSION__: process.env.npm_config_app_version || '0.0.0', + __APP_PORT__: process.env.npm_config_app_port }, }); diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index a3dbc1f8d..e6dd6f7b4 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -21,13 +21,21 @@ "Iconfont", "JDBC", "KEYPAIR", + "Mddhhmmss", "SQLSERVER", "USERANDPASSWORD", + "ahooks", "antd", "chatgpt", "datas", "datasource", "dbhub", + "echarts", + "favicons", + "pgsql", + "remaininguses", + "sortablejs", + "umijs", "uuidv", "wireframe" ] diff --git a/chat2db-client/src/blocks/Setting/About/index.less b/chat2db-client/src/blocks/Setting/About/index.less index df6d7193e..1c2a2ddaf 100644 --- a/chat2db-client/src/blocks/Setting/About/index.less +++ b/chat2db-client/src/blocks/Setting/About/index.less @@ -20,7 +20,7 @@ .log { margin-top: 4px; - color: var(--custom-primary-color); + color: var(--color-primary); cursor: pointer; &:hover { diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index ea040413c..b09ed4712 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -3,6 +3,7 @@ import { APP_NAME, GITHUB_URL } from '@/constants/appConfig'; import i18n from '@/i18n'; import React from 'react'; import styles from './index.less'; +import { formatDate, getUserTimezoneTimestamp } from '@/utils/date'; // 关于我们 export default function AboutUs() { @@ -12,11 +13,11 @@ export default function AboutUs() {
{APP_NAME}
- {i18n('setting.text.currentEnv')}:{__ENV} + {i18n('setting.text.currentEnv')}:{__ENV__}
- {i18n('setting.text.currentVersion')}:v{'2.0.1' || __APP_VERSION__} build - {__BUILD_TIME__} + {i18n('setting.text.currentVersion')}:v{__APP_VERSION__} build + {formatDate(getUserTimezoneTimestamp(__BUILD_TIME__), 'yyyyMMddhhmmss')}
{i18n('setting.text.viewingUpdateLogs')} diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index f9847320f..16ae9a356 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -4,7 +4,7 @@ import classnames from 'classnames'; import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font -if (__ENV === 'local') { +if (__ENV__ === 'local') { let container = ` /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { diff --git a/chat2db-client/src/components/TestVersion/index.tsx b/chat2db-client/src/components/TestVersion/index.tsx index fc52aa84a..f34c62674 100644 --- a/chat2db-client/src/components/TestVersion/index.tsx +++ b/chat2db-client/src/components/TestVersion/index.tsx @@ -51,7 +51,7 @@ export default memo(function TestVersion(props) { const openNotification = (responseText: any) => { try { - const needToBeUpdated = isVersionHigher(responseText.version, '2.0.1'); + const needToBeUpdated = isVersionHigher(responseText.version, __APP_VERSION__); if (needToBeUpdated) { const key = `open${Date.now()}`; const btn = ( diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index b649f6a20..518603c44 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -34,7 +34,8 @@ declare global { } const __APP_VERSION__: string; const __BUILD_TIME__: string; - const __ENV: string; + const __ENV__: string; + const __APP_PORT__: string; } const initConfig = () => { @@ -152,7 +153,7 @@ function AppContainer() { .testService() .then(() => { clearInterval(time); - // if (__ENV === 'desktop') { + // if (__ENV__ === 'desktop') { // window.location.href = 'http://127.0.0.1:10824/' // } setStartSchedule(2); diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index acde390aa..3fd588c22 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -42,7 +42,7 @@ const noNeedToastErrorCode = [ErrorCode.NEED_LOGGED_IN]; const mockUrl = 'https://yapi.alibaba.com/mock/1000160'; // 桌面端的服务器地址 -const desktopServiceUrl = `http://127.0.0.1:${process.env.APP_PORT || '10824'}`; +const desktopServiceUrl = `http://127.0.0.1:${__APP_PORT__ || '10824'}`; // 非桌面端的服务器地址 const prodServiceUrl = location.origin; @@ -108,7 +108,7 @@ request.interceptors.request.use((url, options) => { request.interceptors.response.use(async (response, options) => { const res = await response.clone().json(); - if (__ENV === 'desktop') { + if (__ENV__ === 'desktop') { const DBHUB = response.headers.get('DBHUB') || ''; if (DBHUB) { localStorage.setItem('DBHUB', DBHUB); diff --git a/chat2db-client/src/utils/date.ts b/chat2db-client/src/utils/date.ts index 0e95159f6..0ac1d47fb 100644 --- a/chat2db-client/src/utils/date.ts +++ b/chat2db-client/src/utils/date.ts @@ -1,4 +1,4 @@ -export function formatDate(date:any, fmt = 'yyyy-MM-dd') { +export function formatDate(date: any, fmt = 'yyyy-MM-dd') { if (!date) { return ''; } @@ -8,7 +8,7 @@ export function formatDate(date:any, fmt = 'yyyy-MM-dd') { if (!(date instanceof Date) || isNaN(date.getTime())) { return ''; } - var o:any = { + var o: any = { 'M+': date.getMonth() + 1, 'd+': date.getDate(), 'h+': date.getHours(), @@ -18,8 +18,20 @@ export function formatDate(date:any, fmt = 'yyyy-MM-dd') { S: date.getMilliseconds(), }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); - for (var k in o) + for (var k in o) if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); return fmt; +} + +// 带有时区的时间戳转换为0时区时间戳 +export function transitionTimezoneTimestamp(timestamp: number) { + const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 + return timestamp + timezoneOffset +} + +// 通过0时区的时间戳计算出用户的时间戳 +export function getUserTimezoneTimestamp(timestamp: number | string) { + const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 + return +timestamp - timezoneOffset } \ No newline at end of file diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 17ff439e0..35846cffb 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -174,6 +174,7 @@ export function clearOlderLocalStorage() { } } +// 判断是否需要更新版本 export function isVersionHigher(version: string, currentVersion: string): boolean { // 按照 . 分割版本号 const versionParts = version.split('.'); From 5652a2f65e2e188b43f4753b5c1e4366eaf5010e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 5 Jul 2023 22:24:50 +0800 Subject: [PATCH 0236/1069] =?UTF-8?q?fix:=20build=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E4=B8=BA=E7=94=A8=E6=88=B7=E6=89=80=E5=9C=A8=E6=97=B6=E5=8C=BA?= =?UTF-8?q?=E7=9A=84=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7de422d83..1ea9462bf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "AZUREAI", + "DBHUB", "Dserver", "Dspring", "Mddhhmmss", @@ -24,6 +25,7 @@ "togglefullscreen", "umijs", "wechat", - "wireframe" + "wireframe", + "yapi" ] } From 0f88a70616ceec56e29f221af3b0eb0376d93868 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 5 Jul 2023 22:45:03 +0800 Subject: [PATCH 0237/1069] fix: fix Cascader --- .../components/Console/MonacoEditor/index.tsx | 23 +++-- .../src/components/Console/index.tsx | 62 ++++++++------ .../src/components/SearchResult/TableBox.tsx | 17 ++-- .../pages/main/dashboard/chart-item/index.tsx | 84 +++++++++++-------- .../src/pages/main/dashboard/index.tsx | 9 +- .../components/WorkspaceRight/index.tsx | 4 +- chat2db-client/src/utils/database.ts | 3 + chat2db-client/src/utils/index.ts | 2 +- 8 files changed, 121 insertions(+), 83 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 12310db78..c53ec705b 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -7,7 +7,7 @@ const { keywords: SQLKeys } = language; const { keywords } = language; import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import styles from './index.less'; -import { useUpdateEffect } from '@/hooks' +import { useUpdateEffect } from '@/hooks'; export type IEditorIns = monaco.editor.IStandaloneCodeEditor; export type IEditorOptions = monaco.editor.IStandaloneEditorConstructionOptions; export type IEditorContentChangeEvent = monaco.editor.IModelContentChangedEvent; @@ -30,7 +30,7 @@ interface IProps { onSave?: (value: string) => void; // 快捷键保存的回调 defaultValue?: string; onExecute?: (value: string) => void; // 快捷键执行的回调 - tables: any[] + tables?: any[]; } export interface IExportRefFunction { @@ -45,7 +45,18 @@ export interface IHintData { } function MonacoEditor(props: IProps, ref: ForwardedRef) { - const { id, className, language = 'sql', didMount, options, isActive, onSave, onExecute, defaultValue, appendValue } = props; + const { + id, + className, + language = 'sql', + didMount, + options, + isActive, + onSave, + onExecute, + defaultValue, + appendValue, + } = props; const editorRef = useRef(); const [appTheme] = useTheme(); @@ -152,7 +163,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { if (appendValue) { appendMonacoValue(editorRef.current, appendValue?.text, appendValue?.range); } - }, [appendValue]) + }, [appendValue]); const setValue = (text: any, range?: IRangeType) => { appendMonacoValue(editorRef.current, text, range); @@ -291,8 +302,8 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e // 创建编辑操作,将当前文档内容替换为新内容 let newRange: IRangeType = range; if (range === 'reset') { - editor.setValue(text) - return + editor.setValue(text); + return; } switch (range) { case 'cover': diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index d13775a0e..a183ecf18 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -12,13 +12,11 @@ import { v4 as uuidv4 } from 'uuid'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import Iconfont from '../Iconfont'; import { ITreeNode } from '@/typings'; -import styles from './index.less'; import i18n from '@/i18n'; -import { IRemainingUse } from '@/typings/ai'; import { IAIState } from '@/models/ai'; -import { WECHAT_MP_URL } from '@/constants/social'; import Popularize from '@/components/Popularize'; -import { handelLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils' +import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; +import styles from './index.less'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -43,7 +41,7 @@ export type IAppendValue = { interface IProps { /** 是谁在调用我 */ - source: 'workspace', + source: 'workspace'; /** 是否是活跃的console,用于快捷键 */ isActive?: boolean; /** 添加或修改的内容 */ @@ -76,7 +74,18 @@ interface IProps { } function Console(props: IProps) { - const { hasAiChat = true, executeParams, appendValue, isActive, hasSaveBtn = true, value, aiModel, dispatch, source, defaultValue } = props; + const { + hasAiChat = true, + executeParams, + appendValue, + isActive, + hasSaveBtn = true, + value, + aiModel, + dispatch, + source, + defaultValue, + } = props; const uid = useMemo(() => uuidv4(), []); const chatResult = useRef(''); const editorRef = useRef(); @@ -107,14 +116,14 @@ function Console(props: IProps) { useEffect(() => { if (source !== 'workspace') { - return + return; } // 离开时保存 if (!isActive) { // 离开时清除定时器 if (timerRef.current) { - clearInterval(timerRef.current) - handelLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); + clearInterval(timerRef.current); + handleLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); } } else { // 活跃时自动保存 @@ -122,25 +131,25 @@ function Console(props: IProps) { } return () => { if (timerRef.current) { - clearInterval(timerRef.current) + clearInterval(timerRef.current); } - } - }, [isActive]) + }; + }, [isActive]); useEffect(() => { if (source !== 'workspace') { - return + return; } - const value = readLocalStorageSavedConsoleText(executeParams.consoleId!) + const value = readLocalStorageSavedConsoleText(executeParams.consoleId!); if (value) { editorRef?.current?.setValue(value, 'reset'); } - }, []) + }, []); function timingAutoSave() { timerRef.current = setInterval(() => { - handelLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); - }, 5000) + handleLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); + }, 5000); } const tableListName = useMemo(() => { @@ -257,7 +266,7 @@ function Console(props: IProps) { }; historyServer.updateSavedConsole(p).then((res) => { - handelLocalStorageSavedConsole(executeParams.consoleId!, 'delete'); + handleLocalStorageSavedConsole(executeParams.consoleId!, 'delete'); message.success(i18n('common.tips.saveSuccessfully')); props.onConsoleSave && props.onConsoleSave(); }); @@ -285,7 +294,7 @@ function Console(props: IProps) { ); const popUpPrompts = () => { - setPopularizeModal(true) + setPopularizeModal(true); }; return (
@@ -317,7 +326,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> @@ -334,7 +343,11 @@ function Console(props: IProps) { {i18n('common.button.execute')} {hasSaveBtn && ( - )} @@ -349,14 +362,9 @@ function Console(props: IProps) { {i18n('common.button.format')}
- setPopularizeModal(false)} - > + setPopularizeModal(false)}> -
); } diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index f929ecbf6..8b7a226c7 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -158,13 +158,12 @@ export default function TableBox(props: ITableProps) { //
<> -
{`${i18n('common.text.result')}:${description}. ${i18n('common.text.timeConsuming')}:${duration}ms`}
+
{`${i18n('common.text.result')}:${description}. ${i18n( + 'common.text.timeConsuming', + )}:${duration}ms`}
) : ( @@ -188,12 +187,10 @@ export default function TableBox(props: ITableProps) {
(); const [cascaderValue, 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(); @@ -116,6 +117,13 @@ 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); @@ -126,6 +134,8 @@ function ChartItem(props: IChartItemProps) { }; /** 根据id请求Chart数据 */ const queryChartData = async () => { + setIsLoading(true); + const { id } = props; let res = await getChartById({ id }); const { dataSourceId, databaseName, ddl } = res; @@ -160,6 +170,7 @@ function ChartItem(props: IChartItemProps) { }); }); } + setIsLoading(false); }; const onExport2Image = () => { @@ -324,6 +335,7 @@ function ChartItem(props: IChartItemProps) { hasSaveBtn={false} value={chartData?.ddl} onExecuteSQL={(result: any, sql: string) => { + setIsLoading(true); let sqlData; if (result && result[0]) { sqlData = handleSQLResult2ChartData(result[0]); @@ -333,6 +345,7 @@ function ChartItem(props: IChartItemProps) { ddl: sql, sqlData, }); + setIsLoading(false); }} editorOptions={{ lineNumbers: 'off', @@ -348,6 +361,7 @@ function ChartItem(props: IChartItemProps) { { console.log('onChange', value, selectedOptions); let p: any = {}; //包含了dataSourceId、databaseName、schemaName @@ -415,42 +429,44 @@ function ChartItem(props: IChartItemProps) { }; return ( -
- {renderPlusIcon()} -
-
{chartData?.name}
-
- { - setIsEditing(true); + +
+ {renderPlusIcon()} +
+
{chartData?.name}
+
+ { + setIsEditing(true); + }, }, - }, - { - key: 'Export', - label: i18n('dashboard.export2image'), - onClick: onExport2Image, - }, - { - key: 'delete', - label: i18n('dashboard.delete'), - onClick: onDeleteChart, - }, - ], - }} - placement="bottomLeft" - > - - + { + key: 'Export', + label: i18n('dashboard.export2image'), + onClick: onExport2Image, + }, + { + key: 'delete', + label: i18n('dashboard.delete'), + onClick: onDeleteChart, + }, + ], + }} + placement="bottomLeft" + > + + +
+ {chartData?.sqlData || isEditing ? renderChart() : renderEmptyBlock()} + {isEditing && renderEditorBlock()}
- {chartData?.sqlData || isEditing ? renderChart() : renderEmptyBlock()} - {isEditing && renderEditorBlock()} -
+ ); } diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 078cb2488..eeb224d1e 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -51,6 +51,11 @@ function Chart(props: IProps) { useEffect(() => { // 获取列表数据 queryDashboardList(); + + // 如果没有连接池,触发一次请求 + dispatch({ + type: 'connection/fetchConnectionList', + }); }, []); useEffect(() => { @@ -232,9 +237,7 @@ function Chart(props: IProps) { ); }; - const updateAiRemainUse = () => { - - }; + const updateAiRemainUse = () => {}; return ( <> 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 36498d245..dc35a9fcb 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -10,7 +10,7 @@ import LoadingContent from '@/components/Loading/LoadingContent'; import WorkspaceRightItem from '../WorkspaceRightItem'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IAIState } from '@/models/ai'; -import { handelLocalStorageSavedConsole } from '@/utils' +import { handleLocalStorageSavedConsole } from '@/utils'; interface IProps { className?: string; @@ -161,7 +161,7 @@ const WorkspaceRight = memo(function (props) { // historyService.deleteSavedConsole({ id: window!.id }); // } else { historyService.updateSavedConsole(p).then(() => { - handelLocalStorageSavedConsole(p.id, 'delete') + handleLocalStorageSavedConsole(p.id, 'delete'); }); // } }; diff --git a/chat2db-client/src/utils/database.ts b/chat2db-client/src/utils/database.ts index dbcefa6e4..2b10ae669 100644 --- a/chat2db-client/src/utils/database.ts +++ b/chat2db-client/src/utils/database.ts @@ -12,6 +12,7 @@ export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType[' value: t.name, label: t.name, type: 'schema', + isLeaf: true, }; }); } @@ -20,6 +21,7 @@ export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType[' label: t.name, type: 'database', children: schemasList, + isLeaf: schemasList.length === 0, }; }); } else if (databaseAndSchema?.schemas) { @@ -28,6 +30,7 @@ export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType[' value: t.name, label: t.name, type: 'schema', + isLeaf: true, }; }); } diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 35846cffb..8bf2d801d 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -140,7 +140,7 @@ export function findObjListValue(list: T[], key: K, value: } // 处理console的保存和删除操作 -export function handelLocalStorageSavedConsole(id: number, type: 'save' | 'delete', text?: string) { +export function handleLocalStorageSavedConsole(id: number, type: 'save' | 'delete', text?: string) { const saved = localStorage.getItem(`timing-auto-save-console-v1`); let savedObj: any = {}; if (saved) { From 71d6b9f5161a509c48363bc81d9b9282d666fbfa Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 5 Jul 2023 22:48:04 +0800 Subject: [PATCH 0238/1069] feat: modify readme --- README.md | 121 ++++++++++++++++------------------ README_CN.md | 183 +++++++++++++++++++++++++-------------------------- 2 files changed, 147 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 8420d0b10..e448b4698 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
- An intelligent and versatile general-purpose SQL client and reporting tool for databases which integrates ChatGPT capabilities. +An intelligent and versatile general-purpose SQL client and reporting tool for databases which integrates ChatGPT capabilities. [![License](https://img.shields.io/github/license/alibaba/fastjson2?color=4D7A97&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0.html) [![GitHub release](https://img.shields.io/github/release/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/releases) @@ -25,17 +25,19 @@ **License Notation**: Chat2DB is constructed and distributed for personal and non-commercial use only. For commercial use of this project, please contact corresponding authors. Languages: English | [中文](README_CN.md) -
+
## DEMO https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb7572465a2e - ## 📖 Introduction +    Chat2DB is a multi-database client tool that is open-source and free from Alibaba. It supports local installation on Windows and Mac, as well as server-side deployment and web page access. Compared to traditional database client software such as Navicat and DBeaver, Chat2DB integrates AIGC's capabilities and is able to convert natural language into SQL. It can also convert SQL into natural language and provide optimization suggestions for SQL to greatly enhance the efficiency of developers. It is a tool for database developers in the AI era, and even non-SQL business operators in the future can use it to quickly query business data and generate reports. + ## ✨ Features + - 🌈 AI intelligent assistant, supporting natural language to SQL conversion, SQL to natural language conversion, and SQL optimization suggestions - 👭 Support team collaboration, developers do not need to know the online database password, solving the problem of enterprise database account security - ⚙️ Powerful data management capability, supporting management of data tables, views, stored procedures, functions, triggers, indexes, sequences, users, roles, authorizations, etc. @@ -43,52 +45,61 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 - 🛡 Front-end development using Electron, providing a solution that integrates Windows, Mac, Linux clients, and web versions - 🎁 Support environment isolation, online, and daily data permission separation - ## ⏬ Download and Install -| Description | Download | -|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | +| Description | Download | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | | MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | -| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | +| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | +| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | ## 🚀 Supported databases -| Databases | Status | -|---------------|---------| + +| Databases | Status | +| ------------- | -------- | | Mysql | ✅ | | H2 | ✅ | -| Oracle | ✅ | -| PostgreSQL | ✅ | -| SQLServer | ✅ | -| SQLLite | ✅ | -| MariaDB | ✅ | -| ClickHouse | ✅ | -| DM | ✅ | -| Presto | ✅ | -| DB2 | ✅ | -| OceanBase | ✅ | -| Redis | ✅ | -| Hive | ✅ | -| KingBase | ✅ | -| MongoDB | ✅ | -| Hbase |Planning | -| Elasticsearch | Planning | -| openGauss | Planning | +| Oracle | ✅ | +| PostgreSQL | ✅ | +| SQLServer | ✅ | +| SQLLite | ✅ | +| MariaDB | ✅ | +| ClickHouse | ✅ | +| DM | ✅ | +| Presto | ✅ | +| DB2 | ✅ | +| OceanBase | ✅ | +| Redis | ✅ | +| Hive | ✅ | +| KingBase | ✅ | +| MongoDB | ✅ | +| Hbase | Planning | +| Elasticsearch | Planning | +| openGauss | Planning | | TiDB | Planning | | InfluxDB | Planning | ## 🌰 Demo + ### Create data source +
+ ### Data source management + + ### SQL console + + ### AI intelligent assistant + ## 🔥 AI Configuration + ### CONFIGURE OPENAI Option 1 (recommended): To use the ChatSql function of OPENAI, two conditions must be met: @@ -103,20 +114,18 @@ Option 2 (recommended): We provide a unified proxy service. - No OPENAI_API_KEY is required. - No proxy or VPN is required, as long as the network is connected. -To facilitate users' quick use of AI capabilities, you can scan the QR code below to follow our WeChat public account and apply for our custom API_KEY. +To facilitate users' quick use of AI capabilities, you can scan the QR code below to follow our WeChat public account and apply for our custom API_KEY. - ### CONFIGURE CUSTOM AI + - Customized AI can be any LLM that you deployed, such as ChatGLM、ChatGPT、ERNIE Bot、Tongyi Qianwen, and so on. However, the customized interface need to conform to the protocol definition. Otherwise, secondary development may be required. Two DEMOs are provided in the code, the configuration is as shown below. In specific use, you can refer to the DEMO interface to write a custom interface, or directly perform secondary development in the DEMO interface. - DEMO for configuring customized stream output interface. - DEMO for configuring customized non-stream output interface. - - ## 📦 Docker installation ```bash @@ -124,23 +133,31 @@ docker pull chat2db/chat2db:latest ``` ## 🎯 Operating Environment + Note: If local debugging is required + - Java runtime Open JDK 17 - JRE reference packaging and deployment method of jre. - Node runtime environment Node16 Node.js. ## 💻 Local Debugging + - git clone to local + ```bash $ git clone git@github.com:alibaba/Chat2DB.git ``` + - Front-End installation + ```bash $ cd Chat2DB/chat2db-client $ npm install # Mounting front-end dependency $ npm run build:prod # Package js to the source directory on the back end ``` + - Backend debug + ```bash $ cd ../chat2db-server $ mvn clean install # maven 3.8 or later needs to be installed @@ -150,63 +167,41 @@ $ # open http://127.0.0.1:10821 to start debug Note: Front-end installation is r ``` - Front-End debug + ```bash $ cd Chat2DB/chat2db-client -$ yarn +$ yarn $ npm run start:web -$ # open http://127.0.0.1:10821 to start Front-End debug -$ # Note Front-end page completely depends on the service, so front-end students need to debug the back-end project -``` -But front debugging need mapping of resources, you can download [XSwitch](https://chrome.google.com/webstore/detail/idkjhjggpffolpidfkikidcokdkdaogg), add the following configuration file -``` json -{ - "proxy": [ - [ - "http://127.0.0.1:10821/(.*).js$", - "http://127.0.0.1:8001/$1.js", - ], - [ - "http://127.0.0.1:10821/(.*).css$", - "http://127.0.0.1:8001/$1.css", - ], - [ - "http://127.0.0.1:10821/static/front/(.*)", - "http://127.0.0.1:8001/$1", - ], - [ - "http://127.0.0.1:10821/static/(.*)$", - "http://127.0.0.1:8001/static/$1", - ], - ], -} - ``` ## 📑 Documentation -* Official website document -* Issue +- Official website document +- Issue ## Stargazers + [![Stargazers repo roster for @chat2db/Chat2DB](https://reporoster.com/stars/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) ## Forkers + [![Forkers repo roster for @chat2db/Chat2DB](https://reporoster.com/forks/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) ## ☎️ Contact Us + Please star and fork on GitHub before joining the group. Follow our WeChat public account. ## ❤️ Acknowledgements + Thanks to all the students who contributed to Chat2DB~ - ## Star History @@ -216,5 +211,3 @@ Thanks to all the students who contributed to Chat2DB~ Star History Chart - - diff --git a/README_CN.md b/README_CN.md index a967ee365..5d8b3362a 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,7 +2,7 @@
- 智能且多功能的SQL客户端和报表工具,适用于各种数据库 +智能且多功能的 SQL 客户端和报表工具,适用于各种数据库 [![License](https://img.shields.io/github/license/alibaba/fastjson2?color=4D7A97&logo=apache)](https://www.apache.org/licenses/LICENSE-2.0.html) [![Java support](https://img.shields.io/badge/Java-17+-green?logo=java&logoColor=white)](https://openjdk.java.net/) @@ -23,93 +23,106 @@

-**许可说明**: Chat2DB开源内容仅供个人免费使用,如想将该项目用于商业用途,请先联系该项目作者。 +**许可说明**: Chat2DB 开源内容仅供个人免费使用,如想将该项目用于商业用途,请先联系该项目作者。 Languages: 中文 [English](README.md) +
## 案例视频 https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f - ## 📖 简介 -   Chat2DB 是一款有开源免费的多数据库客户端工具,支持windows、mac本地安装,也支持服务器端部署,web网页访问。和传统的数据库客户端软件Navicat、DBeaver 相比Chat2DB集成了AIGC的能力,能够将自然语言转换为SQL,也可以将SQL转换为自然语言,可以给出研发人员SQL的优化建议,极大的提升人员的效率,是AI时代数据库研发人员的利器,未来即使不懂SQL的运营业务也可以使用快速查询业务数据、生成报表能力。 + +   Chat2DB 是一款有开源免费的多数据库客户端工具,支持 windows、mac 本地安装,也支持服务器端部署,web 网页访问。和传统的数据库客户端软件 Navicat、DBeaver 相比 Chat2DB 集成了 AIGC 的能力,能够将自然语言转换为 SQL,也可以将 SQL 转换为自然语言,可以给出研发人员 SQL 的优化建议,极大的提升人员的效率,是 AI 时代数据库研发人员的利器,未来即使不懂 SQL 的运营业务也可以使用快速查询业务数据、生成报表能力。 + ## ✨ 特性 -- 🌈 AI智能助手,支持自然语言转SQL、SQL转自然语言、SQL优化建议 -- 🔥 SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 + +- 🌈 AI 智能助手,支持自然语言转 SQL、SQL 转自然语言、SQL 优化建议 +- 🔥 SQL 查询、AI 查询和数据报表完美集成的一体化解决方案设计与实现 - 👭 支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题 - ⚙️ 强大的数据管理能力,支持数据表、视图、存储过程、函数、触发器、索引、序列、用户、角色、授权等管理 -- 🔌 强大的扩展能力,目前已经支持MySQL、PostgreSQL、Oracle、SQLServer、ClickHouse、OceanBase、H2、SQLite等等,未来会支持更多的数据库 +- 🔌 强大的扩展能力,目前已经支持 MySQL、PostgreSQL、Oracle、SQLServer、ClickHouse、OceanBase、H2、SQLite 等等,未来会支持更多的数据库 - 🛡 前端使用 Electron 开发,提供 Windows、Mac、Linux 客户端、网页版本一体化的解决方案 - 🎁 支持环境隔离、线上、日常数据权限分离 ## 产品展示 + ## ⏬ 下载安装 -| 描述 | 下载地址 | -|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | -| MacOS ARM64 (Apple芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | -| MacOS X64 (Intel芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | -| Jar包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | + +| 描述 | 下载地址 | +| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | +| MacOS ARM64 (Apple 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | +| MacOS X64 (Intel 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | +| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | ## 🚀 支持的数据库 -| 数据库 | 支持计划 | -|------------|---------| -| Mysql | ✅ | -| H2 | ✅ | -| Oracle | ✅ | -| PostgreSQL | ✅ | -| SQLServer | ✅ | -| SQLLite | ✅ | -| MariaDB | ✅ | -| ClickHouse | ✅ | -| DM | ✅ | -| Presto | ✅ | -| DB2 | ✅ | -| OceanBase | ✅ | -| Redis | ✅ | -| Hive | ✅ | -| KingBase | ✅ | -| MongoDB | ✅ | -| Hbase |Planning | -| Elasticsearch| Planning | -| openGauss| Planning | -| TiDB| Planning | -| InfluxDB| Planning | -## 🌰 使用Demo + +| 数据库 | 支持计划 | +| ------------- | -------- | +| Mysql | ✅ | +| H2 | ✅ | +| Oracle | ✅ | +| PostgreSQL | ✅ | +| SQLServer | ✅ | +| SQLLite | ✅ | +| MariaDB | ✅ | +| ClickHouse | ✅ | +| DM | ✅ | +| Presto | ✅ | +| DB2 | ✅ | +| OceanBase | ✅ | +| Redis | ✅ | +| Hive | ✅ | +| KingBase | ✅ | +| MongoDB | ✅ | +| Hbase | Planning | +| Elasticsearch | Planning | +| openGauss | Planning | +| TiDB | Planning | +| InfluxDB | Planning | + +## 🌰 使用 Demo + ### 创建数据源 + ### 数据源管理 + -### SQL控制台 及 AI智能助手 -#### 使用前需要配置OpenAI的Api Key及本地代理配置 +### SQL 控制台 及 AI 智能助手 + +#### 使用前需要配置 OpenAI 的 Api Key 及本地代理配置 + +## 🔥 AI 配置 + +### 使用 ChatGPT + +方式一(推荐):使用 OPENAI 的 ChatSql 功能需要满足两个条件 -## 🔥 AI配置 -### 使用ChatGPT -方式一(推荐):使用OPENAI的ChatSql功能需要满足两个条件 -- 1、需要有一个openAI的key:OPENAI_API_KEY -- 2、客户端网络可以连接到OPENAI官网,国内需要科学上网。注意:如果本地VPN未能全局生效,可以通过在客户端中设置网络代理HOST和PORT来保证网络连通性 -- - +- 1、需要有一个 openAI 的 key:OPENAI_API_KEY +- 2、客户端网络可以连接到 OPENAI 官网,国内需要科学上网。注意:如果本地 VPN 未能全局生效,可以通过在客户端中设置网络代理 HOST 和 PORT 来保证网络连通性 +- 方式二(推荐):使用我们提供了一个统一的代理服务。 -- 1、不需要openAI的key -- 2、不需要代理,不需要VPN只要可以联网即可使用。 -为了方便大家更快速的使用AI的能力,可以关注微信公众号,回复"AI" 获得我们的自定义API_KEY,申请完成之后参考下图进行配置即可进行使用 +- 1、不需要 openAI 的 key +- 2、不需要代理,不需要 VPN 只要可以联网即可使用。 - +为了方便大家更快速的使用 AI 的能力,可以关注微信公众号,回复"AI" 获得我们的自定义 API_KEY,申请完成之后参考下图进行配置即可进行使用 + -## 📦 Docker部署 +## 📦 Docker 部署 ```bash docker pull chat2db/chat2db:latest @@ -118,29 +131,39 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 // 后台运行,运行后可以关闭命令行 docker run --name=chat2db -p 10824:10824 chat2db/chat2db:latest // 这里正常会提示 Tomcat started on port(s): 10824 (http) with context path 就可以结束了 - + // 如果这里提示 The container name "/chat2db" is already in use by container, 代表已经存在容器了 运行 dcoker run chat2db // 如果想更新chat2db 则需要先rm 再运行 dcoker rm chat2db ``` + ## 🎯 运行环境 + 注意: 如果需要本地调试 -- java运行 Open JDK 17 -- Node运行环境Node16 Node.js. + +- java 运行 Open JDK 17 +- Node 运行环境 Node16 Node.js. + ## 💻 本地调试 -- git clone到本地 + +- git clone 到本地 + ```bash $ git clone git@github.com:chat2db/Chat2DB.git ``` + - 前端安装 + ```bash $ cd Chat2DB/chat2db-client $ npm install # 安装前端依赖 $ npm run build:prod # 把js打包生成到后端的source目录 ``` + - 后端调试 + ```bash $ cd ../chat2db-server $ mvn clean install # 需要安装maven 3.8以上版本 @@ -150,45 +173,19 @@ $ # 打开 http://127.0.0.1:10821 开启调试 注:需要进行前端安装 ``` - 前端调试 + ```bash $ cd Chat2DB/chat2db-client -$ npm install -$ npm run start -$ # 打开 http://127.0.0.1:10821 开启前端调试 -$ # 注:前端页面完全赖服务,所以前端同学调试也需要把后端项目跑起来 -``` -但是前端调试需要映射下资源,可以下载[XSwitch](https://chrome.google.com/webstore/detail/idkjhjggpffolpidfkikidcokdkdaogg),添加以下配置文件 -``` json -{ - "proxy": [ - [ - "http://127.0.0.1:10821/(.*).js$", - "http://127.0.0.1:8001/$1.js", - ], - [ - "http://127.0.0.1:10821/(.*).css$", - "http://127.0.0.1:8001/$1.css", - ], - [ - "http://127.0.0.1:10821/static/front/(.*)", - "http://127.0.0.1:8001/$1", - ], - [ - "http://127.0.0.1:10821/static/(.*)$", - "http://127.0.0.1:8001/static/$1", - ], - ], -} +$ yarn +$ npm run start:web ``` - - ## 📑 文档 -* 官方文档 -* Issue +- 官方文档 +- Issue -## 常见问题 +## 常见问题 ### 1、无法获取数据源驱动:getJDBCDriver error: null @@ -197,6 +194,7 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 解决办法:手动下载相关驱动放入到 ~/.chat2db/jdbc-lib 目录下 下载链接 参考:Application jdbc-jar-downLoad-urls + - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-8.0.30.jar - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-5.1.47.jar - https://oss-chat2db.alibaba.com/lib/clickhouse-jdbc-0.3.2-patch8-http.jar @@ -207,23 +205,23 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 - https://oss-chat2db.alibaba.com/lib/sqlite-jdbc-3.39.3.0.jar - https://oss-chat2db.alibaba.com/lib/ojdbc11.jar - - - ## Stargazers + [![Stargazers repo roster for @chat2db/Chat2DB](https://reporoster.com/stars/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/stargazers) ## Forkers + [![Forkers repo roster for @chat2db/Chat2DB](https://reporoster.com/forks/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/network/members) ## ☎️ 联系我们 -加群前请先Star和Fork,谢谢~关注微信公众号可加入微信、钉钉、QQ群一起讨论,并可以获取Chat2DB最新动态和更新。 +加群前请先 Star 和 Fork,谢谢~关注微信公众号可加入微信、钉钉、QQ 群一起讨论,并可以获取 Chat2DB 最新动态和更新。 ## ❤️ 致谢 -感谢所有为Chat2DB贡献力量的同学们~ + +感谢所有为 Chat2DB 贡献力量的同学们~ @@ -238,4 +236,3 @@ $ # 注:前端页面完全赖服务,所以前端同学调试也需要把后 Star History Chart - From 8375af776c7a9152d09bc1cb5380c8295f172701 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 5 Jul 2023 23:02:09 +0800 Subject: [PATCH 0239/1069] =?UTF-8?q?feat:yarn=20run=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.ts | 4 ++-- chat2db-client/readme.md | 5 +++++ chat2db-client/src/main/index.js | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 74a0c2a84..b545daedd 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -52,7 +52,7 @@ export default defineConfig({ define: { __ENV__: process.env.UMI_ENV, __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), - __APP_VERSION__: process.env.npm_config_app_version || '0.0.0', - __APP_PORT__: process.env.npm_config_app_port + __APP_VERSION__: process.argv[3]|| '0.0.0', + __APP_PORT__: process.argv[4] }, }); diff --git a/chat2db-client/readme.md b/chat2db-client/readme.md index 1d43aa257..9a9cdb332 100644 --- a/chat2db-client/readme.md +++ b/chat2db-client/readme.md @@ -8,6 +8,11 @@ 目录结构 tree ./ -L 2 -I node_modules +## 启动项目 + 0. 强制使用yarn,因为环境变量、lock文件只维护了yarn,npm/pnpm可能会产生意想不到的bug + 1. `yarn` + 2. `yarn run start:web` + ## TS书写规范 1. 所有的interfase 与 type 必须已I开头 `interfase IState { name: string }` // good diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index a6c332927..d83c0df83 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -65,7 +65,7 @@ app.on('before-quit', (event) => { 'Content-Type': 'application/json', }, method: 'POST', - url: 'http://127.0.0.1:10824/api/system/stop', + url: 'http://127.0.0.1:10824/api/system/stop', //TODO: 测试包需要换10822 }); request.write(JSON.stringify({})); request.on('response', (response) => { From 096cb197fe457e13646ddb72bb7056796a996066 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Wed, 5 Jul 2023 23:02:09 +0800 Subject: [PATCH 0240/1069] fix bugs --- .../CreateConnection/config/dataSource.ts | 667 +----------------- .../kingbase/builder/DBConfigBuilder.java | 2 +- .../domain/core/impl/DatabaseServiceImpl.java | 30 +- .../core/impl/DlTemplateServiceImpl.java | 6 +- .../chat2db/spi/jdbc/DefaultMetaService.java | 19 +- .../ai/chat2db/spi/sql/Chat2DBContext.java | 40 +- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 17 +- 7 files changed, 84 insertions(+), 697 deletions(-) diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index c4dbc2d87..dc83fa8bf 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -332,9 +332,9 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'host', - name: 'host', + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', required: false, styles: { width: '70%', @@ -343,7 +343,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, @@ -358,7 +358,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', - labelNameEN: 'userName', + labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { @@ -547,19 +547,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'host', - name: 'host', + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', required: false, styles: { width: '70%', - } }, { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, @@ -574,7 +573,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', - labelNameEN: 'userName', + labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { @@ -735,19 +734,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'host', - name: 'host', + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', required: false, styles: { width: '70%', - } }, { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, @@ -762,7 +760,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', - labelNameEN: 'userName', + labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { @@ -950,19 +948,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'host', - name: 'host', + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', required: false, styles: { width: '70%', - } }, { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, @@ -977,7 +974,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', - labelNameEN: 'userName', + labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { @@ -1067,19 +1064,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'host', - name: 'host', + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', required: false, styles: { width: '70%', - } }, { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, @@ -1094,7 +1090,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', - labelNameEN: 'userName', + labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { @@ -1255,19 +1251,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'host', - name: 'host', + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', required: false, styles: { width: '70%', - } }, { defaultValue: '', inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: 'SSH 端口', labelNameEN: 'Port', name: 'port', required: false, @@ -1282,7 +1277,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ defaultValue: '', inputType: InputType.INPUT, labelNameCN: '用户名', - labelNameEN: 'userName', + labelNameEN: 'SSH UserName', name: 'userName', required: false, styles: { @@ -1441,18 +1436,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - styles: { - width: '70%', - - } - }, { defaultValue: '', inputType: InputType.INPUT, @@ -1462,7 +1445,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - } }, { @@ -1647,7 +1629,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: false, styles: { width: '70%', - } }, { @@ -2491,594 +2472,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], type: DatabaseTypeCode.REDIS }, - //redis - { - baseInfo: { - items: [ - { - defaultValue: '@localhost', - inputType: InputType.INPUT, - labelNameCN: '名称', - labelNameEN: 'Name', - name: 'alias', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: 'localhost', - inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'Host', - name: 'host', - required: true, - styles: { - width: '70%', - } - }, - { - defaultValue: '6379', - inputType: InputType.INPUT, - labelNameCN: '端口', - labelNameEN: 'Port', - name: 'port', - labelTextAlign: 'right', - required: true, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: AuthenticationType.USERANDPASSWORD, - inputType: InputType.SELECT, - labelNameCN: '身份验证', - labelNameEN: 'Authentication', - name: 'authentication', - required: true, - selects: [ - { - items: [ - { - defaultValue: 'root', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'User', - name: 'user', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ], - label: 'User&Password', - value: AuthenticationType.USERANDPASSWORD, - }, - { - label: 'NONE', - value: AuthenticationType.NONE, - }, - ], - styles: { - width: '50%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '数据库', - labelNameEN: 'Database', - name: 'database', - required: false, - styles: { - width: '100%', - } - }, - { - defaultValue: 'jdbc:redis://localhost:6379', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:redis://{host}:{port}/{database}', - }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, - extendInfo: [ - - ], - type: DatabaseTypeCode.REDIS - }, - //hive - { - baseInfo: { - items: [ - { - defaultValue: '@localhost', - inputType: InputType.INPUT, - labelNameCN: '名称', - labelNameEN: 'Name', - name: 'alias', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: 'localhost', - inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'Host', - name: 'host', - required: true, - styles: { - width: '70%', - } - }, - { - defaultValue: '10000', - inputType: InputType.INPUT, - labelNameCN: '端口', - labelNameEN: 'Port', - name: 'port', - labelTextAlign: 'right', - required: true, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: AuthenticationType.USERANDPASSWORD, - inputType: InputType.SELECT, - labelNameCN: '身份验证', - labelNameEN: 'Authentication', - name: 'authentication', - required: true, - selects: [ - { - items: [ - { - defaultValue: 'root', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'User', - name: 'user', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ], - label: 'User&Password', - value: AuthenticationType.USERANDPASSWORD, - }, - { - label: 'NONE', - value: AuthenticationType.NONE, - }, - ], - styles: { - width: '50%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '数据库', - labelNameEN: 'Database', - name: 'database', - required: false, - styles: { - width: '100%', - } - }, - { - defaultValue: 'jdbc:hive2://localhost:10000', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:hive2://{host}:{port}/{database}', - }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, - extendInfo: [ - - ], - type: DatabaseTypeCode.HIVE - }, - //hive - { - baseInfo: { - items: [ - { - defaultValue: '@localhost', - inputType: InputType.INPUT, - labelNameCN: '名称', - labelNameEN: 'Name', - name: 'alias', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: 'localhost', - inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'Host', - name: 'host', - required: true, - styles: { - width: '70%', - } - }, - { - defaultValue: '10000', - inputType: InputType.INPUT, - labelNameCN: '端口', - labelNameEN: 'Port', - name: 'port', - labelTextAlign: 'right', - required: true, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: AuthenticationType.USERANDPASSWORD, - inputType: InputType.SELECT, - labelNameCN: '身份验证', - labelNameEN: 'Authentication', - name: 'authentication', - required: true, - selects: [ - { - items: [ - { - defaultValue: 'root', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'User', - name: 'user', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ], - label: 'User&Password', - value: AuthenticationType.USERANDPASSWORD, - }, - { - label: 'NONE', - value: AuthenticationType.NONE, - }, - ], - styles: { - width: '50%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '数据库', - labelNameEN: 'Database', - name: 'database', - required: false, - styles: { - width: '100%', - } - }, - { - defaultValue: 'jdbc:hive2://localhost:10000', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:hive2://{host}:{port}/{database}', - }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, - extendInfo: [ - - ], - type: DatabaseTypeCode.HIVE - }, //hive { baseInfo: { 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 index a83bbfc3e..1ec2c7c65 100644 --- 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 @@ -11,7 +11,7 @@ public static DBConfig buildDBConfig() { dbConfig.setDbType("KINGBASE"); DriverConfig driverConfig = new DriverConfig(); driverConfig.setJdbcDriver("kingbase8-8.6.0.jar"); - driverConfig.setJdbcDriverClass("om.kingbase8.Driver"); + driverConfig.setJdbcDriverClass("com.kingbase8.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/kingbase8-8.6.0.jar")); driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 6e6802be9..9fed7670c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -4,17 +4,18 @@ import java.util.Map; import java.util.stream.Collectors; -import ai.chat2db.server.domain.api.param.*; +import ai.chat2db.server.domain.api.param.DatabaseOperationParam; +import ai.chat2db.server.domain.api.param.DatabaseQueryAllParam; +import ai.chat2db.server.domain.api.param.MetaDataQueryParam; +import ai.chat2db.server.domain.api.param.SchemaOperationParam; +import ai.chat2db.server.domain.api.param.SchemaQueryParam; import ai.chat2db.server.domain.api.service.DatabaseService; -import ai.chat2db.server.tools.base.wrapper.Result; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; - import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -42,11 +43,18 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); List databases = Chat2DBContext.getMetaData().databases(); if (!CollectionUtils.isEmpty(databases)) { - for (Database dataBase : databases) { - List schemaList = Chat2DBContext.getMetaData().schemas(dataBase.getName()); - dataBase.setSchemas(schemaList); + List schemaList = Chat2DBContext.getMetaData().schemas(null); + if (databases.size() == 1) { + databases.get(0).setSchemas(schemaList); + metaSchema.setDatabases(databases); + } else { + Map> schemaMap = schemaList.stream().collect( + Collectors.groupingBy(schema -> schema.getDatabaseName() != null ? schema.getDatabaseName() : "")); + for (Database dataBase : databases) { + dataBase.setSchemas(schemaMap.get(dataBase.getName())); + } + metaSchema.setDatabases(databases); } - metaSchema.setDatabases(databases); } else { List schemas = Chat2DBContext.getMetaData().schemas(null); metaSchema.setSchemas(schemas); @@ -87,7 +95,7 @@ public ActionResult createSchema(SchemaOperationParam param) { @Override public ActionResult modifySchema(SchemaOperationParam param) { Chat2DBContext.getDBManage().modifySchema(param.getDatabaseName(), param.getSchemaName(), - param.getNewSchemaName()); + param.getNewSchemaName()); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 1e758b1b7..7ec75babd 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -91,7 +91,11 @@ public ListResult execute(DlExecuteParam param) { pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); int offset = (pageNo - 1) * pageSize; - sql = PagerUtils.limit(sql, dbType, offset, pageSize); + try { + sql = PagerUtils.limit(sql, dbType, offset, pageSize); + }catch (Exception e){ + autoLimit = false; + } } sqlType = SqlTypeEnum.SELECT.getCode(); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 8813918a5..2629e23df 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -1,18 +1,21 @@ package ai.chat2db.spi.jdbc; -import java.lang.reflect.InvocationTargetException; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import ai.chat2db.spi.MetaData; -import ai.chat2db.spi.model.*; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; -import cn.hutool.json.JSON; -import com.google.common.collect.Lists; import org.apache.commons.beanutils.BeanUtils; -import org.springframework.util.CollectionUtils; /** * @author jipengfei @@ -22,9 +25,6 @@ public class DefaultMetaService implements MetaData { @Override public List databases() { List dataBases = SQLExecutor.getInstance().databases(); - if (CollectionUtils.isEmpty(dataBases)) { - return Lists.newArrayList(); - } return dataBases.stream().map(str -> Database.builder().name(str).build()).collect(Collectors.toList()); } @@ -32,9 +32,6 @@ public List databases() { @Override public List schemas(String databaseName) { List> maps = SQLExecutor.getInstance().schemas(databaseName, null); - if (CollectionUtils.isEmpty(maps)) { - return Lists.newArrayList(); - } return maps.stream().map(map -> map2Schema(map)).collect(Collectors.toList()); } 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 2f9740ae9..a88ebdd82 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 @@ -4,7 +4,6 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; -import java.util.List; import java.util.Map; import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; @@ -15,7 +14,6 @@ import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.SSHInfo; - import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; @@ -30,8 +28,6 @@ public class Chat2DBContext { private static final ThreadLocal CONNECT_INFO_THREAD_LOCAL = new ThreadLocal<>(); - public static List JDBC_JAR_DOWNLOAD_URL_LIST; - public static Map PLUGIN_MAP = new ConcurrentHashMap<>(); static { @@ -108,11 +104,11 @@ private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { connectInfo.setDriverConfig(config); } - connection = getConnect(url, host, port, connectInfo.getUser(), - connectInfo.getPassword(), connectInfo.getDbType(), - connectInfo.getDriverConfig(), ssh, connectInfo.getExtendMap()); + connection = IDriverManager.getConnection(url, connectInfo.getUser(), connectInfo.getPassword(), + connectInfo.getDriverConfig(), connectInfo.getExtendMap()); + } catch (Exception e1) { - log.error("getConnect error", e1); + log.error("GetConnect error", e1); if (connection != null) { try { connection.close(); @@ -132,29 +128,12 @@ private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { log.error("session disconnect error", e); } } - throw new RuntimeException("getConnect error", e1); + throw new RuntimeException("GetConnect error", e1); } connectInfo.setSession(session); connectInfo.setConnection(connection); } - /** - * 测试数据库连接 - * - * @param url 数据库连接 - * @param userName 用户名 - * @param password 密码 - * @param dbType 数据库类型 - * @return - */ - private static Connection getConnect(String url, String host, String port, - String userName, String password, String dbType, - DriverConfig jdbc, SSHInfo ssh, Map properties) throws SQLException { - // 创建连接 - return IDriverManager.getConnection(url, userName, password, jdbc, properties); - - } - private static Session getSession(SSHInfo ssh) { Session session = null; if (ssh != null && ssh.isUse()) { @@ -177,15 +156,6 @@ public static void removeContext() { } catch (SQLException e) { log.error("close connection error", e); } - //Session session = connectInfo.getSession(); - //if (session != null) { - // try { - // session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); - // session.disconnect(); - // } catch (JSchException e) { - // log.error("close session error", e); - // } - //} CONNECT_INFO_THREAD_LOCAL.remove(); } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 4321dc564..6c1a65275 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -181,6 +181,21 @@ public List databases() { */ public List> schemas(String databaseName, String schemaName) { List> schemaList = Lists.newArrayList(); + if(StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)){ + try (ResultSet resultSet = getConnection().getMetaData().getSchemas()) { + if (resultSet != null) { + while (resultSet.next()) { + Map map = new HashMap<>(); + map.put("name", resultSet.getString("TABLE_SCHEM")); + map.put("databaseName", resultSet.getString("TABLE_CATALOG")); + schemaList.add(map); + } + } + } catch (SQLException e) { + throw new RuntimeException("Get schemas error",e); + } + return schemaList; + } try (ResultSet resultSet = getConnection().getMetaData().getSchemas(databaseName, schemaName)) { if (resultSet != null) { while (resultSet.next()) { @@ -191,7 +206,7 @@ public List> schemas(String databaseName, String schemaName) } } } catch (SQLException e) { - throw new RuntimeException(e); + throw new RuntimeException("Get schemas error",e); } return schemaList; } From 19d2a9a33b7c6df7152317a950115a5dbf73f888 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 5 Jul 2023 23:02:26 +0800 Subject: [PATCH 0241/1069] =?UTF-8?q?feat:yarn=20run=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1ea9462bf..e1c81a13f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "monaco", "nsis", "pgsql", + "pnpm", "remaininguses", "sortablejs", "togglefullscreen", From 36515172da482270b28dcdca29ca64dfd741063b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 6 Jul 2023 08:48:29 +0800 Subject: [PATCH 0242/1069] =?UTF-8?q?fix:=E6=89=93=E5=8C=85=E5=91=BD?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 4 ++-- .github/workflows/release_test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 840a8727b..18ef4b074 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,7 +120,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:prod ${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -135,7 +135,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop --app_version=${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:desktop ${{ steps.chat2db_version.outputs.substring }} # windows - name: Build/release Electron app for Windows diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 2b59a9a49..d7eedbfb6 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -106,7 +106,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --app_version=1.0.${{ github.run_id }} --app_port=10822 + yarn run build:web:prod 1.0.${{ github.run_id }} 10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -121,7 +121,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop --app_version=1.0.${{ github.run_id }} --app_port=10822 + yarn run build:web:desktop 1.0.${{ github.run_id }} 10822 # windows - name: Build/release Electron app for Windows From 045cbc3f90fb5116b90abaa3004d6438a12f8f2f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 6 Jul 2023 21:56:04 +0800 Subject: [PATCH 0243/1069] =?UTF-8?q?fix:=E8=8E=B7=E5=8F=96yarn=E6=89=93?= =?UTF-8?q?=E5=8C=85=E6=98=AF=E5=91=BD=E4=BB=A4=E8=A1=8C=E4=BC=A0=E5=85=A5?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 4 +-- .github/workflows/release_test.yml | 4 +-- .vscode/settings.json | 1 + chat2db-client/.umirc.ts | 10 +++--- chat2db-client/src/utils/webpack.ts | 51 +++++++++++++++++++++++++++++ 5 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 chat2db-client/src/utils/webpack.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18ef4b074..840a8727b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -120,7 +120,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod ${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -135,7 +135,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop ${{ steps.chat2db_version.outputs.substring }} + yarn run build:web:desktop --app_version=${{ steps.chat2db_version.outputs.substring }} # windows - name: Build/release Electron app for Windows diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index d7eedbfb6..2b59a9a49 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -106,7 +106,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod 1.0.${{ github.run_id }} 10822 + yarn run build:web:prod --app_version=1.0.${{ github.run_id }} --app_port=10822 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -121,7 +121,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop 1.0.${{ github.run_id }} 10822 + yarn run build:web:desktop --app_version=1.0.${{ github.run_id }} --app_port=10822 # windows - name: Build/release Electron app for Windows diff --git a/.vscode/settings.json b/.vscode/settings.json index e1c81a13f..89ee4fa0e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "sortablejs", "togglefullscreen", "umijs", + "umirc", "wechat", "wireframe", "yapi" diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index b545daedd..3ac30dc25 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -1,8 +1,10 @@ -import { transitionTimezoneTimestamp } from './src/utils/date'; +import { extractYarnConfig, transitionTimezoneTimestamp } from './src/utils/webpack'; import { defineConfig } from 'umi'; -import { getLang } from '@/utils/localStorage'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); +// yarn run build --app_port=xx 获取打包时命令行传入的参数 +const yarn_config = extractYarnConfig(process.argv); + const chainWebpack = (config: any, { webpack }: any) => { config.plugin('monaco-editor').use(MonacoWebpackPlugin, [ { @@ -52,7 +54,7 @@ export default defineConfig({ define: { __ENV__: process.env.UMI_ENV, __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), - __APP_VERSION__: process.argv[3]|| '0.0.0', - __APP_PORT__: process.argv[4] + __APP_VERSION__: yarn_config.app_version || '0.0.0', + __APP_PORT__: yarn_config.app_port }, }); diff --git a/chat2db-client/src/utils/webpack.ts b/chat2db-client/src/utils/webpack.ts new file mode 100644 index 000000000..5b7d26b3f --- /dev/null +++ b/chat2db-client/src/utils/webpack.ts @@ -0,0 +1,51 @@ +// .umirc里无法识别到@/xxxx 所以单独开了webpack.ts,这个文件只可以写函数,不可以引入其他组件 + +// 分割出yarn启动命令里添加的参数 +export function extractYarnConfig(argv: string[]){ + const newArgv = argv.slice(2) + const yarn_config:{[k in string]: string} = {} + newArgv.forEach(t=>{ + if(t && t.startsWith("--")){ + const regex = /--(.+?)=(.+)/; + const matches = t.match(regex); + if (matches) { + const key = matches[1]; + const value = matches[2]; + yarn_config[key] = value + } + } + }) + return yarn_config +} + +export function formatDate(date: any, fmt = 'yyyy-MM-dd') { + if (!date) { + return ''; + } + if (typeof date == 'number' || typeof date == 'string') { + date = new Date(date); + } + if (!(date instanceof Date) || isNaN(date.getTime())) { + return ''; + } + var o: any = { + 'M+': date.getMonth() + 1, + 'd+': date.getDate(), + 'h+': date.getHours(), + 'm+': date.getMinutes(), + 's+': date.getSeconds(), + 'q+': Math.floor((date.getMonth() + 3) / 3), + S: date.getMilliseconds(), + }; + if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length)); + for (var k in o) + if (new RegExp('(' + k + ')').test(fmt)) + fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)); + return fmt; +} + +// 带有时区的时间戳转换为0时区时间戳 +export function transitionTimezoneTimestamp(timestamp: number) { + const timezoneOffset = new Date().getTimezoneOffset() * 60 * 1000 + return timestamp + timezoneOffset +} From 0f422bc1d6fdc54dbf17faee5866a056d201a264 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 6 Jul 2023 23:05:41 +0800 Subject: [PATCH 0244/1069] =?UTF-8?q?style:=E8=A1=A8=E6=A0=BC=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/SearchResult/TableBox.less | 20 ++++++++++++++----- .../src/components/SearchResult/TableBox.tsx | 13 ++++++------ .../src/components/SearchResult/index.less | 6 +++++- .../src/components/StateIndicator/index.less | 1 + .../src/components/StateIndicator/index.tsx | 10 ++++++---- 5 files changed, 34 insertions(+), 16 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index ca6264886..7a21f69b9 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -8,7 +8,21 @@ bottom: 0; display: none; overflow: auto; - background-color: var(--color-bg-elevated); + :global{ + // .hGEOgo td { + // border-bottom: 0; + // // height: 400px !important; + // } + .hGEOgo td.first{ + border-left: 0; + } + .hGEOgo tr.first th{ + border-top: 0; + } + .hGEOgo th.first{ + border-left: 0; + } + } } .statusBar { @@ -70,7 +84,3 @@ height: 400px; margin: -15px; } - -.table { - --border-color: transparent; -} diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index 8b7a226c7..e9914da86 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -25,20 +25,20 @@ interface IViewTableCellData { name: string; value: any; } -// --bgcolor: #333; -// --header-bgcolor: #45494f; + const DarkSupportBaseTable: any = styled(BaseTable)` &.dark { - --bgcolor: #131418; - --header-bgcolor: #0a0b0c; + --bgcolor: var(--color-bg-base); + --header-bgcolor: var(--color-bg-elevated); --hover-bgcolor: #46484a; --header-hover-bgcolor: #606164; --highlight-bgcolor: #191a1b; --header-highlight-bgcolor: #191a1b; - --color: #dadde1; + --color: var(--color-text); --header-color: #dadde1; --lock-shadow: rgb(37 37 37 / 0.5) 0 0 6px 2px; - --border-color: transparent; + --border-color: var(--color-border-secondary); + --hover-bgcolor: var(--color-hover-bg); } `; @@ -159,6 +159,7 @@ export default function TableBox(props: ITableProps) { <>

{i18n('common.text.noData') }

}} {...pipeline.getProps()} />
{`${i18n('common.text.result')}:${description}. ${i18n( diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 8cf15ab6f..f8880aaac 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -96,5 +96,9 @@ } .cursorStateIndicator{ - display: block; + margin: 0 auto; + max-width: 80%; + display: flex; + align-items: center; + justify-content: center; } \ No newline at end of file diff --git a/chat2db-client/src/components/StateIndicator/index.less b/chat2db-client/src/components/StateIndicator/index.less index 41537fb3c..d4c42f26b 100644 --- a/chat2db-client/src/components/StateIndicator/index.less +++ b/chat2db-client/src/components/StateIndicator/index.less @@ -38,6 +38,7 @@ color: var(--error-color); text-align: center; transform: translateY(-20px); + white-space: pre-wrap; } .successBox { diff --git a/chat2db-client/src/components/StateIndicator/index.tsx b/chat2db-client/src/components/StateIndicator/index.tsx index 06c603d75..507fdb4fb 100644 --- a/chat2db-client/src/components/StateIndicator/index.tsx +++ b/chat2db-client/src/components/StateIndicator/index.tsx @@ -9,6 +9,7 @@ interface IProps { className?: string; state: 'loading' | 'empty' | 'error' | 'success'; text?: string; + image?: boolean; } export const enum State { @@ -33,7 +34,7 @@ const config = { }, } -export default memo(function StateIndicator({ className, state, text }) { +export default memo(function StateIndicator({ className, state, text, image = false }) { const renderState = () => { switch (state) { @@ -41,16 +42,17 @@ export default memo(function StateIndicator({ className, state, text }) return ; case 'error': return
-
+ {image &&
}
{text}
case 'success': return
-
+ {image &&
}
{text}
default: - return
+ return
+ } } return
From fcf6fb19447f66a9e00029e368d77563793709a3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 7 Jul 2023 23:47:52 +0800 Subject: [PATCH 0245/1069] =?UTF-8?q?fix=EF=BC=9Abug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/pages/main/connection/index.tsx | 2 +- .../main/workspace/components/Tree/TreeNodeRightClick/index.tsx | 2 +- .../src/pages/main/workspace/components/Tree/treeConfig.tsx | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 03bdd2b64..e15d1f8fe 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -53,7 +53,7 @@ function Connections(props: IProps) { () => (connectionList || []).map((t) => ({ key: t.id, - icon: , + icon: , label: t.alias, meta: t, })), diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 1ce799aa4..17b2b64e6 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -227,7 +227,7 @@ function TreeNodeRightClick(props: IProps) { return { key: i, label:
- +
{concrete.text}
diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index 9baeda6b2..4b71177f9 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -51,7 +51,6 @@ export enum OperationColumn { ExportDDL = 'exportDDL', EditSource = 'editSource', Top = 'top' - } export interface ITreeConfigItem { From 6d920353f0bb9439133a6a46ac225cca03596222 Mon Sep 17 00:00:00 2001 From: robinji0 <850379744@qq.com> Date: Sat, 8 Jul 2023 15:23:04 +0800 Subject: [PATCH 0246/1069] exception optimization --- .../core/impl/DataSourceServiceImpl.java | 2 +- .../config/Chat2dbWebMvcConfigurer.java | 2 +- .../convertor/BindExceptionConvertor.java | 3 +- .../convertor/BusinessExceptionConvertor.java | 3 +- .../convertor/DefaultExceptionConvertor.java | 3 +- ...xUploadSizeExceededExceptionConvertor.java | 3 +- ...hodArgumentNotValidExceptionConvertor.java | 3 +- ...rgumentTypeMismatchExceptionConvertor.java | 3 +- .../convertor/ParamExceptionConvertor.java | 3 +- .../server/tools/base/wrapper/Result.java | 24 ++++++++++++ .../base/wrapper/result/ActionResult.java | 39 +++++++++++++++++-- .../tools/base/wrapper/result/DataResult.java | 30 ++++++++++++++ .../tools/base/wrapper/result/ListResult.java | 30 ++++++++++++++ .../tools/base/wrapper/result/PageResult.java | 30 ++++++++++++++ .../wrapper/result/web/WebPageResult.java | 30 ++++++++++++++ .../web/api/controller/ai/ChatController.java | 3 +- .../chat2db/spi/model/DataSourceConnect.java | 5 +++ .../ai/chat2db/spi/util/ExceptionUtils.java | 31 +++++++++++++++ .../java/ai/chat2db/spi/util/JdbcUtils.java | 1 + 19 files changed, 235 insertions(+), 13 deletions(-) create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java 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 143d876d1..159e8ab63 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 @@ -128,7 +128,7 @@ public ActionResult preConnect(DataSourcePreConnectParam param) { testParam.getUsername(), testParam.getPassword(), testParam.getDbType(), driverConfig, param.getSsh(), KeyValue.toMap(param.getExtendInfo())); if (BooleanUtils.isNotTrue(dataSourceConnect.getSuccess())) { - return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription()); + return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription(), dataSourceConnect.getErrorDetail()); } return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index 1fea3855c..aab7ed6d4 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -106,7 +106,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl String path = SaHolder.getRequest().getRequestPath(); if (path.startsWith(API_PREFIX)) { response.getWriter().println(JSON.toJSONString( - ActionResult.fail("common.needLoggedIn", I18nUtils.getMessage("common.needLoggedIn")))); + ActionResult.fail("common.needLoggedIn", I18nUtils.getMessage("common.needLoggedIn"), ""))); return false; } else { throw new RedirectBusinessException( diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BindExceptionConvertor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BindExceptionConvertor.java index 3338f944d..7472f8dfa 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BindExceptionConvertor.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BindExceptionConvertor.java @@ -1,6 +1,7 @@ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.validation.BindException; /** @@ -13,6 +14,6 @@ public class BindExceptionConvertor implements ExceptionConvertor @Override public ActionResult convert(BindException exception) { String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult()); - return ActionResult.fail("common.paramError", message); + return ActionResult.fail("common.paramError", message, ExceptionUtils.getErrorInfoFromException(exception)); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BusinessExceptionConvertor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BusinessExceptionConvertor.java index cef7e3aa2..6c031acaa 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BusinessExceptionConvertor.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/BusinessExceptionConvertor.java @@ -3,6 +3,7 @@ import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.spi.util.ExceptionUtils; /** * BusinessException @@ -13,6 +14,6 @@ public class BusinessExceptionConvertor implements ExceptionConvertor @Override public ActionResult convert(Throwable exception) { - return ActionResult.fail("common.systemError", I18nUtils.getMessage("common.systemError")); + return ActionResult.fail("common.systemError", I18nUtils.getMessage("common.systemError"), ExceptionUtils.getErrorInfoFromException(exception)); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MaxUploadSizeExceededExceptionConvertor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MaxUploadSizeExceededExceptionConvertor.java index 814638ab6..b7b160a88 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MaxUploadSizeExceededExceptionConvertor.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MaxUploadSizeExceededExceptionConvertor.java @@ -3,6 +3,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.multipart.MaxUploadSizeExceededException; /** @@ -14,6 +15,6 @@ public class MaxUploadSizeExceededExceptionConvertor implements ExceptionConvert @Override public ActionResult convert(MaxUploadSizeExceededException exception) { - return ActionResult.fail("common.maxUploadSize", I18nUtils.getMessage("common.maxUploadSize")); + return ActionResult.fail("common.maxUploadSize", I18nUtils.getMessage("common.maxUploadSize"), ExceptionUtils.getErrorInfoFromException(exception)); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentNotValidExceptionConvertor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentNotValidExceptionConvertor.java index 98a416ab0..8576ec751 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentNotValidExceptionConvertor.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentNotValidExceptionConvertor.java @@ -2,6 +2,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.bind.MethodArgumentNotValidException; /** @@ -14,6 +15,6 @@ public class MethodArgumentNotValidExceptionConvertor implements ExceptionConver @Override public ActionResult convert(MethodArgumentNotValidException exception) { String message = ExceptionConvertorUtils.buildMessage(exception.getBindingResult()); - return ActionResult.fail("common.paramError", message); + return ActionResult.fail("common.paramError", message, ExceptionUtils.getErrorInfoFromException(exception)); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentTypeMismatchExceptionConvertor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentTypeMismatchExceptionConvertor.java index f1389afc7..31be931fb 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentTypeMismatchExceptionConvertor.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/MethodArgumentTypeMismatchExceptionConvertor.java @@ -3,6 +3,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.spi.util.ExceptionUtils; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; /** @@ -15,6 +16,6 @@ public class MethodArgumentTypeMismatchExceptionConvertor @Override public ActionResult convert(MethodArgumentTypeMismatchException exception) { - return ActionResult.fail("common.paramError", I18nUtils.getMessage("common.paramError")); + return ActionResult.fail("common.paramError", I18nUtils.getMessage("common.paramError"), ExceptionUtils.getErrorInfoFromException(exception)); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ParamExceptionConvertor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ParamExceptionConvertor.java index 1737cf23e..28f0e8e85 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ParamExceptionConvertor.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/exception/convertor/ParamExceptionConvertor.java @@ -1,6 +1,7 @@ package ai.chat2db.server.start.exception.convertor; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.spi.util.ExceptionUtils; /** * 参数异常 目前包括 @@ -14,6 +15,6 @@ public class ParamExceptionConvertor implements ExceptionConvertor { @Override public ActionResult convert(Throwable exception) { - return ActionResult.fail("common.paramError", exception.getMessage()); + return ActionResult.fail("common.paramError", exception.getMessage(), ExceptionUtils.getErrorInfoFromException(exception)); } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/Result.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/Result.java index 0deddcef6..e63227020 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/Result.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/Result.java @@ -49,4 +49,28 @@ public interface Result extends Traceable{ * @param errorMessage */ void errorMessage(String errorMessage); + + /** + * error detail stack info + */ + void errorDetail(String errorDetail); + + /** + * error detail + * + * @return + */ + String errorDetail(); + + /** + * solution link + */ + void solutionLink(String solutionLink); + + /** + * solution link + * + * @return + */ + String solutionLink(); } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java index 2657105a5..a8003037a 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java @@ -41,6 +41,16 @@ public class ActionResult implements Serializable, Result { */ private String errorMessage; + /** + * error detail + */ + private String errorDetail; + + /** + * solution link + */ + private String solutionLink; + /** * traceId */ @@ -89,18 +99,41 @@ public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } + @Override + public void errorDetail(String errorDetail) { + this.errorDetail = errorDetail; + } + + @Override + public String errorDetail() { + return errorDetail; + } + + @Override + public void solutionLink(String solutionLink) { + this.solutionLink = solutionLink; + } + + @Override + public String solutionLink() { + return solutionLink; + } + /** * 返回失败 * - * @param errorCode 错误编码 - * @param errorMessage 错误信息 + * @param errorCode error code + * @param errorMessage error message + * @param errorDetail error detail * @return 运行结果 */ - public static ActionResult fail(String errorCode, String errorMessage) { + public static ActionResult fail(String errorCode, String errorMessage, String errorDetail) { ActionResult result = new ActionResult(); result.errorCode = errorCode; result.errorMessage = errorMessage; result.success = Boolean.FALSE; + result.solutionLink("https://github.com/chat2db/Chat2DB/wiki/Chat2DB"); + result.errorDetail(errorDetail); return result; } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/DataResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/DataResult.java index 284d1a115..776905d51 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/DataResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/DataResult.java @@ -42,6 +42,16 @@ public class DataResult implements Serializable, Result { */ private String errorMessage; + /** + * error detail + */ + private String errorDetail; + + /** + * solution link + */ + private String solutionLink; + /** * 数据信息 */ @@ -156,4 +166,24 @@ public String errorMessage() { public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } + + @Override + public void errorDetail(String errorDetail) { + this.errorDetail = errorDetail; + } + + @Override + public String errorDetail() { + return errorDetail; + } + + @Override + public void solutionLink(String solutionLink) { + this.solutionLink = solutionLink; + } + + @Override + public String solutionLink() { + return solutionLink; + } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ListResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ListResult.java index 585173afc..203e0f364 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ListResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ListResult.java @@ -51,6 +51,16 @@ public class ListResult implements Serializable, Result { */ private String traceId; + /** + * error detail + */ + private String errorDetail; + + /** + * solution link + */ + private String solutionLink; + public ListResult() { this.success = Boolean.TRUE; } @@ -156,4 +166,24 @@ public String errorMessage() { public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } + + @Override + public void errorDetail(String errorDetail) { + this.errorDetail = errorDetail; + } + + @Override + public String errorDetail() { + return errorDetail; + } + + @Override + public void solutionLink(String solutionLink) { + this.solutionLink = solutionLink; + } + + @Override + public String solutionLink() { + return solutionLink; + } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java index 76c4bf833..65e55e7e9 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java @@ -68,6 +68,16 @@ public class PageResult implements Serializable, Result> { */ private Boolean hasNextPage; + /** + * error detail + */ + private String errorDetail; + + /** + * solution link + */ + private String solutionLink; + public PageResult() { this.pageNo = 1; this.pageSize = 10; @@ -321,4 +331,24 @@ public String errorMessage() { public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } + + @Override + public void errorDetail(String errorDetail) { + this.errorDetail = errorDetail; + } + + @Override + public String errorDetail() { + return errorDetail; + } + + @Override + public void solutionLink(String solutionLink) { + this.solutionLink = solutionLink; + } + + @Override + public String solutionLink() { + return solutionLink; + } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java index 842401c02..bfa670229 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/web/WebPageResult.java @@ -51,6 +51,16 @@ public class WebPageResult implements Serializable, Result> { */ private String traceId; + /** + * error detail + */ + private String errorDetail; + + /** + * solution link + */ + private String solutionLink; + public WebPageResult() { this.success = Boolean.TRUE; this.data = new Page<>(); @@ -232,6 +242,26 @@ public void errorMessage(String errorMessage) { this.errorMessage = errorMessage; } + @Override + public void errorDetail(String errorDetail) { + this.errorDetail = errorDetail; + } + + @Override + public String errorDetail() { + return errorDetail; + } + + @Override + public void solutionLink(String solutionLink) { + this.solutionLink = solutionLink; + } + + @Override + public String solutionLink() { + return solutionLink; + } + /** * 分页信息 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 6e056e958..7dfe44e58 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -521,6 +521,7 @@ private String buildPrompt(ChatQueryRequest queryRequest) { //if (I18nUtils.isEn()) { // schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty); //} - return schemaProperty; + String result = String.format("%s. \n要求返回Markdown格式", schemaProperty); + return result; } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DataSourceConnect.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DataSourceConnect.java index 815fb5dd1..18b4b2281 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DataSourceConnect.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/DataSourceConnect.java @@ -31,4 +31,9 @@ public class DataSourceConnect { * 描述 */ private String description; + + /** + * error detail + */ + private String errorDetail; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java new file mode 100644 index 000000000..0e0625b8b --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java @@ -0,0 +1,31 @@ +package ai.chat2db.spi.util; + +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * exception utils + */ +public class ExceptionUtils { + + /** + * print stack trace + * + * @param throwable + * @return + */ + public static String getErrorInfoFromException(Throwable throwable) { + String errorDetail = ""; + try { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + errorDetail = "\r\n" + sw.toString() + "\r\n"; + sw.close(); + pw.close(); + } catch (Exception e2) { + return "ErrorInfoFromException"; + } + return errorDetail; + } +} 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 e64180af5..b79e6ca61 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 @@ -223,6 +223,7 @@ public static DataSourceConnect testConnect(String url, String host, String port t = t.getCause(); } dataSourceConnect.setMessage(t.getMessage()); + dataSourceConnect.setErrorDetail(ExceptionUtils.getErrorInfoFromException(t)); return dataSourceConnect; } finally { if (connection != null) { From e414c0092128f771aeb15c3d59f246efc8153756 Mon Sep 17 00:00:00 2001 From: robinji0 <850379744@qq.com> Date: Sat, 8 Jul 2023 15:53:56 +0800 Subject: [PATCH 0247/1069] return password --- .../web/api/controller/data/source/vo/DataSourceVO.java | 5 +++++ 1 file changed, 5 insertions(+) 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 2b8222740..5b939c1b1 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 @@ -36,6 +36,11 @@ public class DataSourceVO { */ private String user; + /** + * password + */ + private String password; + /** * 连接类型 */ From 63b5a17cb02c24033b9cbe41a88e58bf4535fa98 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 8 Jul 2023 16:22:18 +0800 Subject: [PATCH 0248/1069] =?UTF-8?q?=E6=94=AF=E6=8C=81=E8=87=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugin/mysql/builder/DBConfigBuilder.java | 2 - .../clickhouse/builder/DBConfigBuilder.java | 1 - .../plugin/db2/builder/DBConfigBuilder.java | 1 - .../plugin/dm/builder/DBConfigBuilder.java | 1 - .../plugin/h2/builder/DBConfigBuilder.java | 1 - .../plugin/hive/builder/DBConfigBuilder.java | 1 - .../kingbase/builder/DBConfigBuilder.java | 1 - .../mariadb/builder/DBConfigBuilder.java | 1 - .../mongodb/builder/DBConfigBuilder.java | 1 - .../oceanbase/builder/DBConfigBuilder.java | 1 - .../oracle/builder/DBConfigBuilder.java | 1 - .../postgresql/builder/DBConfigBuilder.java | 1 - .../presto/builder/DBConfigBuilder.java | 1 - .../plugin/redis/builder/DBConfigBuilder.java | 1 - .../sqlite/builder/DBConfigBuilder.java | 1 - .../builder/SqlServerDBConfigBuilder.java | 1 - .../server/domain/api/model/DataSource.java | 12 +- .../api/param/DataSourceCreateParam.java | 6 + .../api/param/DataSourcePreConnectParam.java | 13 ++- .../api/param/DataSourceUpdateParam.java | 12 +- .../api/param/DatabaseQueryAllParam.java | 5 + .../domain/api/param/MetaDataQueryParam.java | 5 + .../domain/api/param/SchemaQueryParam.java | 7 ++ .../domain/api/service/JdbcDriverService.java | 34 ++++++ .../chat2db-server-domain-core/pom.xml | 12 +- .../server/domain/core/cache/CacheManage.java | 55 +++++++++ .../core/converter/DataSourceConverter.java | 15 ++- .../domain/core/impl/DatabaseServiceImpl.java | 37 +++++- .../core/impl/JdbcDriverServiceImpl.java | 110 ++++++++++++++++++ .../repository/entity/DataSourceDO.java | 9 +- .../repository/entity/JdbcDriverDO.java | 49 ++++++++ .../repository/mapper/JdbcDriverMapper.java | 12 ++ .../domain/repository/JdbcDriverMapper.xml | 5 + .../ai/chat2db/server/start/Application.java | 2 + .../src/main/resources/application.yml | 23 +--- ...2\344\271\211\351\251\261\345\212\250.sql" | 13 +++ .../chat2db-server-web-api/pom.xml | 8 ++ .../web/api/controller/SystemController.java | 6 +- .../request/DataSourceCreateRequest.java | 7 ++ .../source/request/DataSourceTestRequest.java | 6 + .../request/DataSourceUpdateRequest.java | 6 + .../data/source/vo/DataSourceVO.java | 15 ++- .../driver/JdbcDriverController.java | 95 +++++++++++++++ .../ai/chat2db/spi/config/DriverConfig.java | 15 ++- .../java/ai/chat2db/spi/model/Database.java | 9 +- .../java/ai/chat2db/spi/model/KeyValue.java | 3 +- .../java/ai/chat2db/spi/model/MetaSchema.java | 9 +- .../java/ai/chat2db/spi/model/SSLInfo.java | 3 + .../java/ai/chat2db/spi/model/Schema.java | 6 +- .../ai/chat2db/spi/sql/Chat2DBContext.java | 108 +++++++++-------- .../ai/chat2db/spi/sql/IDriverManager.java | 10 +- .../ai/chat2db/spi/util/JdbcJarUtils.java | 2 +- chat2db-server/pom.xml | 15 +++ 53 files changed, 643 insertions(+), 133 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/JdbcDriverDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/JdbcDriverMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/JdbcDriverMapper.xml create mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_7__\350\207\252\345\256\232\344\271\211\351\251\261\345\212\250.sql" create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java index bbaad43ff..68087ab15 100644 --- a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java @@ -15,7 +15,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("mysql-connector-java-8.0.30.jar"); driverConfig.setJdbcDriverClass("com.mysql.cj.jdbc.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/mysql-connector-java-8.0.30.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); @@ -23,7 +22,6 @@ public static DBConfig buildDBConfig() { driverConfig2.setJdbcDriver("mysql-connector-java-5.1.47.jar"); driverConfig2.setJdbcDriverClass("com.mysql.jdbc.Driver"); driverConfig2.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/mysql-connector-java-5.1.47.jar")); - driverConfig2.setName(driverConfig2.getJdbcDriver() + ":" + driverConfig2.getJdbcDriverClass()); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig,driverConfig2)); return dbConfig; 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 index 3b7df4576..8537c1d0a 100644 --- 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 @@ -14,7 +14,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("clickhouse-jdbc-0.3.2-patch8-http.jar"); driverConfig.setJdbcDriverClass("com.clickhouse.jdbc.ClickHouseDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/clickhouse-jdbc-0.3.2-patch8-http.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); 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 index 32d12d655..8dbd61cf1 100644 --- 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 @@ -14,7 +14,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("db2jcc4_4.26.14.jar"); driverConfig.setJdbcDriverClass("com.ibm.db2.jcc.DB2Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/db2jcc4_4.26.14.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); 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 index 2e67fe78c..0a5673961 100644 --- 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 @@ -15,7 +15,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("DmJdbcDriver18-8.1.2.141.jar"); driverConfig.setJdbcDriverClass("dm.jdbc.driver.DmDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/DmJdbcDriver18-8.1.2.141.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); 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 index 627bde64d..6b68970fb 100644 --- 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 @@ -15,7 +15,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("h2-2.1.214.jar"); driverConfig.setJdbcDriverClass("org.h2.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/h2-2.1.214.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); 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 index 6e09f1cdd..960a091a1 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("hive-jdbc-3.1.2-standalone.jar"); driverConfig.setJdbcDriverClass("org.apache.hive.jdbc.HiveDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/hive-jdbc-3.1.2-standalone.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); return dbConfig; 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 index 1ec2c7c65..7b0a01619 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("kingbase8-8.6.0.jar"); driverConfig.setJdbcDriverClass("com.kingbase8.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/kingbase8-8.6.0.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/builder/DBConfigBuilder.java index e7612716f..6159cc36d 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("mariadb-java-client-3.0.8.jar"); driverConfig.setJdbcDriverClass("org.mariadb.jdbc.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/mariadb-java-client-3.0.8.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/builder/DBConfigBuilder.java index 9d3fa652b..e79269e0d 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("mongo-jdbc-standalone-1.18.jar"); driverConfig.setJdbcDriverClass("com.dbschema.MongoJdbcDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/mongo-jdbc-standalone-1.18.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/builder/DBConfigBuilder.java index a970c3a65..94ccc4d66 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("oceanbase-client-2.4.2.jar"); driverConfig.setJdbcDriverClass("com.oceanbase.jdbc.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/oceanbase-client-2.4.2.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/DBConfigBuilder.java index bee5a66f7..cd113fae3 100644 --- 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 @@ -15,7 +15,6 @@ public static DBConfig buildDBConfig() { 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-chat2db.alibaba.com/lib/ojdbc8-19.3.0.0.jar", "https://oss-chat2db.alibaba.com/lib/orai18n-19.3.0.0.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); 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 index adcf1a535..9d8ed2c1e 100644 --- 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 @@ -14,7 +14,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("postgresql-42.5.1.jar"); driverConfig.setJdbcDriverClass("org.postgresql.Driver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/postgresql-42.5.1.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/builder/DBConfigBuilder.java index 399eadf0d..3fb4b3059 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("presto-jdbc-0.245.1.jar"); driverConfig.setJdbcDriverClass("com.facebook.presto.jdbc.PrestoDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/presto-jdbc-0.245.1.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/builder/DBConfigBuilder.java index e7166fa6b..44bcab511 100644 --- 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 @@ -13,7 +13,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("redis-jdbc-driver-1.3.jar"); driverConfig.setJdbcDriverClass("jdbc.RedisDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/redis-jdbc-driver-1.3.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/DBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/DBConfigBuilder.java index 756760534..a57e2fb7d 100644 --- 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 @@ -14,7 +14,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("sqlite-jdbc-3.39.3.0.jar"); driverConfig.setJdbcDriverClass("org.sqlite.JDBC"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/sqlite-jdbc-3.39.3.0.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); 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/builder/SqlServerDBConfigBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerDBConfigBuilder.java index c3fbc6b91..fea95d996 100644 --- 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 @@ -14,7 +14,6 @@ public static DBConfig buildDBConfig() { driverConfig.setJdbcDriver("mssql-jdbc-11.2.1.jre17.jar"); driverConfig.setJdbcDriverClass("com.microsoft.sqlserver.jdbc.SQLServerDriver"); driverConfig.setDownloadJdbcDriverUrls(Lists.newArrayList("https://oss-chat2db.alibaba.com/lib/mssql-jdbc-11.2.1.jre17.jar")); - driverConfig.setName(driverConfig.getJdbcDriver() + ":" + driverConfig.getJdbcDriverClass()); dbConfig.setDefaultDriverConfig(driverConfig); dbConfig.setDriverConfigList(Lists.newArrayList(driverConfig)); return dbConfig; 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 726c32fe5..0dd476359 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 @@ -3,9 +3,10 @@ import java.time.LocalDateTime; import java.util.List; - -import ai.chat2db.spi.model.*; - +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; import lombok.Data; /** @@ -101,4 +102,9 @@ public class DataSource { * 扩展信息 */ private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceCreateParam.java index a08e4aa87..d34e24ebf 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceCreateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceCreateParam.java @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; @@ -88,4 +89,9 @@ public class DataSourceCreateParam { */ private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourcePreConnectParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourcePreConnectParam.java index ddee5290a..d6c883ef6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourcePreConnectParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourcePreConnectParam.java @@ -1,12 +1,12 @@ package ai.chat2db.server.domain.api.param; import java.util.List; -import java.util.Map; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; - -import ai.chat2db.spi.model.*; - import lombok.Data; /** @@ -86,4 +86,9 @@ public class DataSourcePreConnectParam { * 扩展信息 */ private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceUpdateParam.java index ae04227b9..81b147464 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceUpdateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceUpdateParam.java @@ -1,12 +1,12 @@ package ai.chat2db.server.domain.api.param; import java.util.List; -import java.util.Map; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; - -import ai.chat2db.spi.model.*; - import lombok.Data; /** @@ -96,4 +96,8 @@ public class DataSourceUpdateParam { */ private List extendInfo; + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseQueryAllParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseQueryAllParam.java index 297b4b4e5..fcd1d197c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseQueryAllParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DatabaseQueryAllParam.java @@ -22,4 +22,9 @@ public class DatabaseQueryAllParam { */ @NotNull private Long dataSourceId; + + /** + * if true, refresh the cache + */ + private boolean refresh; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/MetaDataQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/MetaDataQueryParam.java index 86c0d561a..609b46321 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/MetaDataQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/MetaDataQueryParam.java @@ -15,4 +15,9 @@ public class MetaDataQueryParam { @NotNull private Long dataSourceId; + + /** + * if true, refresh the cache + */ + private boolean refresh; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java index fae9d8067..b450b527e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SchemaQueryParam.java @@ -22,4 +22,11 @@ public class SchemaQueryParam { private Long dataSourceId; private String dataBaseName; + + + + /** + * if true, refresh the cache + */ + private boolean refresh; } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java new file mode 100644 index 000000000..1881cdd0a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/JdbcDriverService.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.spi.config.DBConfig; + +public interface JdbcDriverService { + + /** + * Query the driver list of the current DB + * + * @param dbType + * @return + */ + DataResult getDrivers(String dbType); + + /** + * Upload the driver + * + * @param dbType + * @param jdbcDriverClass + * @param jdbcDriver + * @return + */ + ActionResult upload(String dbType, String jdbcDriverClass, String jdbcDriver); + + /** + * Upload the driver + * + * @param dbType + * @return + */ + ActionResult download(String dbType); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml index 8da73a8ea..7b2b02309 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml @@ -29,10 +29,14 @@ ai.chat2db chat2db-server-tools-common - - - - + + org.ehcache + ehcache + + + javax.cache + cache-api + ai.chat2db chat2db-spi diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java new file mode 100644 index 000000000..1489952d4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java @@ -0,0 +1,55 @@ +package ai.chat2db.server.domain.core.cache; + +import java.io.File; + +import com.alibaba.fastjson2.JSON; + +import org.ehcache.Cache; +import org.ehcache.CacheManager; +import org.ehcache.config.builders.CacheConfigurationBuilder; +import org.ehcache.config.builders.CacheManagerBuilder; +import org.ehcache.config.builders.ResourcePoolsBuilder; +import org.ehcache.config.units.EntryUnit; +import org.ehcache.config.units.MemoryUnit; +import org.springframework.util.StringUtils; + +public class CacheManage { + private static final String PATH = System.getProperty("user.home") + File.separator + ".chat2db" + + File.separator + + "cache" + File.separator + "chat2db-ehcache-data"; + + private static final String CACHE = "meta_cache"; + private static CacheManager cacheManager; + + static { + cacheManager = CacheManagerBuilder.newCacheManagerBuilder() + .with(CacheManagerBuilder.persistence(PATH)) // 确保这个路径是存在且有写权限的 + .withCache(CACHE, CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, + ResourcePoolsBuilder.newResourcePoolsBuilder() + .heap(1, EntryUnit.ENTRIES) + .disk(20, MemoryUnit.GB, true))) // 磁盘持久化设置为true + .build(true); + } + + public static T get(String key,Class clazz) { + Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); + String value = myCache.get(key); + if(!StringUtils.isEmpty(value)){ + return JSON.parseObject(value,clazz); + } + return null; + } + + + + public static void put(String s, Object value) { + Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); + myCache.put(s, JSON.toJSONString(value)); + } + + public static void close() { + cacheManager.close(); + } + + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java index a74d72246..e2e62e152 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java @@ -42,6 +42,9 @@ public abstract class DataSourceConverter { @Mapping(target = "extendInfo", expression = "java(param.getExtendInfo()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + ".getExtendInfo()))") + @Mapping(target = "driverConfig", + expression = "java(param.getDriverConfig()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + + ".getDriverConfig()))") public abstract DataSourceDO param2do(DataSourceCreateParam param); /** @@ -114,7 +117,10 @@ protected String decryptString(DataSourceDO param) { expression = "java(param.getSsl()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param.getSsl()))"), @Mapping(target = "extendInfo", expression = "java(param.getExtendInfo()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" - + ".getExtendInfo()))") + + ".getExtendInfo()))"), + @Mapping(target = "driverConfig", + expression = "java(param.getDriverConfig()==null?null: com.alibaba.fastjson2.JSON.toJSONString(param" + + ".getDriverConfig()))") }) public abstract DataSourceDO param2do(DataSourceUpdateParam param); @@ -155,8 +161,13 @@ public abstract DataSourceTestParam param2param( "java(com.alibaba.fastjson2.JSON.parseObject(dataSourceDO.getSsl(),ai.chat2db.spi" + ".model.SSLInfo" + ".class))") + @Mapping(target = "driverConfig", + expression = + "java(com.alibaba.fastjson2.JSON.parseObject(dataSourceDO.getDriverConfig(),ai.chat2db.spi.config" + + ".DriverConfig" + + ".class))") @Mapping(target = "extendInfo", - expression = "java(com.alibaba.fastjson2.JSON.parseArray(dataSourceDO.getExtendInfo(),KeyValue.class))") + expression = "java(com.alibaba.fastjson2.JSON.parseArray(dataSourceDO.getExtendInfo(),ai.chat2db.spi.model.KeyValue.class))") public abstract DataSource do2dto(DataSourceDO dataSourceDO); /** diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 9fed7670c..02049eaec 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -4,12 +4,15 @@ import java.util.Map; import java.util.stream.Collectors; +import com.alibaba.fastjson2.JSON; + import ai.chat2db.server.domain.api.param.DatabaseOperationParam; import ai.chat2db.server.domain.api.param.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.param.SchemaOperationParam; import ai.chat2db.server.domain.api.param.SchemaQueryParam; import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -17,6 +20,7 @@ import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.sql.Chat2DBContext; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -29,6 +33,7 @@ public class DatabaseServiceImpl implements DatabaseService { @Override + @Cacheable(value = "database", key = "'data_source_' + #param.dataSourceId", condition = "#param.refresh == false") public ListResult queryAll(DatabaseQueryAllParam param) { return ListResult.of(Chat2DBContext.getMetaData().databases()); } @@ -40,6 +45,36 @@ public ListResult querySchema(SchemaQueryParam param) { @Override public DataResult queryDatabaseSchema(MetaDataQueryParam param) { + DataResult result = CacheManage.get("data_source_" + param.getDataSourceId(), DataResult.class); + if (result == null || param.isRefresh()) { + result = new DataResult<>(); + MetaSchema metaSchema = new MetaSchema(); + List databases = Chat2DBContext.getMetaData().databases(); + if (!CollectionUtils.isEmpty(databases)) { + List schemaList = Chat2DBContext.getMetaData().schemas(null); + if (databases.size() == 1) { + databases.get(0).setSchemas(schemaList); + metaSchema.setDatabases(databases); + } else { + Map> schemaMap = schemaList.stream().collect( + Collectors.groupingBy( + schema -> schema.getDatabaseName() != null ? schema.getDatabaseName() : "")); + for (Database dataBase : databases) { + dataBase.setSchemas(schemaMap.get(dataBase.getName())); + } + metaSchema.setDatabases(databases); + } + } else { + List schemas = Chat2DBContext.getMetaData().schemas(null); + metaSchema.setSchemas(schemas); + } + result.setData(metaSchema); + CacheManage.put("data_source_" + param.getDataSourceId(), result); + } + return result; + } + + public String queryDatabaseSchemaCache(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); List databases = Chat2DBContext.getMetaData().databases(); if (!CollectionUtils.isEmpty(databases)) { @@ -59,7 +94,7 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { List schemas = Chat2DBContext.getMetaData().schemas(null); metaSchema.setSchemas(schemas); } - return DataResult.of(metaSchema); + return JSON.toJSONString(metaSchema); } @Override 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 new file mode 100644 index 000000000..29356a9ee --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/JdbcDriverServiceImpl.java @@ -0,0 +1,110 @@ +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.repository.entity.JdbcDriverDO; +import ai.chat2db.server.domain.repository.mapper.JdbcDriverMapper; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.util.JdbcJarUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +public class JdbcDriverServiceImpl implements JdbcDriverService { + + @Autowired + private JdbcDriverMapper jdbcDriverMapper; + + @Override + public DataResult getDrivers(String dbType) { + Map driverConfigMap = new LinkedHashMap<>(); + LambdaQueryWrapper query = new LambdaQueryWrapper(); + query.eq(JdbcDriverDO::getDbType, dbType); + List driverDOS = jdbcDriverMapper.selectList(query); + 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()); + } + } + } + + 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 (flag && driverConfigMap.get(driverConfig.getJdbcDriver()) == null) { + driverConfigMap.put(driverConfig.getJdbcDriver(), driverConfig); + } else { + log.warn("Driver file not found: {}", driverConfig.getJdbcDriver()); + } + } + dbConfig.setDriverConfigList(driverConfigMap.isEmpty() ? null : Lists.newArrayList(driverConfigMap.values())); + return DataResult.of(dbConfig); + } + + @Override + public ActionResult upload(String dbType, String jdbcDriverClass, String localPath) { + JdbcDriverDO driverDO = new JdbcDriverDO(); + driverDO.setJdbcDriverClass(jdbcDriverClass); + driverDO.setDbType(dbType); + driverDO.setJdbcDriver(localPath); + jdbcDriverMapper.insert(driverDO); + return ActionResult.isSuccess(); + } + + @Override + public ActionResult download(String dbType) { + DBConfig dbConfig = Chat2DBContext.PLUGIN_MAP.get(dbType).getDBConfig(); + List driverConfigList = dbConfig.getDriverConfigList(); + for (DriverConfig driverConfig : driverConfigList) { + List downloadJdbcDriverUrls = driverConfig.getDownloadJdbcDriverUrls(); + for (String downloadJdbcDriverUrl : downloadJdbcDriverUrls) { + try { + JdbcJarUtils.download(downloadJdbcDriverUrl); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + return ActionResult.isSuccess(); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java index 0736135f2..e8d3c0d1c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java @@ -1,10 +1,11 @@ package ai.chat2db.server.domain.repository.entity; +import java.io.Serializable; +import java.time.LocalDateTime; + import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; @@ -114,4 +115,8 @@ public class DataSourceDO implements Serializable { */ private String extendInfo; + /** + * 驱动配置 + */ + private String driverConfig; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/JdbcDriverDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/JdbcDriverDO.java new file mode 100644 index 000000000..83543da07 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/JdbcDriverDO.java @@ -0,0 +1,49 @@ +package ai.chat2db.server.domain.repository.entity; + +import java.io.Serializable; +import java.time.LocalDateTime; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@TableName("JDBC_DRIVER") +public class JdbcDriverDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * dbType + */ + private String dbType; + + /** + * jdbcDriver + */ + private String jdbcDriver; + + /** + * jdbcDriverClass + */ + private String jdbcDriverClass; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/JdbcDriverMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/JdbcDriverMapper.java new file mode 100644 index 000000000..bd6a6ef40 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/JdbcDriverMapper.java @@ -0,0 +1,12 @@ + +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.JdbcDriverDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + * @author jipengfei + * @version : SystemConfigMapper.java + */ +public interface JdbcDriverMapper extends BaseMapper { +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/JdbcDriverMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/JdbcDriverMapper.xml new file mode 100644 index 000000000..09063866b --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/JdbcDriverMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index c4e752593..4725ced8a 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -3,6 +3,7 @@ import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; import org.springframework.stereotype.Indexed; @@ -15,6 +16,7 @@ @ComponentScan(value = {"ai.chat2db.server"}) @MapperScan("ai.chat2db.server.domain.repository.mapper") @Indexed +@EnableCaching public class Application { public static void main(String[] args) { diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application.yml b/chat2db-server/chat2db-server-start/src/main/resources/application.yml index d68e9c7d2..9eb2846c5 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application.yml @@ -28,28 +28,13 @@ spring: # 用于数据库表结构版本管理 flyway: locations: classpath:db/migration + servlet: + multipart: + max-file-size: -1 + max-request-size: -1 ali: dbhub: version: 1.0.0 - jdbc-jar-downLoad-urls: - - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-8.0.30.jar - - https://oss-chat2db.alibaba.com/lib/mysql-connector-java-5.1.47.jar - - https://oss-chat2db.alibaba.com/lib/clickhouse-jdbc-0.3.2-patch8-http.jar - - https://oss-chat2db.alibaba.com/lib/mariadb-java-client-3.0.8.jar - - https://oss-chat2db.alibaba.com/lib/mssql-jdbc-11.2.1.jre17.jar - - https://oss-chat2db.alibaba.com/lib/oceanbase-client-2.4.2.jar - - https://oss-chat2db.alibaba.com/lib/postgresql-42.5.1.jar - - https://oss-chat2db.alibaba.com/lib/sqlite-jdbc-3.39.3.0.jar - - https://oss-chat2db.alibaba.com/lib/ojdbc11.jar - - https://oss-chat2db.alibaba.com/lib/ojdbc8-19.3.0.0.jar - - https://oss-chat2db.alibaba.com/lib/orai18n-19.3.0.0.jar - - https://oss-chat2db.alibaba.com/lib/h2-2.1.214.jar - - https://oss-chat2db.alibaba.com/lib/DmJdbcDriver18-8.1.2.141.jar - - https://oss-chat2db.alibaba.com/lib/HikariCP-4.0.3.jar - - https://oss-chat2db.alibaba.com/lib/redis-jdbc-driver-1.3.jar - - https://oss-chat2db.alibaba.com/lib/kingbase8-8.6.0.jar - - https://oss-chat2db.alibaba.com/lib/hive-jdbc-3.1.2-standalone.jar - - https://oss-chat2db.alibaba.com/lib/mongo-jdbc-standalone-1.18.jar # flywaydb 输出执行sql的日志 logging: level: diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_7__\350\207\252\345\256\232\344\271\211\351\251\261\345\212\250.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_7__\350\207\252\345\256\232\344\271\211\351\251\261\345\212\250.sql" new file mode 100644 index 000000000..af4c1d0ed --- /dev/null +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_7__\350\207\252\345\256\232\344\271\211\351\251\261\345\212\250.sql" @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS `jdbc_driver` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `db_type` varchar(32) NOT NULL COMMENT 'db类型', + `jdbc_driver` varchar(512) DEFAULT NULL COMMENT 'jar包', + `jdbc_driver_class` varchar(512) DEFAULT NULL COMMENT 'driver class类', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='自定义驱动表' +; +create INDEX idx_db_type on jdbc_driver(db_type) ; +ALTER TABLE `data_source` ADD COLUMN `driver_config` varchar(1024) NULL COMMENT 'driver_config配置'; + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index 6373a7a11..3530cc44d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -22,6 +22,10 @@ ai.chat2db chat2db-server-domain-api + + ai.chat2db + chat2db-server-domain-core + org.springframework.boot spring-boot-starter-web @@ -42,6 +46,10 @@ com.theokanning.openai-gpt3-java service + + commons-io + commons-io + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java index 715191474..9a22a0d70 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java @@ -4,6 +4,7 @@ */ package ai.chat2db.server.web.api.controller; +import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.AliDbhubProperties; import ai.chat2db.server.tools.common.util.I18nUtils; @@ -14,7 +15,6 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -59,7 +59,7 @@ public DataResult getVersion() { /** * 退出服务 */ - @PostMapping("/stop") + @RequestMapping("/stop") public DataResult stop() { log.info("退出应用"); new Thread(() -> { @@ -78,10 +78,12 @@ public DataResult stop() { // 有可能SpringApplication.exit 会退出失败 // 直接系统退出 log.info("开始退出系统应用"); + CacheManage.close(); try { System.exit(0); } catch (Exception ignore) { } + }).start(); return DataResult.of("ok"); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCreateRequest.java index 2b04cea03..62152e824 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceCreateRequest.java @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.spi.config.DriverConfig; import jakarta.validation.constraints.NotNull; import ai.chat2db.spi.model.KeyValue; @@ -93,4 +94,10 @@ public class DataSourceCreateRequest { * 扩展信息 */ private List extendInfo; + + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceTestRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceTestRequest.java index 53e89c4cb..083dbe2c8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceTestRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceTestRequest.java @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.spi.config.DriverConfig; import jakarta.validation.constraints.NotNull; import ai.chat2db.spi.model.KeyValue; @@ -88,4 +89,9 @@ public class DataSourceTestRequest { * 扩展信息 */ private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceUpdateRequest.java index 727d5a73d..0d8eb367f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceUpdateRequest.java @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.spi.config.DriverConfig; import jakarta.validation.constraints.NotNull; import ai.chat2db.spi.model.KeyValue; @@ -98,4 +99,9 @@ public class DataSourceUpdateRequest { * 扩展信息 */ private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } 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 5b939c1b1..dd4bca44f 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 @@ -2,10 +2,9 @@ import java.util.List; +import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; - import lombok.Data; /** @@ -66,10 +65,10 @@ public class DataSourceVO { */ private SSHInfo ssh; - /** - * ssh - */ - private SSLInfo ssl; + ///** + // * ssh + // */ + //private SSLInfo ssl; /** * sid @@ -92,4 +91,8 @@ public class DataSourceVO { */ private List extendInfo; + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java new file mode 100644 index 000000000..87a70e260 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java @@ -0,0 +1,95 @@ +package ai.chat2db.server.web.api.controller.driver; + +import java.io.File; +import java.io.IOException; + +import ai.chat2db.server.domain.api.service.JdbcDriverService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.spi.config.DBConfig; +import ai.chat2db.spi.util.JdbcJarUtils; +import org.apache.commons.io.FilenameUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +/** + * JDBC driver management + * + * @author moji + * @version JdbcDriverController.java, v 0.1 2022年09月16日 17:41 moji Exp $ + * @date 2022/09/16 + */ +@RequestMapping("/api/jdbc/driver") +@RestController +public class JdbcDriverController { + + @Autowired + private JdbcDriverService jdbcDriverService; + + /** + * 查询当前DB驱动信息 + * + * @param dbType + * @return + */ + @GetMapping("/list") + public DataResult list(@RequestParam String dbType) { + return jdbcDriverService.getDrivers(dbType); + } + + /** + * 下载驱动 + * @param dbType + * @return + */ + + @GetMapping("/download") + public ActionResult download(@RequestParam String dbType) { + return jdbcDriverService.download(dbType); + } + + /** + * 上传驱动 + * + * @param multipartFiles + * @param jdbcDriverClass + * @return + */ + @PostMapping("/upload") + public ActionResult upload(@RequestParam MultipartFile[] multipartFiles, @RequestParam String jdbcDriverClass, + @RequestParam String dbType) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < multipartFiles.length; i++) { + if (i > 0) { + stringBuilder.append(","); + } + MultipartFile multipartFile = multipartFiles[i]; + String originalFilename = FilenameUtils.getName(multipartFile.getOriginalFilename()); + String location = JdbcJarUtils.PATH + originalFilename; + try { + multipartFile.transferTo(new File(location)); + } catch (IOException e) { + throw new RuntimeException(e); + } + stringBuilder.append(originalFilename); + } + return jdbcDriverService.upload(dbType, jdbcDriverClass, stringBuilder.toString()); + + } + + ///** + // * 删除驱动 + // * + // * @param request + // * @return + // */ + //@DeleteMapping("/delete") + //public ActionResult delete(@RequestBody KeyDeleteRequest request) { + // return null; + //} +} 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 9f17f3ab6..39be1918a 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 @@ -21,14 +21,21 @@ public class DriverConfig { */ private String jdbcDriverClass; - /** - * name - */ - private String name; + ///** + // * name + // */ + //private String name; /** * downloadJdbcDriverUrls */ private List downloadJdbcDriverUrls; + + private String dbType; + + /** + * 自定义 + */ + private boolean custom; } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java index 216ce7b8d..ffc434f9e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java @@ -1,12 +1,14 @@ package ai.chat2db.spi.model; +import java.io.Serializable; +import java.util.List; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; -import java.util.List; - /** * 数据库 * @@ -16,7 +18,8 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Database { +public class Database implements Serializable { + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * 数据库名字 */ 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 907b41f48..1716356e6 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 @@ -1,6 +1,7 @@ package ai.chat2db.spi.model; +import java.io.Serializable; import java.util.List; import java.util.Map; @@ -13,7 +14,7 @@ * @version : KeyValue.java */ @Data -public class KeyValue { +public class KeyValue implements Serializable { /** * 属性名 */ diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java index 0ac4b706b..424c8445a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/MetaSchema.java @@ -1,18 +1,21 @@ package ai.chat2db.spi.model; +import java.io.Serializable; +import java.util.List; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; -import java.util.List; - @Data @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class MetaSchema { +public class MetaSchema implements Serializable { + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * database list */ diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java index beb6d78f7..7219c30c6 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SSLInfo.java @@ -1,9 +1,12 @@ package ai.chat2db.spi.model; +import lombok.Data; + /** * @author jipengfei * @version : SSLInfo.java */ +@Data public class SSLInfo { } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java index 52e0d1329..c72e3dfb0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java @@ -1,6 +1,9 @@ package ai.chat2db.spi.model; +import java.io.Serializable; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -14,7 +17,8 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class Schema { +public class Schema implements Serializable { + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; /** * databaseName 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 a88ebdd82..7167ba6f1 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 @@ -65,7 +65,12 @@ public static DBManage getDBManage() { } public static Connection getConnection() { - return getConnectInfo().getConnection(); + ConnectInfo connectInfo = getConnectInfo(); + Connection connection = connectInfo.getConnection(); + if (connection == null) { + connection = setConnectInfoThreadLocal(connectInfo); + } + return connection; } /** @@ -74,64 +79,67 @@ public static Connection getConnection() { * @param info */ public static void putContext(ConnectInfo info) { - ConnectInfo connectInfo = CONNECT_INFO_THREAD_LOCAL.get(); CONNECT_INFO_THREAD_LOCAL.set(info); - if (connectInfo == null) { - setConnectInfoThreadLocal(info); - if (StringUtils.isNotBlank(info.getDatabaseName())) { - PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBManage().connectDatabase(info.getDatabaseName()); - } - } } - private static void setConnectInfoThreadLocal(ConnectInfo connectInfo) { - Session session = null; - Connection connection = null; - SSHInfo ssh = connectInfo.getSsh(); - String url = connectInfo.getUrl(); - String host = connectInfo.getHost(); - String port = connectInfo.getPort() + ""; - try { - ssh.setRHost(host); - ssh.setRPort(port); - session = getSession(ssh); - if (session != null) { - url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); - } - DriverConfig config = connectInfo.getDriverConfig(); - if (config == null) { - config = getDefaultDriverConfig(connectInfo.getDbType()); - connectInfo.setDriverConfig(config); - } - - connection = IDriverManager.getConnection(url, connectInfo.getUser(), connectInfo.getPassword(), - connectInfo.getDriverConfig(), connectInfo.getExtendMap()); - - } catch (Exception e1) { - log.error("GetConnect error", e1); + private static Connection setConnectInfoThreadLocal(ConnectInfo connectInfo) { + synchronized (connectInfo) { + Connection connection = connectInfo.getConnection(); if (connection != null) { - try { - connection.close(); - } catch (SQLException e) { - log.error("session close error", e); - } + return connection; } - if (session != null) { - try { - session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); - } catch (JSchException e) { - log.error("session delPortForwardingL error", e); + Session session = null; + SSHInfo ssh = connectInfo.getSsh(); + String url = connectInfo.getUrl(); + String host = connectInfo.getHost(); + String port = connectInfo.getPort() + ""; + try { + ssh.setRHost(host); + ssh.setRPort(port); + session = getSession(ssh); + if (session != null) { + url = url.replace(host, "127.0.0.1").replace(port, ssh.getLocalPort()); } - try { - session.disconnect(); - } catch (Exception e) { - log.error("session disconnect error", e); + DriverConfig config = connectInfo.getDriverConfig(); + if (config == null) { + config = getDefaultDriverConfig(connectInfo.getDbType()); + connectInfo.setDriverConfig(config); } + + connection = IDriverManager.getConnection(url, connectInfo.getUser(), connectInfo.getPassword(), + connectInfo.getDriverConfig(), connectInfo.getExtendMap()); + + } catch (Exception e1) { + log.error("GetConnect error", e1); + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + log.error("session close error", e); + } + } + if (session != null) { + try { + session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); + } catch (JSchException e) { + log.error("session delPortForwardingL error", e); + } + try { + session.disconnect(); + } catch (Exception e) { + log.error("session disconnect error", e); + } + } + throw new RuntimeException("GetConnect error", e1); + } + connectInfo.setSession(session); + connectInfo.setConnection(connection); + if (StringUtils.isNotBlank(connectInfo.getDatabaseName())) { + PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBManage().connectDatabase( + connectInfo.getDatabaseName()); } - throw new RuntimeException("GetConnect error", e1); + return connection; } - connectInfo.setSession(session); - connectInfo.setConnection(connection); } private static Session getSession(SSHInfo ssh) { 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 28ef96048..7d126a034 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 @@ -71,16 +71,14 @@ public static Connection getConnection(String url, Properties info, DriverConfig if (url == null) { throw new SQLException("The url cannot be null", "08001"); } - DriverManager.println("DriverManager.getConnection(\"" + url + "\")"); SQLException reason = null; - DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getName()); + DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); if (driverEntry == null) { driverEntry = getJDBCDriver(driver); } try { Connection con = driverEntry.getDriver().connect(url, info); if (con != null) { - DriverManager.println("getConnection returning " + driverEntry.getDriver().getClass().getName()); return con; } } catch (SQLException var7) { @@ -116,13 +114,13 @@ private static DriverEntry getJDBCDriver(DriverConfig driver) throws SQLException { synchronized (driver) { try { - if (DRIVER_ENTRY_MAP.containsKey(driver.getName())) { - return DRIVER_ENTRY_MAP.get(driver.getName()); + 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(); DriverEntry driverEntry = DriverEntry.builder().driverConfig(driver).driver(d).build(); - DRIVER_ENTRY_MAP.put(driver.getName(), driverEntry); + DRIVER_ENTRY_MAP.put(driver.getJdbcDriver(), driverEntry); return driverEntry; } catch (Exception e) { log.error("getJDBCDriver error", e); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java index 9b0e7d6dd..0ca106a84 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java @@ -27,7 +27,7 @@ public class JdbcJarUtils { private static final OkHttpClient client = new OkHttpClient(); - private static final String PATH = System.getProperty("user.home") + File.separator + ".chat2db" + File.separator + public static final String PATH = System.getProperty("user.home") + File.separator + ".chat2db" + File.separator + "jdbc-lib" + File.separator; static { diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 34be59153..67cd1bb3e 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -257,6 +257,21 @@ azure-ai-openai 1.0.0-beta.2 + + org.ehcache + ehcache + 3.10.8 + + + javax.cache + cache-api + 1.1.1 + + + commons-io + commons-io + 2.7 + From c34ffddf33b17eaf7e74309db73c7c8bc740471d Mon Sep 17 00:00:00 2001 From: robinji0 <850379744@qq.com> Date: Sat, 8 Jul 2023 16:52:41 +0800 Subject: [PATCH 0249/1069] update prompt detail --- .../api/controller/ai/listener/OpenAIEventSourceListener.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java index 04e9e08ca..7fc7b47fc 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java @@ -81,9 +81,7 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { if (Objects.isNull(response)) { String message = t.getMessage(); if ("No route to host".equals(message)) { - message = "网络连接超时,请检查网络连通性,参考文章"; - } else { - message = "AI无法正常访问,请参考文章进行配置"; + message = "网络连接超时,请百度自行解决网络问题"; } Message sseMessage = new Message(); sseMessage.setContent(message); From 58106c276f49788fa94b30b94290932a341a43a9 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 17:26:49 +0800 Subject: [PATCH 0250/1069] feat: improve error tips --- .../components/Console/ChatInput/index.less | 2 +- .../components/Console/ChatInput/index.tsx | 20 +++---- .../src/components/Console/index.tsx | 6 +++ .../src/components/MyNotification/index.less | 18 +++++++ .../src/components/MyNotification/index.tsx | 52 +++++++++++++++++++ chat2db-client/src/i18n/en-us/chat.ts | 3 ++ chat2db-client/src/i18n/en-us/common.ts | 2 + chat2db-client/src/i18n/zh-cn/chat.ts | 5 ++ chat2db-client/src/i18n/zh-cn/common.ts | 2 + .../src/layouts/init/registerNotification.ts | 4 +- chat2db-client/src/pages/main/index.tsx | 4 +- chat2db-client/src/service/base.ts | 19 ++++--- 12 files changed, 115 insertions(+), 22 deletions(-) create mode 100644 chat2db-client/src/components/MyNotification/index.less create mode 100644 chat2db-client/src/components/MyNotification/index.tsx diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 90a4bb596..50bd038c4 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -57,7 +57,7 @@ .aiSelectedTable { display: flex; flex-direction: column; - max-width: 140px; + max-width: 280px; } .aiSelectedTableTips { diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 740c9d8b8..7f31e93d6 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; -import { Checkbox, Dropdown, Input, Modal, Popover } from 'antd'; +import { Checkbox, Dropdown, Input, Modal, Popover, Select } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; @@ -33,24 +33,26 @@ function ChatInput(props: IProps) { const renderSelectTable = () => { const { tables, selectedTables, onSelectTables } = props; - return tables && tables.length ? ( + const options = (tables || []).map(t => ({ value: t, label: t })) + return (
{/* */} {i18n('chat.input.remain.tooltip')} - { - onSelectTables && onSelectTables(v); + onSelectTables && onSelectTables(v) }} />
- ) : ( -
暂无表
- ); + ) }; const renderSuffix = () => { diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index a183ecf18..3fe096e48 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -306,6 +306,12 @@ function Console(props: IProps) { onPressEnter={onPressChatInput} selectedTables={selectedTables} onSelectTables={(tables: string[]) => { + if(tables.length > 8){ + message.warning({ + content: i18n('chat.input.tableSelect.error.TooManyTable') + }) + return; + } setSelectedTables(tables); }} onClickRemainBtn={() => { diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less new file mode 100644 index 000000000..f7219294d --- /dev/null +++ b/chat2db-client/src/components/MyNotification/index.less @@ -0,0 +1,18 @@ +@import '../../styles/var.less'; + +.notification{ + padding: 10px 16px; +} + +.message { + .f-lines(1); +} + + +.description { + display: flex; + + button { + padding: 4px 0; + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx new file mode 100644 index 000000000..ceb26d03a --- /dev/null +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -0,0 +1,52 @@ +import { Button, Modal, notification, } from 'antd'; +import React from 'react' +import styles from './index.less' +import i18n from '@/i18n'; +// import { staticNotification } from '@/layouts' + +interface IProps { + type?: IconType; + message?: React.ReactNode; + /** 错误代码 */ + errorCode: string; + /** 错误信息 */ + errorMessage: string; + /** 错误详情 */ + errorDetail: string; + /** 问题wiki路径 */ + solutionLink: string; +} + +function MyNotification(props: IProps) { + const { errorCode, errorMessage, errorDetail, solutionLink } = props; + + const type = props.type || 'warning'; + const title = `${errorCode}:${errorMessage}`; + const message = props.message ||
{errorCode}:{errorMessage}
+ + const description =
+ + +
+ + + return notification.open({ + ...props, + className: styles.notification, + type, + message, + description, + }) + + +} + +export default MyNotification; \ No newline at end of file diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts index efcf1d4f0..4d455e804 100644 --- a/chat2db-client/src/i18n/en-us/chat.ts +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -1,6 +1,9 @@ export default { 'chat.input.remain': '{1} remaining', 'chat.input.remain.tooltip': 'Table schema added to prompt.', + 'chat.input.tableSelect.placeholder':'Please choose tables', + 'chat.input.tableSelect.error.TooManyTable':'You can only select up to 8 tables', 'chat.input.remain.dialog.tips': 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', + }; diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 363502e3a..cd72d0463 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -58,4 +58,6 @@ export default { 'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.', 'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', + 'common.notification.detial': 'More Detial', + 'common.notification.solution': 'Solution', }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index da9e2472b..9220a5a13 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -1,4 +1,9 @@ export default { 'chat.input.remain': '剩余 {1} 次', 'chat.input.remain.tooltip': '选中表的schema将会被添加到prompt中', + 'chat.input.tableSelect.placeholder': '请选择表', + 'chat.input.tableSelect.error.TooManyTable': '最多选择8张表', + 'chat.input.remain.dialog.tips': + '关注公众号,发送"推广"获取更多体验次数', + }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 06e2f6f35..f37fc49ce 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -58,4 +58,6 @@ export default { 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', + 'common.notification.detial': '查看详情', + 'common.notification.solution': '解决办法', }; diff --git a/chat2db-client/src/layouts/init/registerNotification.ts b/chat2db-client/src/layouts/init/registerNotification.ts index d5696ce5a..1ef394f5b 100644 --- a/chat2db-client/src/layouts/init/registerNotification.ts +++ b/chat2db-client/src/layouts/init/registerNotification.ts @@ -2,8 +2,8 @@ import { notification } from 'antd'; export default () => { notification.config({ - placement: 'topRight', + placement: 'BottomRight', maxCount: 2, - duration: 3, + duration: 0, }); }; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 63e5c8347..15624f173 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -117,13 +117,13 @@ function MainPage(props: IProps) { })}
- { window.open('https://github.com/chat2db/chat2db/wiki'); }} - /> + /> */}
diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 3fd588c22..22c0c849c 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,6 +1,7 @@ import { extend, ResponseError } from 'umi-request'; import { message, notification } from 'antd'; import { getLang } from '@/utils/localStorage'; +import ErrorNotification from '@/components/MyNotification'; const path = require('path'); export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; @@ -127,8 +128,9 @@ request.interceptors.response.use(async (response, options) => { export default function createRequest

(url: string, options?: IOptions) { const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside } = options || {}; + // 是否需要mock - let _baseURL = mock ? mockUrl : baseURL; + let _baseURL = (mock ? mockUrl : baseURL) || ''; return function (params: P) { // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; @@ -167,15 +169,16 @@ export default function createRequest

(url: string, options?: I request[method](eventualUrl, { [dataName]: params }) .then((res) => { if (!res) return; - const { success, errorCode, errorMessage, data } = res; + const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { - // notification.open({ - // type: 'error', - // message: errorCode, - // description: errorMessage, - // }); - message.error(`${errorCode}: ${errorMessage}`); + ErrorNotification({ + errorCode, + errorMessage, + errorDetail, + solutionLink, + }) + // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; From 677a03a6cba1e23aec1e87f2a83be3e16a48b821 Mon Sep 17 00:00:00 2001 From: robinji0 <850379744@qq.com> Date: Sat, 8 Jul 2023 18:55:17 +0800 Subject: [PATCH 0251/1069] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e448b4698..ff5128c0c 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 ## 📖 Introduction -   Chat2DB is a multi-database client tool that is open-source and free from Alibaba. It supports local installation on Windows and Mac, as well as server-side deployment and web page access. Compared to traditional database client software such as Navicat and DBeaver, Chat2DB integrates AIGC's capabilities and is able to convert natural language into SQL. It can also convert SQL into natural language and provide optimization suggestions for SQL to greatly enhance the efficiency of developers. It is a tool for database developers in the AI era, and even non-SQL business operators in the future can use it to quickly query business data and generate reports. +   Chat2DB is a multi-database client tool that is open-source and free. It supports local installation on Windows and Mac, as well as server-side deployment and web page access. Compared to traditional database client software such as Navicat and DBeaver, Chat2DB integrates AIGC's capabilities and is able to convert natural language into SQL. It can also convert SQL into natural language and provide optimization suggestions for SQL to greatly enhance the efficiency of developers. It is a tool for database developers in the AI era, and even non-SQL business operators in the future can use it to quickly query business data and generate reports. ## ✨ Features From 9dea50c0616a2be04fc317a537930ac9fcbe31d2 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 8 Jul 2023 19:02:53 +0800 Subject: [PATCH 0252/1069] Add CacheManage --- .../domain/api/param/TablePageQueryParam.java | 13 ++-- .../server/domain/core/cache/CacheKey.java | 21 +++++ .../server/domain/core/cache/CacheManage.java | 55 +++++++++++-- .../domain/core/impl/DatabaseServiceImpl.java | 77 +++++++------------ .../domain/core/impl/TableServiceImpl.java | 37 +++++---- .../source/request/DataSourceBaseRequest.java | 6 ++ .../driver/JdbcDriverController.java | 32 +++++--- .../driver/request/JdbcDriverRequest.java | 13 ++++ .../api/controller/rdb/RdbDdlController.java | 19 ++--- .../rdb/request/TableBriefQueryRequest.java | 5 ++ .../java/ai/chat2db/spi/sql/SQLExecutor.java | 55 +++++++------ 11 files changed, 215 insertions(+), 118 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/request/JdbcDriverRequest.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java index aef24e6a1..f2102773a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java @@ -1,11 +1,7 @@ package ai.chat2db.server.domain.api.param; -import java.io.Serial; - -import jakarta.validation.constraints.NotNull; - import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; - +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -44,4 +40,11 @@ public class TablePageQueryParam extends PageQueryParam { * */ private String schemaName; + + + + /** + * if true, refresh the cache + */ + private boolean refresh; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java new file mode 100644 index 000000000..93807e976 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.domain.core.cache; + +import org.springframework.util.StringUtils; + +public class CacheKey { + + public static String getDataSourceKey(Long dataSourceId) { + return "schemas_datasourceId_" + dataSourceId; + } + + public static String getTableKey(Long dataSourceId, String databaseName, String schemaName) { + StringBuffer stringBuffer = new StringBuffer("tables_dataSourceId" + dataSourceId); + if (!StringUtils.isEmpty(databaseName)) { + stringBuffer.append("_databaseName" + databaseName); + } + if (!StringUtils.isEmpty(schemaName)) { + stringBuffer.append("_schemaName" + schemaName); + } + return stringBuffer.toString(); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java index 1489952d4..61f8156fd 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheManage.java @@ -1,6 +1,8 @@ package ai.chat2db.server.domain.core.cache; import java.io.File; +import java.util.List; +import java.util.function.Function; import com.alibaba.fastjson2.JSON; @@ -22,32 +24,73 @@ public class CacheManage { private static CacheManager cacheManager; static { - cacheManager = CacheManagerBuilder.newCacheManagerBuilder() + cacheManager = CacheManagerBuilder.newCacheManagerBuilder() .with(CacheManagerBuilder.persistence(PATH)) // 确保这个路径是存在且有写权限的 .withCache(CACHE, CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, String.class, ResourcePoolsBuilder.newResourcePoolsBuilder() - .heap(1, EntryUnit.ENTRIES) + .heap(1000, EntryUnit.ENTRIES) .disk(20, MemoryUnit.GB, true))) // 磁盘持久化设置为true .build(true); } - public static T get(String key,Class clazz) { + public static T get(String key, Class clazz) { Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); String value = myCache.get(key); - if(!StringUtils.isEmpty(value)){ - return JSON.parseObject(value,clazz); + if (!StringUtils.isEmpty(value)) { + return JSON.parseObject(value, clazz); + } + return null; + } + + public static List getList(String key, Class clazz) { + Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); + String value = myCache.get(key); + if (!StringUtils.isEmpty(value)) { + return JSON.parseArray(value, clazz); } return null; } + public static T get(String key, Class clazz, Function refresh, + Function function) { + T t; + if (refresh.apply(key)) { + t = function.apply(key); + put(key, t); + } else { + t = get(key, clazz); + if (t == null) { + t = function.apply(key); + put(key, t); + } + } + return t; + } + + + public static List getList(String key, Class clazz, Function refresh, + Function> function) { + List t; + if (refresh.apply(key)) { + t = function.apply(key); + put(key, t); + } else { + t = getList(key, clazz); + if (t == null) { + t = function.apply(key); + put(key, t); + } + } + return t; + } public static void put(String s, Object value) { Cache myCache = cacheManager.getCache(CACHE, String.class, String.class); myCache.put(s, JSON.toJSONString(value)); } - public static void close() { + public static void close() { cacheManager.close(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 02049eaec..47c47134e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -4,8 +4,6 @@ import java.util.Map; import java.util.stream.Collectors; -import com.alibaba.fastjson2.JSON; - import ai.chat2db.server.domain.api.param.DatabaseOperationParam; import ai.chat2db.server.domain.api.param.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; @@ -20,10 +18,11 @@ import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.sql.Chat2DBContext; -import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; +import static ai.chat2db.server.domain.core.cache.CacheKey.getDataSourceKey; + /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 2022年09月23日 15:51 moji Exp $ @@ -33,7 +32,6 @@ public class DatabaseServiceImpl implements DatabaseService { @Override - @Cacheable(value = "database", key = "'data_source_' + #param.dataSourceId", condition = "#param.refresh == false") public ListResult queryAll(DatabaseQueryAllParam param) { return ListResult.of(Chat2DBContext.getMetaData().databases()); } @@ -45,58 +43,35 @@ public ListResult querySchema(SchemaQueryParam param) { @Override public DataResult queryDatabaseSchema(MetaDataQueryParam param) { - DataResult result = CacheManage.get("data_source_" + param.getDataSourceId(), DataResult.class); - if (result == null || param.isRefresh()) { - result = new DataResult<>(); - MetaSchema metaSchema = new MetaSchema(); - List databases = Chat2DBContext.getMetaData().databases(); - if (!CollectionUtils.isEmpty(databases)) { - List schemaList = Chat2DBContext.getMetaData().schemas(null); - if (databases.size() == 1) { - databases.get(0).setSchemas(schemaList); - metaSchema.setDatabases(databases); - } else { - Map> schemaMap = schemaList.stream().collect( - Collectors.groupingBy( - schema -> schema.getDatabaseName() != null ? schema.getDatabaseName() : "")); - for (Database dataBase : databases) { - dataBase.setSchemas(schemaMap.get(dataBase.getName())); + MetaSchema ms = CacheManage.get(getDataSourceKey(param.getDataSourceId()), MetaSchema.class, + (key) -> param.isRefresh(), (key) -> { + MetaSchema metaSchema = new MetaSchema(); + List databases = Chat2DBContext.getMetaData().databases(); + if (!CollectionUtils.isEmpty(databases)) { + List schemaList = Chat2DBContext.getMetaData().schemas(null); + if (databases.size() == 1) { + databases.get(0).setSchemas(schemaList); + metaSchema.setDatabases(databases); + } else { + Map> schemaMap = schemaList.stream().collect( + Collectors.groupingBy( + schema -> schema.getDatabaseName() != null ? schema.getDatabaseName() : "")); + for (Database dataBase : databases) { + dataBase.setSchemas(schemaMap.get(dataBase.getName())); + } + metaSchema.setDatabases(databases); } - metaSchema.setDatabases(databases); + } else { + List schemas = Chat2DBContext.getMetaData().schemas(null); + metaSchema.setSchemas(schemas); } - } else { - List schemas = Chat2DBContext.getMetaData().schemas(null); - metaSchema.setSchemas(schemas); - } - result.setData(metaSchema); - CacheManage.put("data_source_" + param.getDataSourceId(), result); - } - return result; - } + return metaSchema; + }); - public String queryDatabaseSchemaCache(MetaDataQueryParam param) { - MetaSchema metaSchema = new MetaSchema(); - List databases = Chat2DBContext.getMetaData().databases(); - if (!CollectionUtils.isEmpty(databases)) { - List schemaList = Chat2DBContext.getMetaData().schemas(null); - if (databases.size() == 1) { - databases.get(0).setSchemas(schemaList); - metaSchema.setDatabases(databases); - } else { - Map> schemaMap = schemaList.stream().collect( - Collectors.groupingBy(schema -> schema.getDatabaseName() != null ? schema.getDatabaseName() : "")); - for (Database dataBase : databases) { - dataBase.setSchemas(schemaMap.get(dataBase.getName())); - } - metaSchema.setDatabases(databases); - } - } else { - List schemas = Chat2DBContext.getMetaData().schemas(null); - metaSchema.setSchemas(schemas); - } - return JSON.toJSONString(metaSchema); + return DataResult.of(ms); } + @Override public ActionResult deleteDatabase(DatabaseOperationParam param) { Chat2DBContext.getDBManage().dropDatabase(param.getDatabaseName()); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 82eb18afa..8d018d6a4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -6,10 +6,20 @@ import java.util.function.Function; import java.util.stream.Collectors; -import ai.chat2db.server.domain.api.param.*; +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.PinTableParam; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.domain.core.converter.PinTableConverter; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; @@ -17,12 +27,6 @@ import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; - import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; @@ -30,6 +34,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import static ai.chat2db.server.domain.core.cache.CacheKey.getTableKey; + /** * @author moji * @version DataSourceCoreServiceImpl.java, v 0.1 2022年09月23日 15:51 moji Exp $ @@ -77,9 +83,9 @@ public DataResult

query(TableQueryParam param, TableSelector selector) { if (!CollectionUtils.isEmpty(tables)) { Table table = tables.get(0); table.setIndexList( - metaSchema.indexes(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.indexes(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); table.setColumnList( - metaSchema.columns(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.columns(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); return DataResult.of(table); } return DataResult.of(null); @@ -93,8 +99,14 @@ public ListResult buildSql(Table oldTable, Table newTable) { @Override public PageResult
pageQuery(TablePageQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); - List
list = metaSchema.tables(param.getDatabaseName(), param.getSchemaName(), param.getTableName()); - list = pinTable(list,param); + + String tableKey = getTableKey(param.getDataSourceId(),param.getDatabaseName(), param.getSchemaName()); + + List
list = CacheManage.getList(tableKey, Table.class, + (key) -> param.isRefresh(), (key) -> + metaSchema.tables(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + + list = pinTable(list, param); if (CollectionUtils.isEmpty(list)) { return PageResult.of(list, 0L, param); } @@ -122,14 +134,13 @@ private List
pinTable(List
list, TablePageQueryParam param) { } for (Table table : list) { - if (table!=null && !tables.contains(table)) { + if (table != null && !tables.contains(table)) { tables.add(table); } } return tables; } - @Override public List queryColumns(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequest.java index ed4ddefdc..b178ebf18 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceBaseRequest.java @@ -27,4 +27,10 @@ public class DataSourceBaseRequest implements DataSourceBaseRequestInfo{ * 表所在空间 */ private String schemaName; + + + /** + * if true, refresh the cache + */ + private boolean refresh; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java index 87a70e260..523dafcf8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/JdbcDriverController.java @@ -2,16 +2,21 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import ai.chat2db.server.domain.api.service.JdbcDriverService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.controller.driver.request.JdbcDriverRequest; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.util.JdbcJarUtils; import org.apache.commons.io.FilenameUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -44,6 +49,7 @@ public DataResult list(@RequestParam String dbType) { /** * 下载驱动 + * * @param dbType * @return */ @@ -57,17 +63,13 @@ public ActionResult download(@RequestParam String dbType) { * 上传驱动 * * @param multipartFiles - * @param jdbcDriverClass * @return */ @PostMapping("/upload") - public ActionResult upload(@RequestParam MultipartFile[] multipartFiles, @RequestParam String jdbcDriverClass, - @RequestParam String dbType) { - StringBuilder stringBuilder = new StringBuilder(); + public ListResult upload(@RequestParam MultipartFile[] multipartFiles) { + List list = new ArrayList<>(); for (int i = 0; i < multipartFiles.length; i++) { - if (i > 0) { - stringBuilder.append(","); - } + MultipartFile multipartFile = multipartFiles[i]; String originalFilename = FilenameUtils.getName(multipartFile.getOriginalFilename()); String location = JdbcJarUtils.PATH + originalFilename; @@ -76,10 +78,22 @@ public ActionResult upload(@RequestParam MultipartFile[] multipartFiles, @Reques } catch (IOException e) { throw new RuntimeException(e); } - stringBuilder.append(originalFilename); + list.add(originalFilename); } - return jdbcDriverService.upload(dbType, jdbcDriverClass, stringBuilder.toString()); + return ListResult.of(list); + } + + /** + * save + * + * @param request + * @return + */ + @PostMapping("/save") + public ActionResult save(@RequestBody JdbcDriverRequest request) { + return jdbcDriverService.upload(request.getDbType(), request.getJdbcDriverClass(), + String.join(",", request.getJdbcDriver())); } ///** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/request/JdbcDriverRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/request/JdbcDriverRequest.java new file mode 100644 index 000000000..64d4fc643 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/driver/request/JdbcDriverRequest.java @@ -0,0 +1,13 @@ +package ai.chat2db.server.web.api.controller.driver.request; + +import java.util.List; + +import lombok.Data; + +@Data +public class JdbcDriverRequest { + String jdbcDriverClass; + String dbType; + + List jdbcDriver; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java index d70d6c9f6..0c0eb29ee 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java @@ -75,7 +75,7 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), - request.getPageSize()); + request.getPageSize()); } /** @@ -100,7 +100,8 @@ public ListResult schemaList(@Valid DataSourceBaseRequest request) { */ @GetMapping("/database_schema_list") public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { - MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()).build(); + MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()).refresh( + request.isRefresh()).build(); DataResult result = databaseService.queryDatabaseSchema(queryParam); MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); return DataResult.of(schemaDto2vo); @@ -139,7 +140,7 @@ public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest req @PostMapping("/modify_database") public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()) - .newDatabaseName(request.getNewDatabaseName()).build(); + .newDatabaseName(request.getNewDatabaseName()).build(); return databaseService.modifyDatabase(param); } @@ -152,7 +153,7 @@ public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest req @PostMapping("/delete_schema") public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); + .schemaName(request.getSchemaName()).build(); return databaseService.deleteSchema(param); } @@ -165,7 +166,7 @@ public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest reque @PostMapping("/create_schema") public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); + .schemaName(request.getSchemaName()).build(); return databaseService.createSchema(param); } @@ -178,7 +179,7 @@ public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest reque @PostMapping("/modify_schema") public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); + .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); return databaseService.modifySchema(param); } @@ -283,9 +284,9 @@ public DataResult query(@Valid TableDetailQueryRequest request) { @GetMapping("/modify/sql") public ListResult modifySql(@Valid TableModifySqlRequest request) { return tableService.buildSql( - rdbWebConverter.tableRequest2param(request.getOldTable()), - rdbWebConverter.tableRequest2param(request.getNewTable())) - .map(rdbWebConverter::dto2vo); + rdbWebConverter.tableRequest2param(request.getOldTable()), + rdbWebConverter.tableRequest2param(request.getNewTable())) + .map(rdbWebConverter::dto2vo); } /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java index e8ba850d0..0b74b7868 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java @@ -38,4 +38,9 @@ public class TableBriefQueryRequest extends PageQueryRequest implements DataSour * 模糊搜索词 */ private String searchKey; + + /** + * if true, refresh the cache + */ + private boolean refresh; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 6c1a65275..e438750a9 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -111,10 +111,10 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL executeResult.setHeaderList(headerList); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() - .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(resultSetMetaData.getColumnName(i)) - .build()); + .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) + .name(resultSetMetaData.getColumnName(i)) + .build()); } // 获取数据信息 @@ -152,7 +152,6 @@ public ExecuteResult execute(String sql) throws SQLException { return execute(sql, getConnection()); } - /** * 获取所有的数据库 * @@ -181,7 +180,7 @@ public List databases() { */ public List> schemas(String databaseName, String schemaName) { List> schemaList = Lists.newArrayList(); - if(StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)){ + if (StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)) { try (ResultSet resultSet = getConnection().getMetaData().getSchemas()) { if (resultSet != null) { while (resultSet.next()) { @@ -192,7 +191,7 @@ public List> schemas(String databaseName, String schemaName) } } } catch (SQLException e) { - throw new RuntimeException("Get schemas error",e); + throw new RuntimeException("Get schemas error", e); } return schemaList; } @@ -206,7 +205,7 @@ public List> schemas(String databaseName, String schemaName) } } } catch (SQLException e) { - throw new RuntimeException("Get schemas error",e); + throw new RuntimeException("Get schemas error", e); } return schemaList; } @@ -222,11 +221,16 @@ public List> schemas(String databaseName, String schemaName) */ public List
tables(String databaseName, String schemaName, String tableName, String types[]) { List
tables = Lists.newArrayList(); + int n = 0; try (ResultSet resultSet = getConnection().getMetaData().getTables(databaseName, schemaName, tableName, - types)) { + types)) { if (resultSet != null) { while (resultSet.next()) { + n++; tables.add(buildTable(resultSet)); + if (n >= 5000) {// 最多只取5000条 + break; + } } } } catch (SQLException e) { @@ -247,7 +251,7 @@ public List
tables(String databaseName, String schemaName, String tableNa public List columns(String databaseName, String schemaName, String tableName, String columnName) { List tableColumns = Lists.newArrayList(); try (ResultSet resultSet = getConnection().getMetaData().getColumns(databaseName, schemaName, tableName, - columnName)) { + columnName)) { if (resultSet != null) { while (resultSet.next()) { tableColumns.add(buildColumn(resultSet)); @@ -269,8 +273,9 @@ public List columns(String databaseName, String schemaName, String */ public List indexes(String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); - try (ResultSet resultSet = getConnection().getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, - false)) { + try (ResultSet resultSet = getConnection().getMetaData().getIndexInfo(databaseName, schemaName, tableName, + false, + false)) { List tableIndexColumns = Lists.newArrayList(); while (resultSet != null && resultSet.next()) { @@ -278,18 +283,18 @@ public List indexes(String databaseName, String schemaName, String t } tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( - Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() - .stream().forEach(entry -> { - TableIndex tableIndex = new TableIndex(); - TableIndexColumn column = entry.getValue().get(0); - tableIndex.setName(entry.getKey()); - tableIndex.setTableName(column.getTableName()); - tableIndex.setSchemaName(column.getSchemaName()); - tableIndex.setDatabaseName(column.getDatabaseName()); - tableIndex.setUnique(!column.getNonUnique()); - tableIndex.setColumnList(entry.getValue()); - tableIndices.add(tableIndex); - }); + Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() + .stream().forEach(entry -> { + TableIndex tableIndex = new TableIndex(); + TableIndexColumn column = entry.getValue().get(0); + tableIndex.setName(entry.getKey()); + tableIndex.setTableName(column.getTableName()); + tableIndex.setSchemaName(column.getSchemaName()); + tableIndex.setDatabaseName(column.getDatabaseName()); + tableIndex.setUnique(!column.getNonUnique()); + tableIndex.setColumnList(entry.getValue()); + tableIndices.add(tableIndex); + }); } catch (SQLException e) { throw new RuntimeException(e); } @@ -304,7 +309,7 @@ public List indexes(String databaseName, String schemaName, String t * @return */ public List functions(String databaseName, - String schemaName) { + String schemaName) { List functions = Lists.newArrayList(); try (ResultSet resultSet = getConnection().getMetaData().getFunctions(databaseName, schemaName, null);) { while (resultSet != null && resultSet.next()) { From de9255bdebe3f110ed27b3ba1d26e20242df9d7c Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 19:34:13 +0800 Subject: [PATCH 0253/1069] feat: improve notification --- .../src/components/Console/index.tsx | 13 +++++-- .../src/components/MyNotification/index.less | 35 +++++++++++++++++-- .../src/components/MyNotification/index.tsx | 9 ++--- chat2db-client/src/constants/chat.ts | 15 ++++++++ chat2db-client/src/i18n/en-us/common.ts | 2 +- chat2db-client/src/layouts/index.tsx | 16 +++++++-- .../src/pages/main/workspace/index.tsx | 12 ++----- 7 files changed, 79 insertions(+), 23 deletions(-) create mode 100644 chat2db-client/src/constants/chat.ts diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 3fe096e48..76175f44c 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -17,6 +17,7 @@ import { IAIState } from '@/models/ai'; import Popularize from '@/components/Popularize'; import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; import styles from './index.less'; +import { chatErrorCodeArr } from '@/constants/chat'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -195,6 +196,14 @@ function Console(props: IProps) { setIsLoading(false); try { + const hasError = chatErrorCodeArr.includes(message); + //TODO: + if (hasError) { + closeEventSource(); + setIsLoading(false); + return + } + const isEOF = message === '[DONE]'; if (isEOF) { closeEventSource(); @@ -306,7 +315,7 @@ function Console(props: IProps) { onPressEnter={onPressChatInput} selectedTables={selectedTables} onSelectTables={(tables: string[]) => { - if(tables.length > 8){ + if (tables.length > 8) { message.warning({ content: i18n('chat.input.tableSelect.error.TooManyTable') }) @@ -332,7 +341,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index f7219294d..698a972e1 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -1,7 +1,38 @@ @import '../../styles/var.less'; -.notification{ - padding: 10px 16px; + +:global { + .ant-notification-notice { + background-color: var(--color-bg-elevated) !important; + padding: 8px 16px !important; + width: 280px !important; + + .ant-notification-notice-icon { + font-size: 16px !important; + top: 12px !important; + } + + .ant-notification-notice-message { + color: var(--color-text) !important; + } + + .ant-notification-notice-close { + color: var(--color-text) !important + } + } + + .ant-modal-content { + background-color: var(--color-bg-elevated) !important; + + .ant-modal-confirm-title { + color: var(--color-text) !important; + } + + .ant-modal-confirm-content { + color: var(--color-text) !important; + + } + } } .message { diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index ceb26d03a..91a361da8 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -1,4 +1,4 @@ -import { Button, Modal, notification, } from 'antd'; +import { Button, ConfigProvider, Modal, notification, } from 'antd'; import React from 'react' import styles from './index.less' import i18n from '@/i18n'; @@ -22,14 +22,12 @@ function MyNotification(props: IProps) { const type = props.type || 'warning'; const title = `${errorCode}:${errorMessage}`; - const message = props.message ||
{errorCode}:{errorMessage}
+ const message = props.message ||
{errorCode}:{errorMessage || 'Error'}
const description =
); -} +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 3b164bf92..dd6e314fc 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,13 +1,11 @@ -import React, { memo, useEffect, useRef, useState, useReducer, useContext } from 'react'; +import React, { memo, useRef, } from 'react'; import { connect } from 'umi'; -import { Spin } from 'antd' import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; -import LoadingContent from '@/components/Loading/LoadingContent' interface IProps { className?: string; @@ -23,14 +21,11 @@ const dvaModel = connect( ); -const workspace = memo((props) =>{ +const workspace = memo((props) => { const draggableRef = useRef(); - const {workspaceModel,connectionModel} = props; - const { curWorkspaceParams } = workspaceModel; - const { curConnection } = connectionModel; + const { workspaceModel, connectionModel } = props; return ( - //
@@ -39,7 +34,6 @@ const workspace = memo((props) =>{
- //
); }); From 5e8b102c81852909023e1aaaea057fb83a08d1de Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 8 Jul 2023 19:57:17 +0800 Subject: [PATCH 0254/1069] =?UTF-8?q?feat:=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 1 + .../CreateConnection/config/dataSource.ts | 48 ++-- .../CreateConnection/config/types.ts | 6 + .../components/CreateConnection/index.less | 31 ++- .../src/components/CreateConnection/index.tsx | 212 ++++++++++++++---- .../src/components/UploadDriver/index.less | 4 + .../src/components/UploadDriver/index.tsx | 68 ++++++ chat2db-client/src/i18n/en-us/connection.ts | 5 + chat2db-client/src/i18n/zh-cn/connection.ts | 4 + chat2db-client/src/service/base.ts | 19 +- chat2db-client/src/service/connection.ts | 31 ++- chat2db-client/src/service/misc.tsx | 2 +- chat2db-client/src/typings/common.ts | 4 +- chat2db-client/src/typings/connection.ts | 4 + 14 files changed, 365 insertions(+), 74 deletions(-) create mode 100644 chat2db-client/src/components/UploadDriver/index.less create mode 100644 chat2db-client/src/components/UploadDriver/index.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 89ee4fa0e..296197400 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "ahooks", "antd", "asar", + "bgcolor", "cascader", "datasource", "echart", diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index dc83fa8bf..41145e500 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -96,23 +96,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ name: 'url', required: true, }, - { - defaultValue: '8.0', - inputType: InputType.SELECT, - labelNameCN: 'JDBC驱动', - labelNameEN: 'JDBC Driver', - name: 'jdbc', - required: true, - selects: [ - { - value: '8.0', - }, - { - value: '5.0', - }, - ], - - } ], pattern: /jdbc:mysql:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:mysql://{host}:{port}/{database}', @@ -3061,3 +3044,34 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ type: DatabaseTypeCode.MONGODB }, ]; + +export const driveConfig: IConnectionConfig['drive'] = { + items: [ + { + defaultValue: '', + inputType: InputType.SELECT, + labelNameCN: 'jdbcDriver', + labelNameEN: 'jdbcDriver', + name: 'jdbcDriver', + required: false, + selects: [ + { + value: '1', + label: '/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/src/components/CreateConnection/config/dataSource.ts' + }, + { + value: '2', + label: '/222222Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/src/components/CreateConnection/config/dataSource.ts' + }, + ], + }, + // { + // defaultValue: '', + // inputType: InputType.INPUT, + // labelNameCN: 'jdbcDriverClass', + // labelNameEN: 'jdbcDriverClass', + // name: 'jdbcDriverClass', + // required: false, + // }, + ] +}; diff --git a/chat2db-client/src/components/CreateConnection/config/types.ts b/chat2db-client/src/components/CreateConnection/config/types.ts index e64dfb275..bda27c528 100644 --- a/chat2db-client/src/components/CreateConnection/config/types.ts +++ b/chat2db-client/src/components/CreateConnection/config/types.ts @@ -4,6 +4,9 @@ import { DatabaseTypeCode, OperationColumn } from '@/constants'; export type ISelect = { value?: AuthenticationType | SSHAuthenticationType | string; label?: string; + rest?: { + [key in string]: any + } items?: IFormItem[]; }; @@ -34,6 +37,9 @@ export type IConnectionConfig = { template: string; excludes?: OperationColumn[]; }, + drive?: { + items: IFormItem[]; + } ssh: { items: IFormItem[]; }, diff --git a/chat2db-client/src/components/CreateConnection/index.less b/chat2db-client/src/components/CreateConnection/index.less index 56915076f..3cadc5797 100644 --- a/chat2db-client/src/components/CreateConnection/index.less +++ b/chat2db-client/src/components/CreateConnection/index.less @@ -13,7 +13,8 @@ } .connectionBox { - width: 100%; + width: 65%; + flex-shrink: 0; padding: 20px 20%; } @@ -110,6 +111,34 @@ } } +.downloadDriveFooter{ + display: flex; + align-items: center; + justify-content: space-between; + + .downloadDrive{ + display: flex; + .downloadText{ + margin-right: 4px; + color: var(--color-primary); + cursor: pointer; + &:hover{ + text-decoration: underline; + } + } + .downloadTextError{ + color: var(--color-error-text); + } + } + + .uploadCustomDrive{ + cursor: pointer; + &:hover{ + color: var(--color-primary); + } + } +} + .extendTable { max-height: 300px; overflow-y: auto; diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index cb2aa9a6e..a4589977e 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -3,21 +3,22 @@ import { i18n, isEn } from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; -import connectionService from '@/service/connection'; +import connectionService, { IDriverResponse } from '@/service/connection'; import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants'; -import { dataSourceFormConfigs } from './config/dataSource'; +import { dataSourceFormConfigs, driveConfig } from './config/dataSource'; import { IConnectionConfig, IFormItem, ISelect } from './config/types'; import { IConnectionDetails } from '@/typings'; import { InputType } from './config/enum'; import { deepClone } from '@/utils'; -import { Select, Form, Input, message, Table, Button, Collapse } from 'antd'; +import { Select, Form, Input, message, Table, Button, Collapse, Modal } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; +import UploadDriver from '@/components/UploadDriver'; const { Option } = Select; -type ITabsType = 'ssh' | 'baseInfo'; +type ITabsType = 'ssh' | 'baseInfo' | 'drive'; export enum submitType { UPDATE = 'update', @@ -32,18 +33,44 @@ interface IProps { submitCallback?: Function; } +enum DownloadStatus { + Default, + Loading, + Error, + Success +} + export default function CreateConnection(props: IProps) { - const { className, closeCreateConnection, submitCallback } = props; + const { className, closeCreateConnection, submitCallback, connectionData } = props; const [baseInfoForm] = Form.useForm(); const [sshForm] = Form.useForm(); - const [backfillData, setBackfillData] = useState(props.connectionData); + const [driveForm] = Form.useForm(); + const [backfillData, setBackfillData] = useState(connectionData); const [loadings, setLoading] = useState({ confirmButton: false, testButton: false, backfillDataLoading: false }); - // const [connectionData, setConnectionData] = useState(props.connectionData); - // const [currentType, setCurrentType] = useState(createType || DatabaseTypeCode.MYSQL); + const [downloadStatus, setDownloadStatus] = useState(DownloadStatus.Default); + const [uploadDriverModal, setUploadDriverModal] = useState(false); + const [driverObj, setDriverObj] = useState(); + const [driverSaved, setDriverSaved] = useState({}); + const dataSourceFormConfigPropsMemo = useMemo(() => { + const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) + return deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { + const flag = t.type === backfillData.type; + if (flag) { + t.drive = driveConfig; + } + return flag + }); + }, []); + + useEffect(() => { + getDriverList() + }, [backfillData.type]) + + const [dataSourceFormConfigProps, setDataSourceFormConfigProps] = useState(dataSourceFormConfigPropsMemo); useEffect(() => { setBackfillData(props.connectionData); @@ -55,6 +82,26 @@ export default function CreateConnection(props: IProps) { } }, [backfillData.id]); + useEffect(() => { + if (driverObj?.driverConfigList?.length) { + const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) + const newDataSourceFormConfigProps = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { + const flag = t.type === backfillData.type; + if (flag) { + t.drive = driveConfig; + } + return flag + }); + newDataSourceFormConfigProps.drive!.items[0].selects = driverObj?.driverConfigList.map(t => { + return { + value: t.jdbcDriver, + label: t.jdbcDriver + } + }) + setDataSourceFormConfigProps(newDataSourceFormConfigProps) + } + }, [driverObj]) + function getConnectionDetails(id: number) { setLoading({ ...loadings, @@ -81,12 +128,54 @@ export default function CreateConnection(props: IProps) { } const getItems = () => [ + { + key: 'drive', + label: 'Drive', + children: ( +
+ +
+ { + !!driverObj?.driverConfigList?.length ?
:
+ { + (downloadStatus === DownloadStatus.Default) &&
Download
+ } + { + (downloadStatus === DownloadStatus.Loading) &&
Downloading
+ } + { + (downloadStatus === DownloadStatus.Error) &&
Try again download
+ } + {i18n('connection.title.driver')} +
+ } + +
{ setUploadDriverModal(true) }} + > + {i18n('connection.tips.customUpload')} +
+
+
+ ), + }, { key: 'ssh', label: 'SSH Configuration', children: (
- +
{i18n('connection.message.testSshConnection')} @@ -109,6 +198,7 @@ export default function CreateConnection(props: IProps) { // 测试、保存、修改连接 function saveConnection(type: submitType) { const ssh = sshForm.getFieldsValue(); + const driverConfig = driveForm.getFieldsValue() const baseInfo = baseInfoForm.getFieldsValue(); const extendInfo: any = []; const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton'; @@ -123,10 +213,10 @@ export default function CreateConnection(props: IProps) { let p: any = { ssh, + driverConfig, ...baseInfo, extendInfo, - // ...values, - ConnectionEnvType: ConnectionEnvType.DAILY, + connectionEnvType: ConnectionEnvType.DAILY, type: backfillData.type, }; @@ -185,6 +275,32 @@ export default function CreateConnection(props: IProps) { }); } + function downloadDrive() { + setDownloadStatus(DownloadStatus.Loading) + connectionService.downloadDriver({ dbType: backfillData.type }).then(res => { + setDownloadStatus(DownloadStatus.Success) + }).catch(() => { + setDownloadStatus(DownloadStatus.Error) + }) + } + + function getDriverList() { + connectionService.getDriverList({ dbType: backfillData.type }).then(res => { + setDriverObj(res) + }) + } + + function formChange(data: any) { + setDriverSaved(data) + } + + function saveDriver() { + connectionService.saveDriver(driverSaved).then(res => { + setUploadDriverModal(false) + getDriverList() + }) + } + return (
@@ -194,9 +310,9 @@ export default function CreateConnection(props: IProps) {
{databaseMap[backfillData.type]?.name}
- +
- +
{ @@ -225,6 +341,19 @@ export default function CreateConnection(props: IProps) {
+ { saveDriver() }} + onCancel={() => { setUploadDriverModal(false) }} + > + +
); } @@ -233,29 +362,25 @@ interface IRenderFormProps { tab: ITabsType; form: any; backfillData: IConnectionDetails; + dataSourceFormConfigProps: IConnectionConfig } function RenderForm(props: IRenderFormProps) { - const { tab, form, backfillData } = props; - const editId = backfillData.id; - const databaseType = backfillData.type; + const { tab, form, backfillData, dataSourceFormConfigProps } = props; + useEffect(() => { + form.resetFields() + }, [backfillData.id, backfillData.type]) let aliasChanged = false; - const dataSourceFormConfigMemo = useMemo(() => { - return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { - return t.type === databaseType; - }); - }, [databaseType]); - - const [dataSourceFormConfig, setDataSourceFormConfig] = useState(dataSourceFormConfigMemo); + const [dataSourceFormConfig, setDataSourceFormConfig] = useState(dataSourceFormConfigProps); useEffect(() => { - setDataSourceFormConfig(dataSourceFormConfigMemo); - }, [databaseType]); + setDataSourceFormConfig(dataSourceFormConfigProps) + }, [dataSourceFormConfigProps]) const initialValuesMemo = useMemo(() => { - return initialFormData(dataSourceFormConfigMemo[tab].items); + return initialFormData(dataSourceFormConfigProps[tab]?.items); }, []); const [initialValues] = useState(initialValuesMemo); @@ -273,6 +398,9 @@ function RenderForm(props: IRenderFormProps) { if (tab === 'ssh') { regEXFormatting({}, backfillData.ssh || {}); } + if (tab === 'drive') { + regEXFormatting({}, backfillData.driverConfig || {}); + } }, [backfillData]); function initialFormData(dataSourceFormConfig: IFormItem[] | undefined) { @@ -294,7 +422,7 @@ function RenderForm(props: IRenderFormProps) { } function selectChange(t: { name: string; value: any }) { - dataSourceFormConfig[tab].items.map((j, i) => { + dataSourceFormConfig[tab]?.items.map((j, i) => { if (j.name === t.name) { j.defaultValue = t.value; } @@ -366,6 +494,7 @@ function RenderForm(props: IRenderFormProps) { if (keyName === 'host' && !aliasChanged) { newData.alias = '@' + keyValue; } + form.setFieldsValue({ ...dataObj, ...newData, @@ -458,23 +587,33 @@ function RenderForm(props: IRenderFormProps) { ); } + interface IRenderExtendTableProps { backfillData: IConnectionDetails; } let extendTableData: any = []; +interface IExtendTable { + key: number, + label: string, + value: string +} + function RenderExtendTable(props: IRenderExtendTableProps) { const { backfillData } = props; const databaseType = backfillData.type; + const [data, setData] = useState([{ key: 0, label: '', value: '' }]); const dataSourceFormConfigMemo = useMemo(() => { return deepClone(dataSourceFormConfigs).find((t: IConnectionConfig) => { return t.type === databaseType; }); }, [backfillData.type]); - const extendInfo = - dataSourceFormConfigMemo.extendInfo?.map((t, i) => { + useEffect(() => { + const extendInfoList = backfillData?.extendInfo?.length ? backfillData?.extendInfo : dataSourceFormConfigMemo.extendInfo; + + const extendInfo = extendInfoList?.map((t, i) => { return { key: i, label: t.key, @@ -482,19 +621,8 @@ function RenderExtendTable(props: IRenderExtendTableProps) { }; }) || []; - const [data, setData] = useState([...extendInfo, { key: extendInfo.length, label: '', value: '' }]); - - useEffect(() => { - const backfillDataExtendInfo = - (backfillData?.extendInfo || []).map((t, i) => { - return { - key: i, - label: t.key, - value: t.value, - }; - }) || []; - setData([...backfillDataExtendInfo, { key: extendInfo.length, label: '', value: '' }]); - }, [backfillData]); + setData([...extendInfo, { key: extendInfo.length, label: '', value: '' }]) + }, [dataSourceFormConfigMemo, backfillData]) useEffect(() => { extendTableData = data; diff --git a/chat2db-client/src/components/UploadDriver/index.less b/chat2db-client/src/components/UploadDriver/index.less new file mode 100644 index 000000000..2e57988d1 --- /dev/null +++ b/chat2db-client/src/components/UploadDriver/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/components/UploadDriver/index.tsx b/chat2db-client/src/components/UploadDriver/index.tsx new file mode 100644 index 000000000..cf91da5c8 --- /dev/null +++ b/chat2db-client/src/components/UploadDriver/index.tsx @@ -0,0 +1,68 @@ +import React, { memo, useEffect, useState, useRef } from 'react'; +import i18n from '@/i18n' +import styles from './index.less'; +import classnames from 'classnames'; +import { Button, message, Upload, Form, Input } from 'antd'; +import type { UploadProps } from 'antd'; +import connectionService from '@/service/connection'; +import { DatabaseTypeCode } from '@/constants' + +interface IProps { + className?: string; + databaseType: DatabaseTypeCode; + formChange: Function; + jdbcDriverClass: string | undefined; +} + +export default memo(function UploadDriver(props) { + const { className, databaseType = DatabaseTypeCode.MYSQL, formChange, jdbcDriverClass } = props; + const [formData, setFormData] = useState({ + dbType: databaseType, + jdbcDriverClass: jdbcDriverClass, + jdbcDriver: [] + }); + + const uploadProps: UploadProps = { + name: 'multipartFiles', + action: `${window._BaseURL}/api/jdbc/driver/upload`, + multiple: true, + onChange(info) { + if (info.file.percent === 100 && info.file?.response?.data?.[0]) { + setFormData({ + ...formData, + jdbcDriver: [...(formData.jdbcDriver), info.file?.response?.data?.[0]] + }) + } + }, + }; + + useEffect(() => { + formChange(formData) + }, [formData]) + + function onChange(e: any) { + setFormData({ + ...formData, + jdbcDriverClass: e.target.value + }) + } + + return
+
+
+ + + + + + + + + +
+
+}) diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 3027aa320..829fce8be 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -17,4 +17,9 @@ export default { 'connection.message.testSshConnection': 'Test the ssh connection', 'connection.tableHeader.name': 'Name', 'connection.tableHeader.value': 'Value', + 'connection.title.uploadDriver': 'Upload Driver', + 'connection.tips.customUpload': "I don't have the drive I want", + 'connection.title.driver': 'Driver', + 'connection.button.clickUpload': 'Click to Upload', + }; \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 94276f73a..1792d8675 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -17,4 +17,8 @@ export default { 'connection.message.testSshConnection': '测试ssh连接', 'connection.tableHeader.name': '名称', 'connection.tableHeader.value': '值', + 'connection.title.uploadDriver': '上传驱动', + 'connection.tips.customUpload': '没有我想要的驱动', + 'connection.title.driver': '驱动', + 'connection.button.clickUpload': '点击上传', } diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 22c0c849c..3fd588c22 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,7 +1,6 @@ import { extend, ResponseError } from 'umi-request'; import { message, notification } from 'antd'; import { getLang } from '@/utils/localStorage'; -import ErrorNotification from '@/components/MyNotification'; const path = require('path'); export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; @@ -128,9 +127,8 @@ request.interceptors.response.use(async (response, options) => { export default function createRequest

(url: string, options?: IOptions) { const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside } = options || {}; - // 是否需要mock - let _baseURL = (mock ? mockUrl : baseURL) || ''; + let _baseURL = mock ? mockUrl : baseURL; return function (params: P) { // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; @@ -169,16 +167,15 @@ export default function createRequest

(url: string, options?: I request[method](eventualUrl, { [dataName]: params }) .then((res) => { if (!res) return; - const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; + const { success, errorCode, errorMessage, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { - ErrorNotification({ - errorCode, - errorMessage, - errorDetail, - solutionLink, - }) - // message.error(`${errorCode}: ${errorMessage}`); + // notification.open({ + // type: 'error', + // message: errorCode, + // description: errorMessage, + // }); + message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index b3b9ca2c2..1a33603fa 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,6 +1,6 @@ import { IPageResponse, IConnectionDetails } from '@/typings'; +import { DatabaseTypeCode } from '@/constants'; import createRequest from './base'; -// import { IPageResponse, IConnectionDetails, IDB } from '@/types'; export interface IGetConnectionParams { searchKey?: string; @@ -60,6 +60,32 @@ const getDBList = createRequest<{ id: number }, IDB[]>( { 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' }); +const downloadDriver = createRequest<{ dbType: string }, void>('/api/jdbc/driver/download', { errorLevel: false, method: 'get' }); +const saveDriver = createRequest('/api/jdbc/driver/save', { errorLevel: false, method: 'post' }); + export default { getList, getDetails, @@ -71,4 +97,7 @@ export default { getDBList, close, testSSH, + getDriverList, + downloadDriver, + saveDriver, }; diff --git a/chat2db-client/src/service/misc.tsx b/chat2db-client/src/service/misc.tsx index 1114da728..22860ad8c 100644 --- a/chat2db-client/src/service/misc.tsx +++ b/chat2db-client/src/service/misc.tsx @@ -6,5 +6,5 @@ const testApiSmooth = createRequest('/api/system/get-version-a', { e export default { testService, systemStop, - testApiSmooth + testApiSmooth, } \ No newline at end of file diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 943f71df6..1035867d2 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -48,7 +48,7 @@ export interface IUniversalTableParams { * 版本返回 * VersionResponse */ - export interface IVersionResponse { +export interface IVersionResponse { /** * 基础链接 * 类似于:http://test.sqlgpt.cn/gateway @@ -67,3 +67,5 @@ export interface IUniversalTableParams { */ wechatMpName?: string; } + + diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index 97cf1202a..f0e6841f3 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -16,6 +16,10 @@ export interface IConnectionDetails { EnvType: ConnectionEnv; extendInfo: IConnectionExtendInfoItem[]; ssh: any; + driverConfig: { + jdbcDriver: string; + jdbcDriverClass: string; + }; [key: string]: any; } From 0f6c1bb813df229fe6333f85a14c8e9e709b430d Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:05:00 +0800 Subject: [PATCH 0255/1069] ai update --- .../server/web/api/controller/ai/ChatController.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 7dfe44e58..249e40431 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -505,10 +505,11 @@ private String buildPrompt(ChatQueryRequest queryRequest) { : queryRequest.getPromptType(); PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType); String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : ""; + String result = "假设你是个SQL编辑器,接下来你返回的SQL代码要和其他内容分隔,非SQL代码内容的每一行前面追加-- \n"; String schemaProperty = CollectionUtils.isNotEmpty(tableSchemas) ? String.format( - "### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# " - + "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType, - properties, prompt) : String.format("### 请根据以下SQL input%s. %s\n#\n### SQL input: %s", + "%s### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# " + + "%s\n#\n#\n### SQL input: %s", result, pType.getDescription(), ext, dataSourceType, + properties, prompt) : String.format("%s### 请根据以下SQL input%s. %s\n#\n### SQL input: %s", result, pType.getDescription(), ext, prompt); switch (pType) { case SQL_2_SQL: @@ -521,7 +522,6 @@ private String buildPrompt(ChatQueryRequest queryRequest) { //if (I18nUtils.isEn()) { // schemaProperty = String.format("%s\n#\n### 返回结果要求为英文", schemaProperty); //} - String result = String.format("%s. \n要求返回Markdown格式", schemaProperty); - return result; + return schemaProperty; } } From 096a0401d6ad07722db44860aeb3cee8dcdc6f47 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:10:40 +0800 Subject: [PATCH 0256/1069] feat: exception optimization --- .../src/components/MyNotification/index.tsx | 1 + chat2db-client/src/service/base.ts | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index 91a361da8..91c31b75e 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -2,6 +2,7 @@ import { Button, ConfigProvider, Modal, notification, } from 'antd'; import React from 'react' import styles from './index.less' import i18n from '@/i18n'; +import { IconType } from 'antd/es/notification/interface'; // import { staticNotification } from '@/layouts' interface IProps { diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 3fd588c22..22c0c849c 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,6 +1,7 @@ import { extend, ResponseError } from 'umi-request'; import { message, notification } from 'antd'; import { getLang } from '@/utils/localStorage'; +import ErrorNotification from '@/components/MyNotification'; const path = require('path'); export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; @@ -127,8 +128,9 @@ request.interceptors.response.use(async (response, options) => { export default function createRequest

(url: string, options?: IOptions) { const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside } = options || {}; + // 是否需要mock - let _baseURL = mock ? mockUrl : baseURL; + let _baseURL = (mock ? mockUrl : baseURL) || ''; return function (params: P) { // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; @@ -167,15 +169,16 @@ export default function createRequest

(url: string, options?: I request[method](eventualUrl, { [dataName]: params }) .then((res) => { if (!res) return; - const { success, errorCode, errorMessage, data } = res; + const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { - // notification.open({ - // type: 'error', - // message: errorCode, - // description: errorMessage, - // }); - message.error(`${errorCode}: ${errorMessage}`); + ErrorNotification({ + errorCode, + errorMessage, + errorDetail, + solutionLink, + }) + // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); return; From d9b42caddfce633674f4cffcf515d45c444e32d1 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:16:15 +0800 Subject: [PATCH 0257/1069] feat: optimize style --- chat2db-client/src/components/Console/index.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.less b/chat2db-client/src/components/Console/index.less index 85356cd73..8ae1d7edc 100644 --- a/chat2db-client/src/components/Console/index.less +++ b/chat2db-client/src/components/Console/index.less @@ -8,7 +8,7 @@ } .ant-spin-container { - height: 100%; + height: calc(100% - 48px); } } From 47a778b83da2d31376f28b1fb5bfb3e5f779e0db Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 8 Jul 2023 20:17:38 +0800 Subject: [PATCH 0258/1069] SSH support --- .../server/start/config/util/SystemUtils.java | 2 +- .../web/api/controller/SystemController.java | 2 +- .../data/source/DataSourceController.java | 2 +- .../ai/chat2db/spi/sql/Chat2DBContext.java | 1 + .../java/ai/chat2db/spi/ssh/MyUserInfo.java | 41 +++++++++++++++++++ .../chat2db/spi/{sql => ssh}/SSHManager.java | 18 +++++--- .../java/ai/chat2db/spi/util/JdbcUtils.java | 2 +- 7 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/MyUserInfo.java rename chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/{sql => ssh}/SSHManager.java (75%) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java index 2f21c3b8d..abb9fdaa9 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java @@ -1,7 +1,7 @@ package ai.chat2db.server.start.config.util; -import ai.chat2db.spi.sql.SSHManager; +import ai.chat2db.spi.ssh.SSHManager; import lombok.extern.slf4j.Slf4j; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java index 9a22a0d70..242fa937c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java @@ -8,7 +8,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.AliDbhubProperties; import ai.chat2db.server.tools.common.util.I18nUtils; -import ai.chat2db.spi.sql.SSHManager; +import ai.chat2db.spi.ssh.SSHManager; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java index 82e4baa77..bc86b91ab 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java @@ -13,7 +13,7 @@ import ai.chat2db.server.domain.api.service.ConsoleService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.spi.model.Database; -import ai.chat2db.spi.sql.SSHManager; +import ai.chat2db.spi.ssh.SSHManager; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; 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 7167ba6f1..bc67f6b89 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 @@ -14,6 +14,7 @@ import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.ssh.SSHManager; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/MyUserInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/MyUserInfo.java new file mode 100644 index 000000000..8633067f7 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/MyUserInfo.java @@ -0,0 +1,41 @@ +package ai.chat2db.spi.ssh; + +import com.jcraft.jsch.UserInfo; + +public class MyUserInfo implements UserInfo { + + private String passphrase; + + public MyUserInfo(String passphrase) { + this.passphrase = passphrase; + } + @Override + public String getPassphrase() { + return passphrase; + } + + @Override + public String getPassword() { + return null; + } + + @Override + public boolean promptPassword(String s) { + return true; + } + + @Override + public boolean promptPassphrase(String s) { + return true; + } + + @Override + public boolean promptYesNo(String s) { + return true; + } + + @Override + public void showMessage(String s) { + System.out.println(s); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java similarity index 75% rename from chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java rename to chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java index 38d638047..506cd0ca0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SSHManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java @@ -1,9 +1,10 @@ -package ai.chat2db.spi.sql; +package ai.chat2db.spi.ssh; import java.util.concurrent.ConcurrentHashMap; import ai.chat2db.spi.model.SSHInfo; +import cn.hutool.core.net.NetUtil; import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; @@ -35,8 +36,14 @@ private static Session createSession(SSHInfo ssh) { } try { JSch jSch = new JSch(); + if (StringUtils.isNotBlank(ssh.getKeyFile()) && StringUtils.isNotBlank(ssh.getPassphrase())) { + jSch.addIdentity(ssh.getKeyFile(), ssh.getPassphrase()); + } session = jSch.getSession(ssh.getUserName(), ssh.getHostName(), Integer.parseInt(ssh.getPort())); - session.setPassword(ssh.getPassword()); + if (StringUtils.isBlank(ssh.getKeyFile()) || StringUtils.isBlank(ssh.getPassphrase())) { + session.setPassword(ssh.getPassword()); + } + session.setConfig("StrictHostKeyChecking", "no"); session.connect(); SSH_SESSION_MAP.put(ssh, session); @@ -44,10 +51,11 @@ private static Session createSession(SSHInfo ssh) { throw new RuntimeException("create ssh session error", e); } - if (StringUtils.isNotBlank(ssh.getLocalPort()) && StringUtils.isNotBlank(ssh.getRHost()) - && StringUtils.isNotBlank(ssh.getRPort())) { + if (StringUtils.isNotBlank(ssh.getRHost()) && StringUtils.isNotBlank(ssh.getRPort())) { try { - int port1 = session.setPortForwardingL(Integer.parseInt(ssh.getLocalPort()), ssh.getRHost(), + int localPort = !StringUtils.isBlank(ssh.getLocalPort()) ? Integer.parseInt(ssh.getLocalPort()) + : NetUtil.getUsableLocalPort(); + session.setPortForwardingL(localPort, ssh.getRHost(), Integer.parseInt(ssh.getRPort())); } catch (Exception e) { if (session != null && session.isConnected()) { 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 b79e6ca61..2cf030597 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 @@ -20,7 +20,7 @@ import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.sql.IDriverManager; -import ai.chat2db.spi.sql.SSHManager; +import ai.chat2db.spi.ssh.SSHManager; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; From 3abf87d008697a667d7c0a89a58d1a510fc90545 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 8 Jul 2023 20:20:51 +0800 Subject: [PATCH 0259/1069] style --- .../CreateConnection/config/dataSource.ts | 2 +- .../CreateConnection/config/types.ts | 2 +- .../components/CreateConnection/index.less | 9 ++++-- .../src/components/CreateConnection/index.tsx | 30 +++++++++++-------- chat2db-client/src/i18n/en-us/connection.ts | 6 ++-- chat2db-client/src/i18n/zh-cn/connection.ts | 4 ++- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index 41145e500..5c4384044 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -3045,7 +3045,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ }, ]; -export const driveConfig: IConnectionConfig['drive'] = { +export const driveConfig: IConnectionConfig['driver'] = { items: [ { defaultValue: '', diff --git a/chat2db-client/src/components/CreateConnection/config/types.ts b/chat2db-client/src/components/CreateConnection/config/types.ts index bda27c528..d7b8d2173 100644 --- a/chat2db-client/src/components/CreateConnection/config/types.ts +++ b/chat2db-client/src/components/CreateConnection/config/types.ts @@ -37,7 +37,7 @@ export type IConnectionConfig = { template: string; excludes?: OperationColumn[]; }, - drive?: { + driver?: { items: IFormItem[]; } ssh: { diff --git a/chat2db-client/src/components/CreateConnection/index.less b/chat2db-client/src/components/CreateConnection/index.less index 3cadc5797..9a28733c2 100644 --- a/chat2db-client/src/components/CreateConnection/index.less +++ b/chat2db-client/src/components/CreateConnection/index.less @@ -121,14 +121,17 @@ .downloadText{ margin-right: 4px; color: var(--color-primary); + } + .downloadTextDownload{ cursor: pointer; - &:hover{ - text-decoration: underline; - } } .downloadTextError{ color: var(--color-error-text); } + + .downloadTextSuccess{ + color: var(--color-success-text); + } } .uploadCustomDrive{ diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index a4589977e..872a5cb3e 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -18,7 +18,7 @@ import UploadDriver from '@/components/UploadDriver'; const { Option } = Select; -type ITabsType = 'ssh' | 'baseInfo' | 'drive'; +type ITabsType = 'ssh' | 'baseInfo' | 'driver'; export enum submitType { UPDATE = 'update', @@ -60,7 +60,7 @@ export default function CreateConnection(props: IProps) { return deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { const flag = t.type === backfillData.type; if (flag) { - t.drive = driveConfig; + t.driver = driveConfig; } return flag }); @@ -88,11 +88,11 @@ export default function CreateConnection(props: IProps) { const newDataSourceFormConfigProps = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { const flag = t.type === backfillData.type; if (flag) { - t.drive = driveConfig; + t.driver = driveConfig; } return flag }); - newDataSourceFormConfigProps.drive!.items[0].selects = driverObj?.driverConfigList.map(t => { + newDataSourceFormConfigProps.driver!.items[0].selects = driverObj?.driverConfigList.map(t => { return { value: t.jdbcDriver, label: t.jdbcDriver @@ -129,21 +129,21 @@ export default function CreateConnection(props: IProps) { const getItems = () => [ { - key: 'drive', - label: 'Drive', + key: 'driver', + label: 'Driver', children: (

{ - !!driverObj?.driverConfigList?.length ?
:
+ (!driverObj?.driverConfigList?.length || downloadStatus === DownloadStatus.Success) ?
{ - (downloadStatus === DownloadStatus.Default) &&
Download
+ (downloadStatus === DownloadStatus.Default) &&
{i18n('connection.text.downloadDriver')}
} { (downloadStatus === DownloadStatus.Loading) &&
Downloading
@@ -151,8 +151,11 @@ export default function CreateConnection(props: IProps) { { (downloadStatus === DownloadStatus.Error) &&
Try again download
} - {i18n('connection.title.driver')} -
+ { + (downloadStatus === DownloadStatus.Success) &&
{i18n('connection.text.downloadSuccess')}
+ } + +
:
}
{ setDownloadStatus(DownloadStatus.Success) + getDriverList(); }).catch(() => { setDownloadStatus(DownloadStatus.Error) }) @@ -312,7 +316,7 @@ export default function CreateConnection(props: IProps) {
- +
{ @@ -398,7 +402,7 @@ function RenderForm(props: IRenderFormProps) { if (tab === 'ssh') { regEXFormatting({}, backfillData.ssh || {}); } - if (tab === 'drive') { + if (tab === 'driver') { regEXFormatting({}, backfillData.driverConfig || {}); } }, [backfillData]); diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 829fce8be..11f3817dc 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -6,7 +6,7 @@ export default { 'connection.label.host': 'host', 'connection.label.authentication': 'authentication', 'connection.label.database': 'database', - 'connection.label.JDBCDrive': 'JDBC Drive', + 'connection.label.JDBCDrive': 'JDBC Driver', 'connection.label.port': 'port', 'connection.button.testConnection': 'Test', 'connection.label.advancedConfiguration': 'Advanced Configuration', @@ -18,8 +18,10 @@ export default { 'connection.tableHeader.name': 'Name', 'connection.tableHeader.value': 'Value', 'connection.title.uploadDriver': 'Upload Driver', - 'connection.tips.customUpload': "I don't have the drive I want", + 'connection.tips.customUpload': "Upload driver", 'connection.title.driver': 'Driver', 'connection.button.clickUpload': 'Click to Upload', + 'connection.text.downloadDriver': 'Download Driver', + 'connection.text.downloadSuccess': 'Download Success', }; \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 1792d8675..3aa3accf9 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -18,7 +18,9 @@ export default { 'connection.tableHeader.name': '名称', 'connection.tableHeader.value': '值', 'connection.title.uploadDriver': '上传驱动', - 'connection.tips.customUpload': '没有我想要的驱动', + 'connection.tips.customUpload': '上传驱动', 'connection.title.driver': '驱动', 'connection.button.clickUpload': '点击上传', + 'connection.text.downloadDriver': '下载驱动', + 'connection.text.downloadSuccess': '下载成功', } From c5d2f1ff9f97ff813b2812e246a92b9e4fdec85c Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:25:19 +0800 Subject: [PATCH 0260/1069] feat: optimize style --- chat2db-client/src/components/MyNotification/index.less | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index 698a972e1..cb19283c3 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -13,6 +13,7 @@ } .ant-notification-notice-message { + font-size: 14px; color: var(--color-text) !important; } From 943cd870972e2dfd4d0875fbde5f5391cbefdf50 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:26:32 +0800 Subject: [PATCH 0261/1069] feat: optimize style --- chat2db-client/src/components/MyNotification/index.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index cb19283c3..2df450b54 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -4,7 +4,7 @@ :global { .ant-notification-notice { background-color: var(--color-bg-elevated) !important; - padding: 8px 16px !important; + padding: 8px 8px 8px 16px!important width: 280px !important; .ant-notification-notice-icon { From 985f611f14e4890a9c8a86795896aa4bbac8bbe5 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:27:20 +0800 Subject: [PATCH 0262/1069] feat: optimize style --- chat2db-client/src/components/MyNotification/index.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index 2df450b54..bc2e6b64b 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -4,7 +4,7 @@ :global { .ant-notification-notice { background-color: var(--color-bg-elevated) !important; - padding: 8px 8px 8px 16px!important + padding: 8px 8px 8px 16px !important; width: 280px !important; .ant-notification-notice-icon { From 04fcf772ca36f1ce7dbe0982913591131859e2ee Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 8 Jul 2023 20:35:27 +0800 Subject: [PATCH 0263/1069] feat: optimize style --- chat2db-client/src/components/MyNotification/index.less | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index bc2e6b64b..bbb657cf0 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -13,12 +13,16 @@ } .ant-notification-notice-message { - font-size: 14px; + font-size: 14px !important; color: var(--color-text) !important; } .ant-notification-notice-close { - color: var(--color-text) !important + font-size: 12px !important; + color: var(--color-text) !important; + width: 16px !important; + height: 16px !important; + top: 10px !important; } } From ff85a3b2ed077ce52a0958a35f447601d0825a5d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 9 Jul 2023 14:54:08 +0800 Subject: [PATCH 0264/1069] =?UTF-8?q?feat:=E4=B8=8B=E8=BD=BDloading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Driver/index.less | 52 + .../components/Driver/index.tsx | 151 ++ .../components/XXXX_FN/index.less | 4 + .../components/XXXX_FN/index.tsx | 14 + .../CreateConnection/config/dataSource.ts | 1442 ++--------------- .../components/CreateConnection/index.less | 34 +- .../src/components/CreateConnection/index.tsx | 127 +- .../Loading/LoadingGracile/index.less | 133 ++ .../Loading/LoadingGracile/index.tsx | 27 + .../src/components/UploadDriver/index.tsx | 3 +- chat2db-client/src/i18n/en-us/connection.ts | 2 + chat2db-client/src/i18n/zh-cn/connection.ts | 2 + chat2db-client/src/i18n/zh-cn/setting.ts | 2 +- 13 files changed, 565 insertions(+), 1428 deletions(-) create mode 100644 chat2db-client/src/components/CreateConnection/components/Driver/index.less create mode 100644 chat2db-client/src/components/CreateConnection/components/Driver/index.tsx create mode 100644 chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.less create mode 100644 chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.tsx create mode 100644 chat2db-client/src/components/Loading/LoadingGracile/index.less create mode 100644 chat2db-client/src/components/Loading/LoadingGracile/index.tsx diff --git a/chat2db-client/src/components/CreateConnection/components/Driver/index.less b/chat2db-client/src/components/CreateConnection/components/Driver/index.less new file mode 100644 index 000000000..b90a3f4b3 --- /dev/null +++ b/chat2db-client/src/components/CreateConnection/components/Driver/index.less @@ -0,0 +1,52 @@ +@import '../../../../styles/var.less'; + +.box { + :global { + .ant-form-item-row{ + display: flex; + } + .ant-form-item-control{ + flex: 1; + } + .ant-form-item-label { + width: 54px; + } + } +} + +.downloadDriveFooter{ + display: flex; + align-items: center; + justify-content: space-between; + + .downloadDrive{ + display: flex; + .downloadText{ + margin-right: 4px; + color: var(--color-primary); + } + .downloadTextDownload{ + cursor: pointer; + } + .downloadTextError{ + color: var(--color-error-text); + } + .downloadTextLoading{ + display: flex; + align-items: center; + .text{ + margin-left: 4px; + } + } + .downloadTextSuccess{ + color: var(--color-success-text); + } + } + + .uploadCustomDrive{ + cursor: pointer; + &:hover{ + color: var(--color-primary); + } + } +} diff --git a/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx b/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx new file mode 100644 index 000000000..cceef6d25 --- /dev/null +++ b/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx @@ -0,0 +1,151 @@ +import React, { memo, useState, useEffect } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { i18n, isEn } from '@/i18n'; +import { Form, Modal, Input, Select, Spin } from 'antd'; +import connectionService, { IDriverResponse } from '@/service/connection'; +import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants'; +import UploadDriver from '@/components/UploadDriver'; +import LoadingGracile from '@/components/Loading/LoadingGracile'; +const { Option } = Select; + +interface IProps { + className?: string; + onChange: (data: any) => void; + backfillData: any; +} + +enum DownloadStatus { + Default, + Loading, + Error, + Success +} + +export default memo(function Driver(props) { + const { className, backfillData, onChange } = props; + const [downloadStatus, setDownloadStatus] = useState(); + const [driveForm] = Form.useForm(); + const [driverObj, setDriverObj] = useState(); + const [uploadDriverModal, setUploadDriverModal] = useState(false); + const [driverSaved, setDriverSaved] = useState({}); + + useEffect(() => { + if (backfillData) { + getDriverList(); + } + }, [backfillData?.type]) + + useEffect(() => { + if (backfillData) { + driveForm.setFieldsValue({ + jdbcDriverClass: backfillData?.driverConfig?.jdbcDriverClass, + jdbcDriver: backfillData?.driverConfig?.jdbcDriver + }) + } + }, [backfillData?.driverConfig, backfillData?.id]) + + function getDriverList() { + connectionService.getDriverList({ dbType: backfillData.type }).then(res => { + setDriverObj(res) + }) + } + + function formChange(data: any) { + setDriverSaved(data); + } + + function saveDriver() { + connectionService.saveDriver(driverSaved).then(res => { + setUploadDriverModal(false); + getDriverList(); + }) + } + + function downloadDrive() { + setDownloadStatus(DownloadStatus.Loading) + connectionService.downloadDriver({ dbType: backfillData.type }).then(res => { + // setDownloadStatus(DownloadStatus.Success) + getDriverList(); + }).catch(() => { + setDownloadStatus(DownloadStatus.Error) + }) + } + + function onValuesChange(data: any) { + const selected = driverObj?.driverConfigList.find(t => t.jdbcDriver === data.jdbcDriver); + driveForm.setFieldsValue({ + jdbcDriverClass: selected?.jdbcDriverClass + }); + onChange({ + jdbcDriverClass: selected?.jdbcDriverClass, + jdbcDriver: data.jdbcDriver + }) + } + + return
+
+ + + + + + + +
+ { + (!driverObj?.driverConfigList?.length || downloadStatus === DownloadStatus.Success) ?
+ { + (downloadStatus === DownloadStatus.Default) &&
{i18n('connection.text.downloadDriver')}
+ } + { + (downloadStatus === DownloadStatus.Loading) && +
+ +
+ {i18n('connection.text.downloading')} +
+
+ } + { + (downloadStatus === DownloadStatus.Error) &&
{i18n('connection.text.tryAgainDownload')}
+ } + { + (downloadStatus === DownloadStatus.Success) &&
{i18n('connection.text.downloadSuccess')}
+ } + +
:
+ } + +
{ setUploadDriverModal(true) }} + > + {i18n('connection.tips.customUpload')} +
+
+ { saveDriver() }} + onCancel={() => { setUploadDriverModal(false) }} + > + + +
+}) diff --git a/chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.less b/chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.less new file mode 100644 index 000000000..2e57988d1 --- /dev/null +++ b/chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.tsx b/chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.tsx new file mode 100644 index 000000000..ff47777ba --- /dev/null +++ b/chat2db-client/src/components/CreateConnection/components/XXXX_FN/index.tsx @@ -0,0 +1,14 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; + +interface IProps { + className?: string; +} + +export default memo(function XXXX_FN(props) { + const { className } = props + return
+ +
+}) diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index 5c4384044..00e8c935b 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -2,6 +2,86 @@ import { DatabaseTypeCode } from '@/constants'; import { IConnectionConfig } from './types'; import { InputType, AuthenticationType, SSHAuthenticationType, OperationColumn } from './enum'; +export const sshConfig: IConnectionConfig['ssh'] = { + items: [ + { + defaultValue: 'false', + inputType: InputType.SELECT, + labelNameCN: '使用SSH', + labelNameEN: 'USE SSH', + name: 'use', + required: false, + selects: [ + { + value: 'false', + }, + { + value: 'true', + }, + ], + + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 主机', + labelNameEN: 'SSH Hostname', + name: 'hostName', + required: false, + styles: { + width: '70%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: 'SSH 端口', + labelNameEN: 'Port', + name: 'port', + required: false, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '70px', + labelAlign: 'right' + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'SSH UserName', + name: 'userName', + required: false, + styles: { + width: '70%', + + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '本地端口', + labelNameEN: 'LocalPort', + name: 'localPort', + required: false, + styles: { + width: '30%', + labelAlign: 'right' + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + + }, + ] +} + export const dataSourceFormConfigs: IConnectionConfig[] = [ // MYSQL { @@ -100,85 +180,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:mysql:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:mysql://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - }, + ssh: sshConfig, extendInfo: [ { "key": "zeroDateTimeBehavior", @@ -508,85 +510,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:oracle:(.*):@(.*):(\d+):(.*)/, template: 'jdbc:oracle:{driver}:@{host}:{port}:{sid}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - }, + ssh: sshConfig, }, // H2 { @@ -695,134 +619,56 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:h2:tcp://{host}:{port}/{database}', }, - ssh: { + ssh: sshConfig, + }, + // SQLSERVER encrypt=true;trustServerCertificate=true;integratedSecurity=false;Trusted_Connection=yes + { + type: DatabaseTypeCode.SQLSERVER, + extendInfo: [ + { + "key": "encrypt", + "value": "true" + }, + { + "key": "trustServerCertificate", + "value": "true" + }, + { + "key": "integratedSecurity", + "value": "false" + }, + { + "key": "Trusted_Connection", + "value": "yes" + }, + ], + baseInfo: { items: [ { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, }, { - defaultValue: '', + defaultValue: 'localhost', inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, styles: { width: '70%', + } }, { - defaultValue: '', + defaultValue: '1433', inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - }, - }, - // SQLSERVER encrypt=true;trustServerCertificate=true;integratedSecurity=false;Trusted_Connection=yes - { - type: DatabaseTypeCode.SQLSERVER, - extendInfo: [ - { - "key": "encrypt", - "value": "true" - }, - { - "key": "trustServerCertificate", - "value": "true" - }, - { - "key": "integratedSecurity", - "value": "false" - }, - { - "key": "Trusted_Connection", - "value": "yes" - }, - ], - baseInfo: { - items: [ - { - defaultValue: '@localhost', - inputType: InputType.INPUT, - labelNameCN: '名称', - labelNameEN: 'Name', - name: 'alias', - required: true, - - }, - { - defaultValue: 'localhost', - inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'Host', - name: 'host', - required: true, - styles: { - width: '70%', - - } - }, - { - defaultValue: '1433', - inputType: InputType.INPUT, - labelNameCN: '端口', + labelNameCN: '端口', labelNameEN: 'Port', name: 'port', labelTextAlign: 'right', @@ -909,85 +755,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:sqlserver:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:sqlserver://{host}:{port};', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - }, + ssh: sshConfig, }, // SQLITE { @@ -1025,85 +793,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:sqlite/, template: 'jdbc:sqlite://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - }, + ssh: sshConfig, }, // MARIADB { @@ -1197,100 +887,22 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Database', name: 'database', required: false, - - }, - { - defaultValue: 'jdbc:mariadb://localhost:3306', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - - }, - ], - pattern: /jdbc:mariadb:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:mariadb://{host}:{port}/{database}', - }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } + }, { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', + defaultValue: 'jdbc:mariadb://localhost:3306', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', required: true, }, - ] + ], + pattern: /jdbc:mariadb:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:mariadb://{host}:{port}/{database}', }, + ssh: sshConfig, }, // CLICKHOUSE { @@ -1400,85 +1012,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ template: 'jdbc:clickhouse://{host}:{port}/{database}', excludes: [OperationColumn.ExportDDL, OperationColumn.CreateTable] //排除掉导出ddl 和 创建表功能 支持的功能见 ./enum.ts => OperationColumn }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - } + ssh: sshConfig, }, // DM { @@ -1584,85 +1118,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:dm:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:dm://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '30%', - labelAlign: 'right' - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - - }, - ] - }, + ssh: sshConfig, extendInfo: [ { "key": "zeroDateTimeBehavior", @@ -1765,103 +1221,26 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ name: 'database', required: false, styles: { - width: '100%', - } - }, - { - defaultValue: 'jdbc:db2://localhost:50000', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /jdbc:db2:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:db2://{host}:{port}/{database}', - }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', + width: '100%', } }, { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', + defaultValue: 'jdbc:db2://localhost:50000', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', required: true, styles: { width: '100%', } }, - ] + + ], + pattern: /jdbc:db2:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:db2://{host}:{port}/{database}', }, + ssh: sshConfig, extendInfo: [ ], @@ -1980,84 +1359,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:presto:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:presto://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, + ssh: sshConfig, extendInfo: [ ], @@ -2176,84 +1478,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:oceanbase:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:oceanbase://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, + ssh: sshConfig, extendInfo: [ ], @@ -2353,103 +1578,26 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ name: 'database', required: false, styles: { - width: '100%', - } - }, - { - defaultValue: 'jdbc:redis://localhost:6379', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:redis://{host}:{port}/{database}', - }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', + width: '100%', + } + }, + { + defaultValue: 'jdbc:redis://localhost:6379', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', required: true, styles: { width: '100%', } }, - ] + + ], + pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:redis://{host}:{port}/{database}', }, + ssh: sshConfig, extendInfo: [ ], @@ -2568,84 +1716,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:hive2://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, + ssh: sshConfig, extendInfo: [ ], @@ -2764,84 +1835,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /jdbc:kingbase8:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:kingbase8://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, + ssh: sshConfig, extendInfo: [ ], @@ -2960,118 +1954,10 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, template: 'mongodb://{host}:{port}/{database}', }, - ssh: { - items: [ - { - defaultValue: 'false', - inputType: InputType.SELECT, - labelNameCN: '使用SSH', - labelNameEN: 'USE SSH', - name: 'use', - required: false, - selects: [ - { - value: 'false', - }, - { - value: 'true', - }, - ], - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 主机', - labelNameEN: 'SSH Hostname', - name: 'hostName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: 'SSH 端口', - labelNameEN: 'Port', - name: 'port', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'SSH UserName', - name: 'userName', - required: false, - styles: { - width: '70%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '本地端口', - labelNameEN: 'LocalPort', - name: 'localPort', - required: false, - styles: { - width: '28%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ] - }, + ssh: sshConfig, extendInfo: [ ], type: DatabaseTypeCode.MONGODB }, ]; - -export const driveConfig: IConnectionConfig['driver'] = { - items: [ - { - defaultValue: '', - inputType: InputType.SELECT, - labelNameCN: 'jdbcDriver', - labelNameEN: 'jdbcDriver', - name: 'jdbcDriver', - required: false, - selects: [ - { - value: '1', - label: '/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/src/components/CreateConnection/config/dataSource.ts' - }, - { - value: '2', - label: '/222222Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/src/components/CreateConnection/config/dataSource.ts' - }, - ], - }, - // { - // defaultValue: '', - // inputType: InputType.INPUT, - // labelNameCN: 'jdbcDriverClass', - // labelNameEN: 'jdbcDriverClass', - // name: 'jdbcDriverClass', - // required: false, - // }, - ] -}; diff --git a/chat2db-client/src/components/CreateConnection/index.less b/chat2db-client/src/components/CreateConnection/index.less index 9a28733c2..75a2d7a41 100644 --- a/chat2db-client/src/components/CreateConnection/index.less +++ b/chat2db-client/src/components/CreateConnection/index.less @@ -13,7 +13,8 @@ } .connectionBox { - width: 65%; + width: 100%; + box-sizing: border-box; flex-shrink: 0; padding: 20px 20%; } @@ -111,37 +112,6 @@ } } -.downloadDriveFooter{ - display: flex; - align-items: center; - justify-content: space-between; - - .downloadDrive{ - display: flex; - .downloadText{ - margin-right: 4px; - color: var(--color-primary); - } - .downloadTextDownload{ - cursor: pointer; - } - .downloadTextError{ - color: var(--color-error-text); - } - - .downloadTextSuccess{ - color: var(--color-success-text); - } - } - - .uploadCustomDrive{ - cursor: pointer; - &:hover{ - color: var(--color-primary); - } - } -} - .extendTable { max-height: 300px; overflow-y: auto; diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 872a5cb3e..86562d2b7 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -6,7 +6,7 @@ import classnames from 'classnames'; import connectionService, { IDriverResponse } from '@/service/connection'; import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants'; -import { dataSourceFormConfigs, driveConfig } from './config/dataSource'; +import { dataSourceFormConfigs } from './config/dataSource'; import { IConnectionConfig, IFormItem, ISelect } from './config/types'; import { IConnectionDetails } from '@/typings'; import { InputType } from './config/enum'; @@ -14,7 +14,7 @@ import { deepClone } from '@/utils'; import { Select, Form, Input, message, Table, Button, Collapse, Modal } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; -import UploadDriver from '@/components/UploadDriver'; +import Driver from './components/Driver'; const { Option } = Select; @@ -44,32 +44,21 @@ export default function CreateConnection(props: IProps) { const { className, closeCreateConnection, submitCallback, connectionData } = props; const [baseInfoForm] = Form.useForm(); const [sshForm] = Form.useForm(); - const [driveForm] = Form.useForm(); + const [driveData, setDriveData] = useState({}); const [backfillData, setBackfillData] = useState(connectionData); const [loadings, setLoading] = useState({ confirmButton: false, testButton: false, backfillDataLoading: false }); - const [downloadStatus, setDownloadStatus] = useState(DownloadStatus.Default); - const [uploadDriverModal, setUploadDriverModal] = useState(false); - const [driverObj, setDriverObj] = useState(); - const [driverSaved, setDriverSaved] = useState({}); const dataSourceFormConfigPropsMemo = useMemo(() => { const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) return deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { const flag = t.type === backfillData.type; - if (flag) { - t.driver = driveConfig; - } return flag }); }, []); - useEffect(() => { - getDriverList() - }, [backfillData.type]) - const [dataSourceFormConfigProps, setDataSourceFormConfigProps] = useState(dataSourceFormConfigPropsMemo); useEffect(() => { @@ -82,26 +71,6 @@ export default function CreateConnection(props: IProps) { } }, [backfillData.id]); - useEffect(() => { - if (driverObj?.driverConfigList?.length) { - const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) - const newDataSourceFormConfigProps = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { - const flag = t.type === backfillData.type; - if (flag) { - t.driver = driveConfig; - } - return flag - }); - newDataSourceFormConfigProps.driver!.items[0].selects = driverObj?.driverConfigList.map(t => { - return { - value: t.jdbcDriver, - label: t.jdbcDriver - } - }) - setDataSourceFormConfigProps(newDataSourceFormConfigProps) - } - }, [driverObj]) - function getConnectionDetails(id: number) { setLoading({ ...loadings, @@ -127,50 +96,19 @@ export default function CreateConnection(props: IProps) { }); } + function driverFormChange(data: any) { + setDriveData(data) + } + const getItems = () => [ { key: 'driver', - label: 'Driver', - children: ( -
- -
- { - (!driverObj?.driverConfigList?.length || downloadStatus === DownloadStatus.Success) ?
- { - (downloadStatus === DownloadStatus.Default) &&
{i18n('connection.text.downloadDriver')}
- } - { - (downloadStatus === DownloadStatus.Loading) &&
Downloading
- } - { - (downloadStatus === DownloadStatus.Error) &&
Try again download
- } - { - (downloadStatus === DownloadStatus.Success) &&
{i18n('connection.text.downloadSuccess')}
- } - -
:
- } - -
{ setUploadDriverModal(true) }} - > - {i18n('connection.tips.customUpload')} -
-
-
- ), + label: i18n('connection.title.driver'), + children: , }, { key: 'ssh', - label: 'SSH Configuration', + label: i18n('connection.label.sshConfiguration'), children: (
@@ -201,7 +139,6 @@ export default function CreateConnection(props: IProps) { // 测试、保存、修改连接 function saveConnection(type: submitType) { const ssh = sshForm.getFieldsValue(); - const driverConfig = driveForm.getFieldsValue() const baseInfo = baseInfoForm.getFieldsValue(); const extendInfo: any = []; const loadingsButton = type === submitType.TEST ? 'testButton' : 'confirmButton'; @@ -216,7 +153,7 @@ export default function CreateConnection(props: IProps) { let p: any = { ssh, - driverConfig, + driverConfig: driveData, ...baseInfo, extendInfo, connectionEnvType: ConnectionEnvType.DAILY, @@ -278,33 +215,6 @@ export default function CreateConnection(props: IProps) { }); } - function downloadDrive() { - setDownloadStatus(DownloadStatus.Loading) - connectionService.downloadDriver({ dbType: backfillData.type }).then(res => { - setDownloadStatus(DownloadStatus.Success) - getDriverList(); - }).catch(() => { - setDownloadStatus(DownloadStatus.Error) - }) - } - - function getDriverList() { - connectionService.getDriverList({ dbType: backfillData.type }).then(res => { - setDriverObj(res) - }) - } - - function formChange(data: any) { - setDriverSaved(data) - } - - function saveDriver() { - connectionService.saveDriver(driverSaved).then(res => { - setUploadDriverModal(false) - getDriverList() - }) - } - return (
@@ -345,19 +255,6 @@ export default function CreateConnection(props: IProps) {
- { saveDriver() }} - onCancel={() => { setUploadDriverModal(false) }} - > - -
); } diff --git a/chat2db-client/src/components/Loading/LoadingGracile/index.less b/chat2db-client/src/components/Loading/LoadingGracile/index.less new file mode 100644 index 000000000..bad315404 --- /dev/null +++ b/chat2db-client/src/components/Loading/LoadingGracile/index.less @@ -0,0 +1,133 @@ +@import '../../../styles/var.less'; + +.spinner { + font-size: 14px; + position: relative; + display: inline-block; + width: 1em; + height: 1em; +} + +.spinner.center { + +} + +.spinner .spinnerBlade { + position: absolute; + left: 0.4629em; + bottom: 0; + width: 0.074em; + height: 0.2777em; + border-radius: 0.0555em; + background-color: transparent; + -webkit-transform-origin: center -0.2222em; + -ms-transform-origin: center -0.2222em; + transform-origin: center -0.2222em; + animation: spinner-fade9234 1s infinite linear; +} + +.spinner .spinnerBlade:nth-child(1) { + -webkit-animation-delay: 0s; + animation-delay: 0s; + -webkit-transform: rotate(0deg); + -ms-transform: rotate(0deg); + transform: rotate(0deg); +} + +.spinner .spinnerBlade:nth-child(2) { + -webkit-animation-delay: 0.083s; + animation-delay: 0.083s; + -webkit-transform: rotate(30deg); + -ms-transform: rotate(30deg); + transform: rotate(30deg); +} + +.spinner .spinnerBlade:nth-child(3) { + -webkit-animation-delay: 0.166s; + animation-delay: 0.166s; + -webkit-transform: rotate(60deg); + -ms-transform: rotate(60deg); + transform: rotate(60deg); +} + +.spinner .spinnerBlade:nth-child(4) { + -webkit-animation-delay: 0.249s; + animation-delay: 0.249s; + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} + +.spinner .spinnerBlade:nth-child(5) { + -webkit-animation-delay: 0.332s; + animation-delay: 0.332s; + -webkit-transform: rotate(120deg); + -ms-transform: rotate(120deg); + transform: rotate(120deg); +} + +.spinner .spinnerBlade:nth-child(6) { + -webkit-animation-delay: 0.415s; + animation-delay: 0.415s; + -webkit-transform: rotate(150deg); + -ms-transform: rotate(150deg); + transform: rotate(150deg); +} + +.spinner .spinnerBlade:nth-child(7) { + -webkit-animation-delay: 0.498s; + animation-delay: 0.498s; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} + +.spinner .spinnerBlade:nth-child(8) { + -webkit-animation-delay: 0.581s; + animation-delay: 0.581s; + -webkit-transform: rotate(210deg); + -ms-transform: rotate(210deg); + transform: rotate(210deg); +} + +.spinner .spinnerBlade:nth-child(9) { + -webkit-animation-delay: 0.664s; + animation-delay: 0.664s; + -webkit-transform: rotate(240deg); + -ms-transform: rotate(240deg); + transform: rotate(240deg); +} + +.spinner .spinnerBlade:nth-child(10) { + -webkit-animation-delay: 0.747s; + animation-delay: 0.747s; + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} + +.spinner .spinnerBlade:nth-child(11) { + -webkit-animation-delay: 0.83s; + animation-delay: 0.83s; + -webkit-transform: rotate(300deg); + -ms-transform: rotate(300deg); + transform: rotate(300deg); +} + +.spinner .spinnerBlade:nth-child(12) { + -webkit-animation-delay: 0.913s; + animation-delay: 0.913s; + -webkit-transform: rotate(330deg); + -ms-transform: rotate(330deg); + transform: rotate(330deg); +} + +@keyframes spinner-fade9234 { + 0% { + background-color: var(--color-text); + } + + 100% { + background-color: transparent; + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/Loading/LoadingGracile/index.tsx b/chat2db-client/src/components/Loading/LoadingGracile/index.tsx new file mode 100644 index 000000000..e7b0f178c --- /dev/null +++ b/chat2db-client/src/components/Loading/LoadingGracile/index.tsx @@ -0,0 +1,27 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import './index.less' + +interface IProps { + className?: any; +} + +export default memo(function LoadingGracile(props: IProps) { + const { className } = props; + return
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +}); diff --git a/chat2db-client/src/components/UploadDriver/index.tsx b/chat2db-client/src/components/UploadDriver/index.tsx index cf91da5c8..c5dcd3919 100644 --- a/chat2db-client/src/components/UploadDriver/index.tsx +++ b/chat2db-client/src/components/UploadDriver/index.tsx @@ -50,11 +50,10 @@ export default memo(function UploadDriver(props) { return
- + diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 11f3817dc..70e1d088a 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -23,5 +23,7 @@ export default { 'connection.button.clickUpload': 'Click to Upload', 'connection.text.downloadDriver': 'Download Driver', 'connection.text.downloadSuccess': 'Download Success', + 'connection.text.tryAgainDownload': 'Try again download', + 'connection.text.downloading': 'Downloading...', }; \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 3aa3accf9..6206da9cf 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -23,4 +23,6 @@ export default { 'connection.button.clickUpload': '点击上传', 'connection.text.downloadDriver': '下载驱动', 'connection.text.downloadSuccess': '下载成功', + 'connection.text.tryAgainDownload': '尝试重新下载', + 'connection.text.downloading': '下载中...', } diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 36ecebc18..4bbe225de 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -19,7 +19,7 @@ export default { 'setting.button.apply': '应用', 'setting.text.currentEnv': '当前环境', 'setting.text.currentVersion': '当前版本', - 'setting.text.viewingUpdateLogs': '查看当前应用', + 'setting.text.viewingUpdateLogs': '查看更新日志', 'setting.label.isStreamOutput': '接口是否流式输出', 'setting.label.customAiUrl': '自定义接口Url', 'setting.placeholder.httpsProxy': '非必填,用于设置请求OPENAI接口时的HTTP代理{1}', From c1b13aa60637e0e88401f28ff0990889d8f1962a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 9 Jul 2023 15:19:12 +0800 Subject: [PATCH 0265/1069] =?UTF-8?q?fix:driver=E9=BB=98=E8=AE=A4=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E7=AC=AC=E4=B8=80=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Driver/index.tsx | 18 ++++++++++++------ .../CreateConnection/config/dataSource.ts | 3 ++- .../CreateConnection/config/types.ts | 2 ++ .../src/components/CreateConnection/index.tsx | 4 +++- chat2db-client/src/utils/localStorage.ts | 2 +- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx b/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx index cceef6d25..fb91152a5 100644 --- a/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx +++ b/chat2db-client/src/components/CreateConnection/components/Driver/index.tsx @@ -25,7 +25,7 @@ enum DownloadStatus { export default memo(function Driver(props) { const { className, backfillData, onChange } = props; const [downloadStatus, setDownloadStatus] = useState(); - const [driveForm] = Form.useForm(); + const [driverForm] = Form.useForm(); const [driverObj, setDriverObj] = useState(); const [uploadDriverModal, setUploadDriverModal] = useState(false); const [driverSaved, setDriverSaved] = useState({}); @@ -38,7 +38,7 @@ export default memo(function Driver(props) { useEffect(() => { if (backfillData) { - driveForm.setFieldsValue({ + driverForm.setFieldsValue({ jdbcDriverClass: backfillData?.driverConfig?.jdbcDriverClass, jdbcDriver: backfillData?.driverConfig?.jdbcDriver }) @@ -48,6 +48,12 @@ export default memo(function Driver(props) { function getDriverList() { connectionService.getDriverList({ dbType: backfillData.type }).then(res => { setDriverObj(res) + if (res.driverConfigList?.length) { + driverForm.setFieldsValue({ + jdbcDriverClass: res.driverConfigList[0].jdbcDriverClass, + jdbcDriver: res.driverConfigList[0].jdbcDriver + }) + } }) } @@ -65,7 +71,7 @@ export default memo(function Driver(props) { function downloadDrive() { setDownloadStatus(DownloadStatus.Loading) connectionService.downloadDriver({ dbType: backfillData.type }).then(res => { - // setDownloadStatus(DownloadStatus.Success) + setDownloadStatus(DownloadStatus.Success); getDriverList(); }).catch(() => { setDownloadStatus(DownloadStatus.Error) @@ -74,7 +80,7 @@ export default memo(function Driver(props) { function onValuesChange(data: any) { const selected = driverObj?.driverConfigList.find(t => t.jdbcDriver === data.jdbcDriver); - driveForm.setFieldsValue({ + driverForm.setFieldsValue({ jdbcDriverClass: selected?.jdbcDriverClass }); onChange({ @@ -85,7 +91,7 @@ export default memo(function Driver(props) { return
@@ -124,7 +130,7 @@ export default memo(function Driver(props) { (downloadStatus === DownloadStatus.Success) &&
{i18n('connection.text.downloadSuccess')}
} -
:
+
:
}
React.ReactNode } = { @@ -417,7 +418,7 @@ function RenderForm(props: IRenderFormProps) { style={{ '--form-label-width': labelWidth } as any} labelAlign={labelAlign} > - + ), @@ -429,6 +430,7 @@ function RenderForm(props: IRenderFormProps) { labelAlign={labelAlign} > { - onSelectTables && onSelectTables(v) + onSelectTables && onSelectTables(v); }} />
- ) + ); }; const renderSuffix = () => { - const remainCnt = props?.remainingUse?.remainingUses || '-'; + const remainCnt = props?.remainingUse?.remainingUses ?? '-'; return (
{/*
⌘ + ↵
*/} @@ -65,9 +65,12 @@ function ChatInput(props: IProps) {
-
{ - props.onClickRemainBtn && props.onClickRemainBtn(); - }}> +
{ + props.onClickRemainBtn && props.onClickRemainBtn(); + }} + > {i18n('chat.input.remain', remainCnt)}
From c339a11fa133a960cea4c8ee5ccf3abb7eb745d5 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 11 Jul 2023 22:29:03 +0800 Subject: [PATCH 0322/1069] fix mysql plugin name error --- .../chat2db-plugins/{chat2b-mysql => chat2db-mysql}/pom.xml | 2 +- .../src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java | 0 .../src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java | 0 .../src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java | 0 .../java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java | 0 .../src/main/java/ai/chat2db/plugin/mysql/builder/form.json | 0 .../src/main/resources/META-INF/services/ai.chat2db.spi.Plugin | 0 chat2db-server/chat2db-plugins/pom.xml | 2 +- .../chat2db-server-domain/chat2db-server-domain-core/pom.xml | 2 +- 9 files changed, 3 insertions(+), 3 deletions(-) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/pom.xml (94%) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java (100%) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java (100%) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java (100%) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java (100%) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/src/main/java/ai/chat2db/plugin/mysql/builder/form.json (100%) rename chat2db-server/chat2db-plugins/{chat2b-mysql => chat2db-mysql}/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin (100%) diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/pom.xml b/chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml similarity index 94% rename from chat2db-server/chat2db-plugins/chat2b-mysql/pom.xml rename to chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml index 42dcd1c00..4b0bbdb27 100644 --- a/chat2db-server/chat2db-plugins/chat2b-mysql/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/pom.xml @@ -10,7 +10,7 @@ ../pom.xml 4.0.0 - chat2b-mysql + chat2db-mysql diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java similarity index 100% rename from chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java rename to chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java diff --git a/chat2db-server/chat2db-plugins/chat2b-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 similarity index 100% rename from chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java rename to chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java diff --git a/chat2db-server/chat2db-plugins/chat2b-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 similarity index 100% rename from chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java rename to chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlPlugin.java diff --git a/chat2db-server/chat2db-plugins/chat2b-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 similarity index 100% rename from chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java rename to chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/DBConfigBuilder.java diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/form.json b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/form.json similarity index 100% rename from chat2db-server/chat2db-plugins/chat2b-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/form.json rename to chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/form.json diff --git a/chat2db-server/chat2db-plugins/chat2b-mysql/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin similarity index 100% rename from chat2db-server/chat2db-plugins/chat2b-mysql/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin rename to chat2db-server/chat2db-plugins/chat2db-mysql/src/main/resources/META-INF/services/ai.chat2db.spi.Plugin diff --git a/chat2db-server/chat2db-plugins/pom.xml b/chat2db-server/chat2db-plugins/pom.xml index e95e85c56..d058b04e7 100644 --- a/chat2db-server/chat2db-plugins/pom.xml +++ b/chat2db-server/chat2db-plugins/pom.xml @@ -14,7 +14,7 @@ pom - chat2b-mysql + chat2db-mysql chat2db-sqlserver chat2db-postgresql chat2db-oracle diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml index 7b2b02309..233665eff 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/pom.xml @@ -43,7 +43,7 @@ ai.chat2db - chat2b-mysql + chat2db-mysql ${revision} From ae2a7c246608e6e37e3c8fcfcc0f56be95b57fca Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 11 Jul 2023 22:31:49 +0800 Subject: [PATCH 0323/1069] fix: some bug --- chat2db-client/src/components/Console/index.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index a16657b40..77c5d039d 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -368,7 +368,11 @@ function Console(props: IProps) { ); const handleClickRemainBtn = async () => { - if (!aiModel.keyAndAiType.key) { + if ( + !aiModel.keyAndAiType.key || + aiModel.remainingUse?.remainingUses === null || + aiModel.remainingUse?.remainingUses === undefined + ) { handleApiKeyEmptyOrGetQrCode(true); return; } From 982d4208e8d7cb3112227f9bed51617e3c9796c9 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 11 Jul 2023 22:38:24 +0800 Subject: [PATCH 0324/1069] feat: add loading --- chat2db-client/src/components/Console/ChatInput/index.tsx | 1 - chat2db-client/src/components/Console/index.tsx | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 079c3d887..e75a2beba 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -59,7 +59,6 @@ function ChatInput(props: IProps) { const remainCnt = props?.remainingUse?.remainingUses ?? '-'; return (
- {/*
⌘ + ↵
*/}
diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 77c5d039d..0fb9b625a 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -167,7 +167,9 @@ function Console(props: IProps) { }, [props.tables]); const handleApiKeyEmptyOrGetQrCode = async (shouldPoll?: boolean) => { + setIsLoading(true); const { wechatQrCodeUrl, token, tip } = await aiServer.getLoginQrCode({}); + setIsLoading(false); // console.log('weiChatConfig', wechatQrCodeUrl, token); setPopularizeModal(true); setModalProps({ @@ -377,7 +379,9 @@ function Console(props: IProps) { return; } + setIsLoading(true); const { tip, wechatQrCodeUrl } = await aiServer.getInviteQrCode({}); + setIsLoading(false); setModalProps({ imageUrl: wechatQrCodeUrl, tip, From 6defc868b8c6af72cf91f524c6ab04df86a5090c Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 11 Jul 2023 23:12:49 +0800 Subject: [PATCH 0325/1069] feat: add poll --- chat2db-client/src/components/Console/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 0fb9b625a..d72cb69b7 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -275,7 +275,7 @@ function Console(props: IProps) { if (hasErrorToLogin || hasErrorToInvite) { closeEventSource(); setIsLoading(false); - hasErrorToLogin && handleApiKeyEmptyOrGetQrCode(); + hasErrorToLogin && handleApiKeyEmptyOrGetQrCode(true); hasErrorToInvite && handleClickRemainBtn(); dispatch({ type: 'ai/fetchRemainingUse', From b9ae3c8c38cb50ea98c3df7f6bf6cbeb03ccb1c8 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 11 Jul 2023 23:20:27 +0800 Subject: [PATCH 0326/1069] feat: add kill java process --- chat2db-client/src/main/index.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index 5146df158..a669a0ae1 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -64,16 +64,16 @@ app.on('window-all-closed', () => { }); app.on('before-quit', (event) => { - // try { - // const request = net.request({ - // headers: { - // 'Content-Type': 'application/json', - // }, - // method: 'POST', - // url: 'http://127.0.0.1:10824/api/system/stop', - // }); - // request.end(); - // } catch (error) {} + try { + const request = net.request({ + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + url: 'http://127.0.0.1:10824/api/system/stop', + }); + request.end(); + } catch (error) {} }); ipcMain.handle('get-product-name', (event) => { From e1c7d8f069f572097fb9b4626b8228e0a9a4de48 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 11 Jul 2023 23:30:03 +0800 Subject: [PATCH 0327/1069] feat: improve style --- chat2db-client/src/components/SearchResult/TableBox.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index d69f7a9c4..effa8c7e4 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -26,6 +26,10 @@ .art-table-cell { position: relative; } + .art-sticky-scroll.art-horizontal-scroll-container { + position: sticky; + bottom: 24px !important; + } } } From 4a7e8b5e6ab5aa6927b3c4dac6f62b0d8423d231 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 12 Jul 2023 00:20:04 +0800 Subject: [PATCH 0328/1069] release 2.0.2 --- README.md | 8 ++++---- README_CN.md | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ff5128c0c..4028ed354 100644 --- a/README.md +++ b/README.md @@ -49,10 +49,10 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 | Description | Download | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | -| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | -| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | -| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe) | +| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg) | +| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg) | +| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar) | ## 🚀 Supported databases diff --git a/README_CN.md b/README_CN.md index 5d8b3362a..99a0a3933 100644 --- a/README_CN.md +++ b/README_CN.md @@ -55,10 +55,10 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 | 描述 | 下载地址 | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB%20Setup%202.0.1.exe) | -| MacOS ARM64 (Apple 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1-arm64.dmg) | -| MacOS X64 (Intel 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg](https://oss-chat2db.alibaba.com/release/2.0.1/Chat2DB-2.0.1.dmg) | -| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.1/chat2db-server-start.jar) | +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe) | +| MacOS ARM64 (Apple 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg) | +| MacOS X64 (Intel 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg) | +| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar) | ## 🚀 支持的数据库 From a4a05bb9f8f138e1324da01d262ed7f653981d74 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 12 Jul 2023 00:24:25 +0800 Subject: [PATCH 0329/1069] release 2.0.2 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0404bed89..770882f06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# 2.0.2 +## ⭐ New Features +* Optimize the binding process +## 🐞 Bug Fixes +* xx + +## ⭐ 新特性 +* 优化绑定流程 +## 🐞 问题修复 +* xx + # 2.0.1 ## 🐞 Bug Fixes * Fix bug where executing multiple SQL statements at once will prompt for exceptions From 19a16b892eb6f51ffcabf389e8b71d9ef56a30bd Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 12 Jul 2023 00:29:14 +0800 Subject: [PATCH 0330/1069] feat: add changelog --- CHANGELOG.md | 144 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 85 insertions(+), 59 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 770882f06..bb9991b4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,93 +1,119 @@ # 2.0.2 + ## ⭐ New Features -* Optimize the binding process + +- Optimize the binding process + ## 🐞 Bug Fixes -* xx + +- xx ## ⭐ 新特性 -* 优化绑定流程 + +- 优化绑定流程 +- 优化 dataSource 链接编辑 +- 支持自定义驱动 +- 优化错误提示 +- 优化选表交互 +- 优化表格体验 + ## 🐞 问题修复 -* xx # 2.0.1 + ## 🐞 Bug Fixes -* Fix bug where executing multiple SQL statements at once will prompt for exceptions -* Fix getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) -* Fixing the Hive connection and then viewing columns results in an error. [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) + +- Fix bug where executing multiple SQL statements at once will prompt for exceptions +- Fix getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) +- Fixing the Hive connection and then viewing columns results in an error. [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) ## 🐞 问题修复 -* 修复一次性执行多条SQL会提示异常的BUG -* 修复 getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) -* 修复hive方式连接,然后查看columns报错 [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) + +- 修复一次性执行多条 SQL 会提示异常的 BUG +- 修复 getJDBCDriver error: null [Issue #123](https://github.com/chat2db/Chat2DB/issues/123) +- 修复 hive 方式连接,然后查看 columns 报错 [Issue #136](https://github.com/chat2db/Chat2DB/issues/136) # 2.0.0 + ## What's Changed -* 🔥An intelligent solution that perfectly integrates SQL queries, AI assistant, and data analysis. -* 🔥New focused mode experience for advanced datasource management. -* AI integration of more LLM. -* Bilingual in Chinese and English support for client. + +- 🔥An intelligent solution that perfectly integrates SQL queries, AI assistant, and data analysis. +- 🔥New focused mode experience for advanced datasource management. +- AI integration of more LLM. +- Bilingual in Chinese and English support for client. + ## 更新内容 -* 🔥SQL查询、AI查询和数据报表完美集成的一体化解决方案设计与实现 -* 🔥数据源连接和管理进阶为专注模式的全新体验设计与实现 -* 🔥AI对话SQL升级为极简模式的全新交互设计与实现 -* 客户端AI体验重大升级,响应更多用户的诉求 -* 集成更多AI模型 -* 客户端双语支持 -* SQL查询基础功能优化 -* Issue问题修复 + +- 🔥SQL 查询、AI 查询和数据报表完美集成的一体化解决方案设计与实现 +- 🔥 数据源连接和管理进阶为专注模式的全新体验设计与实现 +- 🔥AI 对话 SQL 升级为极简模式的全新交互设计与实现 +- 客户端 AI 体验重大升级,响应更多用户的诉求 +- 集成更多 AI 模型 +- 客户端双语支持 +- SQL 查询基础功能优化 +- Issue 问题修复 # 1.0.11 -* fixed: SQL有特殊字符时AI功能无法正常使用 -* 增减版本信息检测 + +- fixed: SQL 有特殊字符时 AI 功能无法正常使用 +- 增减版本信息检测 # 1.0.10 -* fixed: 格式化SQL异常 -* 优化AI网络连接异常提示 -* 自定义AI添加本地样例 -* Support OceanBase Presto DB2 Redis MongoDB Hive KingBase + +- fixed: 格式化 SQL 异常 +- 优化 AI 网络连接异常提示 +- 自定义 AI 添加本地样例 +- Support OceanBase Presto DB2 Redis MongoDB Hive KingBase # 1.0.9 -* 修复Open Ai 无法连接的问题 -* 支持国产达梦数据库 -* 支持自定义OPEN AI API_HOST -* 🔥 支持自定义AI接口 -* 支持主题色跟随系统 +- 修复 Open Ai 无法连接的问题 + +- 支持国产达梦数据库 +- 支持自定义 OPEN AI API_HOST +- 🔥 支持自定义 AI 接口 +- 支持主题色跟随系统 # 1.0.6 -* 修复Oracle数据库字符集问题 -* 修复mac安装提示的安全问题 + +- 修复 Oracle 数据库字符集问题 +- 修复 mac 安装提示的安全问题 # 1.0.5 -* 🔥 优化Apple芯片的启动速度 -* 修复Windows端数据库连接问题 -* 修改database不生效 -* NullPointerException + +- 🔥 优化 Apple 芯片的启动速度 +- 修复 Windows 端数据库连接问题 +- 修改 database 不生效 +- NullPointerException # 1.0.4 -* 修复ClickHouse jdbc问题 -* 修复连接池管理的NPE -* 修复前端编辑数据源报错问题 -* 增加数据库默认属性配置 + +- 修复 ClickHouse jdbc 问题 +- 修复连接池管理的 NPE +- 修复前端编辑数据源报错问题 +- 增加数据库默认属性配置 # 1.0.3 -* 🔥 支持SSH连接数据库 -* 🎉 支持客户端查看日志 -* 🎉 支持在Console中聊天对话 -* 支持在客户端内设置OPENAI代理 -* 已经启动过应用不会再重复启动 + +- 🔥 支持 SSH 连接数据库 +- 🎉 支持客户端查看日志 +- 🎉 支持在 Console 中聊天对话 +- 支持在客户端内设置 OPENAI 代理 +- 已经启动过应用不会再重复启动 # 1.0.1 -* 修复oracle连接配置编辑、以及连接查询问题 -* 修复Apikey输出到日志可能存在的风险 -* 修复web版本登录的bug + +- 修复 oracle 连接配置编辑、以及连接查询问题 +- 修复 Apikey 输出到日志可能存在的风险 +- 修复 web 版本登录的 bug # 1.0.0 -Chat2DB的 1.0.0 正式版来啦🎉🎉🎉🎉🎉🎉🎉🎉🎉 - -* 🌈 AI智能助手,支持自然语言转SQL、SQL转自然语言、SQL优化建议 -* 👭 支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题 -* ⚙️ 强大的数据管理能力,支持数据表、视图、存储过程、函数、触发器、索引、序列、用户、角色、授权等管理 -* 🔌 强大的扩展能力,目前已经支持Mysql、PostgreSQL、Oracle、SQLServer、ClickHouse、Oceanbase、H2、SQLite等等,未来会支持更多的数据库 -* 🛡 前端使用 Electron 开发,提供 Windows、Mac、Linux 客户端、网页版本一体化的解决方案 -* 🎁 支持环境隔离、线上、日常数据权限分离 + +Chat2DB 的 1.0.0 正式版来啦 🎉🎉🎉🎉🎉🎉🎉🎉🎉 + +- 🌈 AI 智能助手,支持自然语言转 SQL、SQL 转自然语言、SQL 优化建议 +- 👭 支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题 +- ⚙️ 强大的数据管理能力,支持数据表、视图、存储过程、函数、触发器、索引、序列、用户、角色、授权等管理 +- 🔌 强大的扩展能力,目前已经支持 Mysql、PostgreSQL、Oracle、SQLServer、ClickHouse、Oceanbase、H2、SQLite 等等,未来会支持更多的数据库 +- 🛡 前端使用 Electron 开发,提供 Windows、Mac、Linux 客户端、网页版本一体化的解决方案 +- 🎁 支持环境隔离、线上、日常数据权限分离 From c637d9698580d7d39eb602ab828caa64e677d1d7 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Wed, 12 Jul 2023 00:38:52 +0800 Subject: [PATCH 0331/1069] add db2 --- chat2db-client/src/constants/database.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index af0170b8d..725295980 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -76,6 +76,20 @@ export const databaseMap: { // port: 5236, icon: '\ue655', }, + [DatabaseTypeCode.PRESTO]: { + name: 'Presto', + img: moreDBLogo, + code: DatabaseTypeCode.PRESTO, + port: 8080, + icon: '\ue60b', + }, + [DatabaseTypeCode.DB2]: { + name: 'DB2', + img: moreDBLogo, + code: DatabaseTypeCode.DB2, + port: 50000, + icon: '\ue60a', + }, [DatabaseTypeCode.OCEANBASE]: { name: 'OceanBase', img: moreDBLogo, @@ -111,6 +125,13 @@ export const databaseMap: { // 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 a58f8686b146b7bac12b7d480cc59e263e121010 Mon Sep 17 00:00:00 2001 From: Jiaju Zhuang Date: Wed, 12 Jul 2023 11:35:05 +0800 Subject: [PATCH 0332/1069] Update CHANGELOG.md --- CHANGELOG.md | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb9991b4f..8e4f246b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,25 @@ # 2.0.2 ## ⭐ New Features - -- Optimize the binding process - +- Brand new AI binding process +- Support for custom drivers ## 🐞 Bug Fixes - -- xx +- Optimized dataSource link editing +- Enhanced error messages +- Improved table selection interaction +- Enhanced table experience ## ⭐ 新特性 +- 全新的AI绑定流程 +- 支持自定义驱动 -- 优化绑定流程 +## 🐞 问题修复 - 优化 dataSource 链接编辑 -- 支持自定义驱动 - 优化错误提示 - 优化选表交互 - 优化表格体验 -## 🐞 问题修复 + # 2.0.1 From becc3c1e286b3a158a0bc1a6fc6c2a4b0cf6d0a2 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 12 Jul 2023 21:47:58 +0800 Subject: [PATCH 0333/1069] =?UTF-8?q?feat:console=20=E9=87=8D=E5=91=BD?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.vscode/settings.json | 7 +- chat2db-client/src/components/Tabs/index.less | 13 ++++ chat2db-client/src/components/Tabs/index.tsx | 71 +++++++++++++------ .../components/WorkspaceLeft/index.less | 5 ++ .../components/WorkspaceLeft/index.tsx | 4 +- .../components/WorkspaceRight/index.tsx | 32 ++++++++- 6 files changed, 107 insertions(+), 25 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index e6dd6f7b4..737e02829 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -13,6 +13,10 @@ "[less]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "explorer.confirmDelete": true, + "emmet.includeLanguages": { + "javascript": "javascriptreact" + }, "git.mergeEditor": false, "cSpell.words": [ "Appstore", @@ -38,5 +42,6 @@ "umijs", "uuidv", "wireframe" - ] + ], + "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" } \ No newline at end of file diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index bffcdea61..d22e9cf77 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -116,3 +116,16 @@ color: var(--color-primary); } } + +.input{ + border: 0; + width: 86px; + flex: 1; + height: 20px; + outline: none; + font-size: 12px; + font-weight: 400; + input:focus{ + outline: none; + } +} diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 970abcfb3..d6bf16f6e 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -21,13 +21,16 @@ interface IProps { onChange: (key: IOption['value']) => void; onEdit?: (action: 'add' | 'remove', key?: IOption['value']) => void; hideAdd?: boolean; - type?: 'line' + type?: 'line'; + editableName?: boolean; + editableNameOnBlur?: (option: IOption) => void; } export default memo(function Tab(props) { - const { className, tabs, onChange, onEdit, activeTab, hideAdd, type } = props; + const { className, tabs, onChange, onEdit, activeTab, hideAdd, type, editableName, editableNameOnBlur } = props; const [internalTabs, setInternalTabs] = useState(lodash.cloneDeep(tabs || [])); const [internalActiveTab, setInternalActiveTab] = useState(internalTabs[0]?.value); + const [editingTab, setEditingTab] = useState(); useEffect(() => { setInternalActiveTab(activeTab); @@ -52,31 +55,57 @@ export default memo(function Tab(props) { onEdit?.('add') } + function onDoubleClick(t: IOption) { + if (editableName) { + setEditingTab(t.value) + } + } + + + function renderTabItem(t: IOption, index: number) { + function inputOnChange(value: string) { + internalTabs[index].label = value + setInternalTabs([...internalTabs]) + } + + function onBlur() { + editableNameOnBlur?.(t); + setEditingTab(undefined); + } + + return
{ onDoubleClick(t) }} + key={t.value} + className={ + classnames( + { [styles.tabItem]: type !== 'line' }, + { [styles.tabItemLine]: type === 'line' }, + { [styles.activeTabLine]: t.value === internalActiveTab && type === 'line' }, + { [styles.activeTab]: t.value === internalActiveTab && type !== 'line' }, + ) + } + > + { + t.value === editingTab ? + { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> + : +
+ {t.label} +
+ } +
+ +
+
+ } + return
{ !!internalTabs?.length &&
{ internalTabs.map((t, index) => { - return
-
- {t.label} -
-
- -
-
+ return renderTabItem(t, index) }) }
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 8ff1f8b5a..2a9d82e8e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -106,6 +106,11 @@ border-radius: 2px; user-select: none; cursor: pointer; + .saveItemText{ + width: 0px; + flex: 1; + .f-single-line(); + } .moreButton { flex-shrink: 0; display: none; 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 aceb36f1e..4619fc697 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -429,7 +429,9 @@ const RenderSaveBox = dvaModel(function (props: any) { key={t.id} className={styles.saveItem} > -
+
+ +
(function (props) { return
Chat2DB
; } + function editableNameOnBlur(t: IOption) { + let p: any = { + id: t.value, + name: t.label + } + historyService.updateSavedConsole(p).then(() => { + getConsoleList(); + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + pageNo: 1, + pageSize: 999, + status: ConsoleStatus.RELEASE, + ...curWorkspaceParams, + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setConsoleList', + payload: res.data, + }) + } + }); + + }); + } + return (
@@ -173,7 +199,9 @@ const WorkspaceRight = memo(function (props) { { return { From 5de3767721bb8c808eca5cf6c787a3c46b0c9be7 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 12 Jul 2023 23:24:46 +0800 Subject: [PATCH 0334/1069] feat: Modify the readme --- README.md | 15 +++------------ README_CN.md | 15 +++------------ 2 files changed, 6 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4028ed354..ab562478f 100644 --- a/README.md +++ b/README.md @@ -148,12 +148,12 @@ Note: If local debugging is required $ git clone git@github.com:alibaba/Chat2DB.git ``` -- Front-End installation +- Front-End debug ```bash $ cd Chat2DB/chat2db-client -$ npm install # Mounting front-end dependency -$ npm run build:prod # Package js to the source directory on the back end +$ yarn +$ yarn run start:web ``` - Backend debug @@ -163,15 +163,6 @@ $ cd ../chat2db-server $ mvn clean install # maven 3.8 or later needs to be installed $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. -$ # open http://127.0.0.1:10821 to start debug Note: Front-end installation is required -``` - -- Front-End debug - -```bash -$ cd Chat2DB/chat2db-client -$ yarn -$ npm run start:web ``` ## 📑 Documentation diff --git a/README_CN.md b/README_CN.md index 99a0a3933..685fa5e5d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -154,12 +154,12 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 $ git clone git@github.com:chat2db/Chat2DB.git ``` -- 前端安装 +- 前端调试 ```bash $ cd Chat2DB/chat2db-client -$ npm install # 安装前端依赖 -$ npm run build:prod # 把js打包生成到后端的source目录 +$ yarn +$ yarn run start:web ``` - 后端调试 @@ -169,15 +169,6 @@ $ cd ../chat2db-server $ mvn clean install # 需要安装maven 3.8以上版本 $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 -$ # 打开 http://127.0.0.1:10821 开启调试 注:需要进行前端安装 -``` - -- 前端调试 - -```bash -$ cd Chat2DB/chat2db-client -$ yarn -$ npm run start:web ``` ## 📑 文档 From 46ee9d0af150b7666fd2d7b67006b562422953f5 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 13 Jul 2023 22:32:31 +0800 Subject: [PATCH 0335/1069] =?UTF-8?q?fix:=20=E6=8F=90=E7=A4=BA=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/MyNotification/index.less | 45 ++++++++-- .../src/components/MyNotification/index.tsx | 83 +++++++++++++------ chat2db-client/src/hooks/index.ts | 2 +- chat2db-client/src/layouts/index.tsx | 17 ++-- .../src/layouts/init/registerNotification.ts | 2 +- .../components/WorkspaceLeft/index.tsx | 2 +- .../components/WorkspaceRight/index.tsx | 1 + chat2db-client/src/service/base.ts | 4 +- chat2db-client/src/styles/antd.less | 40 ++++----- 9 files changed, 130 insertions(+), 66 deletions(-) diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index bb50f8e05..b889ff100 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -1,13 +1,48 @@ @import '../../styles/var.less'; .message { - .f-lines(1); + display: flex; + align-items: center; + padding: 0px 8px; + i { + margin-right: 10px; + color: var(--color-error-text); + } +} + +.modal{ + :global{ + .ant-modal-content{ + max-height: 80vh; + display: flex; + flex-direction: column; + .ant-modal-header,.ant-modal-footer{ + flex-shrink: 0; + } + .ant-modal-body{ + flex: 1; + height: 0; + overflow-y: auto; + } + } + } } .description { - display: flex; + .f-lines(2); + word-break: break-all; + padding: 0px 10px; +} - button { - padding: 4px 0; - } +.notification{ + z-index: 999 !important; + width: 300px !important; + font-size: 12px !important; + padding: 4px !important; + :global{ + .ant-notification-notice-close{ + top: 8px !important; + inset-inline-end: 8px !important; + } + } } diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index 91c31b75e..d1cda3054 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -1,9 +1,9 @@ -import { Button, ConfigProvider, Modal, notification, } from 'antd'; -import React from 'react' +import React, { useCallback, useEffect, useState } from 'react' +import { Button, ConfigProvider, Modal, notification, Space } from 'antd'; import styles from './index.less' import i18n from '@/i18n'; import { IconType } from 'antd/es/notification/interface'; -// import { staticNotification } from '@/layouts' +import Iconfont from '../Iconfont'; interface IProps { type?: IconType; @@ -18,33 +18,68 @@ interface IProps { solutionLink: string; } -function MyNotification(props: IProps) { - const { errorCode, errorMessage, errorDetail, solutionLink } = props; +function MyNotification() { + const [notificationApi, notificationDom] = notification.useNotification({ + maxCount: 2 + }); + const [open, setOpen] = useState(false); + const [props, setProps] = useState() - const type = props.type || 'warning'; - const title = `${errorCode}:${errorMessage}`; - const message = props.message ||
{errorCode}:{errorMessage || 'Error'}
+ window._notificationApi = useCallback((props: IProps) => { + const { errorCode, errorMessage, errorDetail, solutionLink } = props; + setProps(props); + const btn = ( + + + + + ); - const description =
- - -
+ const renderDescription = () => { + return
+ {props.errorCode}{props.errorCode}{props.errorCode}{props.errorCode} +
+ } + const renderMessage = () => { + return
+ + Error +
+ } - return notification.open({ - ...props, - type, - message, - description, - }) + notificationApi.open({ + className: styles.notification, + message: renderMessage(), + description: renderDescription(), + placement: 'bottomRight', + btn, + duration: null, + }) + }, []) + return <> + {notificationDom} + { + setOpen(false) + }} + > + {props?.errorDetail} + + } export default MyNotification; \ No newline at end of file diff --git a/chat2db-client/src/hooks/index.ts b/chat2db-client/src/hooks/index.ts index 210039750..5d1c1b0a5 100644 --- a/chat2db-client/src/hooks/index.ts +++ b/chat2db-client/src/hooks/index.ts @@ -1,3 +1,3 @@ export * from './useTheme'; export * from './useUpdateEffect'; -export * from './useEventSource' +export * from './useEventSource'; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index cfe18d718..a22f00978 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -1,14 +1,11 @@ import React, { useEffect, useLayoutEffect } from 'react'; import i18n from '@/i18n'; import { Outlet } from 'umi'; -import { ConfigProvider, theme, App, Button, Spin } from 'antd'; +import { ConfigProvider, theme, App, Button, Spin, notification } from 'antd'; import { useState } from 'react'; import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig } from '@/theme'; import { IVersionResponse } from '@/typings'; -import classnames from 'classnames'; -import LoadingLiquid from '@/components/Loading/LoadingLiquid'; -import Setting from '@/blocks/Setting'; import miscService from '@/service/misc'; import antdEnUS from 'antd/locale/en_US'; @@ -22,9 +19,7 @@ import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorag import { clearOlderLocalStorage } from '@/utils'; import registerMessage from './init/registerMessage'; import registerNotification from './init/registerNotification'; -import { NotificationInstance } from 'antd/es/notification/interface'; -import { ModalStaticFunctions } from 'antd/es/modal/confirm'; - +import MyNotification from '@/components/MyNotification'; declare global { interface Window { _Lang: string; @@ -33,6 +28,7 @@ declare global { _BaseURL: string; _AppThemePack: { [key in string]: string }; _appGatewayParams: IVersionResponse; + _notificationApi: any; } const __APP_VERSION__: string; const __BUILD_TIME__: string; @@ -79,17 +75,12 @@ export default function Layout() { /** 重启次数 */ const restartCount = 200; -let staticNotification: NotificationInstance; - function AppContainer() { const { token } = useToken(); const [initEnd, setInitEnd] = useState(false); const [appTheme, setAppTheme] = useTheme(); const [startSchedule, setStartSchedule] = useState(1); // 0 初始状态 1 服务启动中 2 启动成功 const [serviceFail, setServiceFail] = useState(false); - const { notification } = App.useApp(); - - // staticNotification = staticFunction.notification useEffect(() => { let date = new Date('2030-12-30 12:30:00').toUTCString(); @@ -212,6 +203,8 @@ function AppContainer() { {startSchedule === 2 && }
)} + {/* 全局的弹窗 */} +
); } diff --git a/chat2db-client/src/layouts/init/registerNotification.ts b/chat2db-client/src/layouts/init/registerNotification.ts index 1ef394f5b..8fa758d17 100644 --- a/chat2db-client/src/layouts/init/registerNotification.ts +++ b/chat2db-client/src/layouts/init/registerNotification.ts @@ -4,6 +4,6 @@ export default () => { notification.config({ placement: 'BottomRight', maxCount: 2, - duration: 0, + duration: null, }); }; 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 4619fc697..666d4d8ad 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -375,7 +375,7 @@ const RenderSaveBox = dvaModel(function (props: any) { type: 'workspace/fetchGetSavedConsole', payload: { status: ConsoleStatus.RELEASE, - orderByDesc: true, + orderByDesc: false, ...curWorkspaceParams }, callback: (res: any) => { 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 102a43917..be9a6183e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -178,6 +178,7 @@ const WorkspaceRight = memo(function (props) { payload: { pageNo: 1, pageSize: 999, + orderByDesc: false, status: ConsoleStatus.RELEASE, ...curWorkspaceParams, }, diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index a768c9838..f4b02126c 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -173,12 +173,12 @@ export default function createRequest

(url: string, options?: I const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { - ErrorNotification({ + window._notificationApi({ errorCode, errorMessage, errorDetail, solutionLink, - }); + }) // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index 680daa899..6c6235e77 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -18,28 +18,28 @@ border-top: 0px; } - .ant-notification-notice { - background-color: var(--color-bg-elevated) !important; - padding: 8px 8px 8px 16px !important; - width: 280px !important; + // .ant-notification-notice { + // background-color: var(--color-bg-elevated) !important; + // padding: 8px 8px 8px 16px !important; + // width: 280px !important; - .ant-notification-notice-icon { - font-size: 16px !important; - top: 12px !important; - } + // .ant-notification-notice-icon { + // font-size: 16px !important; + // top: 12px !important; + // } - .ant-notification-notice-message { - font-size: 14px !important; - color: var(--color-text) !important; - } + // .ant-notification-notice-message { + // font-size: 14px !important; + // color: var(--color-text) !important; + // } - .ant-notification-notice-close { - font-size: 12px !important; - color: var(--color-text) !important; - width: 16px !important; - height: 16px !important; - top: 10px !important; - } - } + // .ant-notification-notice-close { + // font-size: 12px !important; + // color: var(--color-text) !important; + // width: 16px !important; + // height: 16px !important; + // top: 10px !important; + // } + // } } } From eb7a29457a62e74e77a1ad331fa3b9b1bcd77c62 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 16 Jul 2023 17:20:39 +0800 Subject: [PATCH 0336/1069] add pg default database --- .../src/components/CreateConnection/config/dataSource.ts | 2 +- .../java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index bd9c27a8e..84aaf5553 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -324,7 +324,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ } }, { - defaultValue: '', + defaultValue: 'postgres', inputType: InputType.INPUT, labelNameCN: '数据库', labelNameEN: 'Database', diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java index ec82655b3..ae17b7927 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java @@ -1,15 +1,15 @@ package ai.chat2db.plugin.sqlserver; +import java.sql.SQLException; + import ai.chat2db.spi.DBManage; import ai.chat2db.spi.sql.SQLExecutor; -import java.sql.SQLException; - public class SqlServerDBManage implements DBManage { @Override public void connectDatabase(String database) { try { - SQLExecutor.getInstance().execute("use " + database + ";"); + SQLExecutor.getInstance().execute("use [" + database + "];"); } catch (SQLException e) { throw new RuntimeException(e); } From cd599b2572a92c479e4c377cfa697d5ce233cc26 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 16 Jul 2023 18:51:48 +0800 Subject: [PATCH 0337/1069] add pg default database --- .../java/ai/chat2db/spi/ssh/SSHManager.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java index 0f06f4dec..92053106a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java @@ -17,17 +17,23 @@ public class SSHManager { public static Session getSSHSession(SSHInfo ssh) { - Session session; + Session session = null; try { - byte[] passphrase = StringUtils.isNotBlank(ssh.getPassphrase()) ? StringUtils.getBytes(ssh.getPassphrase(), - "UTF-8") : null; - session = JschUtil.getSession(ssh.getHostName(), Integer.parseInt(ssh.getPort()), ssh.getUserName(), - ssh.getKeyFile(), passphrase); + if (StringUtils.isNotBlank(ssh.getKeyFile())) { + byte[] passphrase = StringUtils.isNotBlank(ssh.getPassphrase()) ? StringUtils.getBytes( + ssh.getPassphrase(), + "UTF-8") : null; + session = JschUtil.getSession(ssh.getHostName(), Integer.parseInt(ssh.getPort()), ssh.getUserName(), + ssh.getKeyFile(), passphrase); + } else if (StringUtils.isNotBlank(ssh.getUserName())) { + session = JschUtil.getSession(ssh.getHostName(), Integer.parseInt(ssh.getPort()), ssh.getUserName(), + ssh.getPassword()); + } } catch (Exception e) { throw new ConnectionException("connection.ssh.error", null, e); } - if (StringUtils.isNotBlank(ssh.getRHost()) && StringUtils.isNotBlank(ssh.getRPort())) { + if (session != null && StringUtils.isNotBlank(ssh.getRHost()) && StringUtils.isNotBlank(ssh.getRPort())) { try { int localPort = !StringUtils.isBlank(ssh.getLocalPort()) ? Integer.parseInt(ssh.getLocalPort()) : NetUtil.getUsableLocalPort(); From 53533d85f53fc2e4efb8b67964d9f92f3d957627 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 16 Jul 2023 20:15:42 +0800 Subject: [PATCH 0338/1069] feat: add loading for result table --- .../src/components/Console/index.tsx | 5 ++- .../src/components/SearchResult/TableBox.tsx | 6 ++-- .../src/components/SearchResult/index.tsx | 32 ++++++++++++------- .../components/WorkspaceRightItem/index.tsx | 11 +++++-- 4 files changed, 37 insertions(+), 17 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index b29ac157e..c5802175d 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -71,6 +71,7 @@ interface IProps { dispatch: Function; // remainingUse: IAIState['remainingUse']; // onSQLContentChange: (v: string) => void; + onExecuteSQLBefore?: () => void; onExecuteSQL: (result: any, sql: string, createHistoryParams) => void; onConsoleSave: () => void; tables: any[]; @@ -314,6 +315,8 @@ function Console(props: IProps) { }; const executeSQL = (sql?: string) => { + props.onExecuteSQLBefore && props.onExecuteSQLBefore(); + const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); if (!sqlContent) { @@ -423,7 +426,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index 8b2891edc..bc429539d 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -4,7 +4,7 @@ import { IManageResultData, ITableHeaderItem } from '@/typings/database'; import { formatDate } from '@/utils/date'; import { Button, message, Modal, Table } from 'antd'; import antd from 'antd'; -import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; +import { BaseTable, ArtColumn, useTablePipeline, features, SortItem, BaseTableProps } from 'ali-react-table'; import Iconfont from '../Iconfont'; import classnames from 'classnames'; import StateIndicator from '../StateIndicator'; @@ -19,6 +19,7 @@ import { compareStrings } from '@/utils/sort'; interface ITableProps { className?: string; data: IManageResultData; + isLoading?: boolean; } interface IViewTableCellData { @@ -42,7 +43,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` `; export default function TableBox(props: ITableProps) { - const { className, data } = props; + const { className, data, isLoading } = props; const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); @@ -151,6 +152,7 @@ export default function TableBox(props: ITableProps) {

{i18n('common.text.noData')}

}} + isLoading={isLoading} {...pipeline.getProps()} />
{`${i18n('common.text.result')}:${description}. ${i18n( diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 64cb749aa..51fc93477 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -17,6 +17,7 @@ import TableBox from './TableBox'; interface IProps { className?: string; manageResultDataList?: IManageResultData[]; + isLoading?: boolean; } interface DataType { @@ -41,7 +42,7 @@ const handleTabs = (result: IManageResultData[]) => { }); }; -export default memo(function SearchResult({ className, manageResultDataList = [] }) { +export default memo(function SearchResult({ className, manageResultDataList = [], isLoading }) { const [isUnfold, setIsUnfold] = useState(true); const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState([]); @@ -49,15 +50,15 @@ export default memo(function SearchResult({ className, manageResultDataL useEffect(() => { if (!manageResultDataList.length) { - return + return; } - const newManageResultDataList = manageResultDataList.map(t => { + const newManageResultDataList = manageResultDataList.map((t) => { return { ...t, - uuid: uuidv4() - } - }) - setCurrentTab(newManageResultDataList[0].uuid) + uuid: uuidv4(), + }; + }); + setCurrentTab(newManageResultDataList[0].uuid); setResultDataList(newManageResultDataList); setTabs(handleTabs(newManageResultDataList)); }, [manageResultDataList]); @@ -86,7 +87,7 @@ export default memo(function SearchResult({ className, manageResultDataL } const renderEmpty = () => { - return
{i18n('common.text.noData')}
; + return
{i18n('common.text.noData')}
; }; const renderTable = useMemo(() => { @@ -103,16 +104,23 @@ export default memo(function SearchResult({ className, manageResultDataL ); } else { - return - - ; + return ( + + + + ); } }); - }, [currentTab]) + }, [currentTab]); return (
diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 69ddc5497..f0294ea5a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -35,6 +35,7 @@ const WorkspaceRightItem = memo(function (props) { const [resultData, setResultData] = useState([]); const { doubleClickTreeNodeData, curTableList, curWorkspaceParams } = workspaceModel; const [showResult, setShowResult] = useState(false); + const [tableLoading, setTableLoading] = useState(false); useEffect(() => { if (!doubleClickTreeNodeData) { @@ -57,16 +58,20 @@ const WorkspaceRightItem = memo(function (props) {
{ + setTableLoading(true); + }} onExecuteSQL={(res: any, a: any, params: any) => { setResultData(res); setShowResult(true); + setTableLoading(false); historyServer.createHistory(params); }} onConsoleSave={() => { @@ -89,7 +94,9 @@ const WorkspaceRightItem = memo(function (props) { remainingUse={aiModel.remainingUse} />
-
{}
+
+ {} +
); From db70a0970c4b8a6e604a3354ec1a8664294a0e93 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 16 Jul 2023 20:31:10 +0800 Subject: [PATCH 0339/1069] Update CHANGELOG.md --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e4f246b2..b61f2d449 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# 2.0.3 +## ⭐ New Features +- Support DB2 database +- Support renaming after console saving +- Support prompts during SQL execution + +## 🐞 Bug Fixes +- Fix the bug that the database in sqlserver is all numbers +- Fix ssh connection bug + +## ⭐ 新特性 +- 支持DB2数据库 +- 支持控制台保存后重命名 +- 支持SQL执行中提示 + +## 🐞 问题修复 +- 修复sqlserver中database全是数字的bug +- 修复ssh连接 bug + # 2.0.2 ## ⭐ New Features From 956ab4e7d01d664f16c3ccfedfddea18cbc74bfb Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 16 Jul 2023 20:34:55 +0800 Subject: [PATCH 0340/1069] feat: add loading for result table --- .../src/components/SearchResult/index.less | 20 ++++++++++++------- .../src/components/SearchResult/index.tsx | 8 +++++--- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index f8880aaac..d6ce57473 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -38,11 +38,17 @@ } } -.resultContent { +.resultContentWrapper { flex: 1; - width: 100%; - position: relative; - overflow: auto; + + :global { + .ant-spin-container { + height: 100%; + width: 100%; + position: relative; + overflow: auto; + } + } } .tableStatus { @@ -91,14 +97,14 @@ align-items: center; } -.stateIndicator{ +.stateIndicator { display: none; } -.cursorStateIndicator{ +.cursorStateIndicator { margin: 0 auto; max-width: 80%; display: flex; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 51fc93477..b90c427c1 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -5,7 +5,7 @@ import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; import LoadingContent from '@/components/Loading/LoadingContent'; import MonacoEditor from '@/components/Console/MonacoEditor'; -import { Button, DatePicker, Input, Table, Modal, message } from 'antd'; +import { Button, DatePicker, Input, Table, Modal, message, Spin } from 'antd'; import { StatusType, TableDataType } from '@/constants'; import { formatDate } from '@/utils/date'; import { IManageResultData, ITableHeaderItem } from '@/typings'; @@ -104,7 +104,7 @@ export default memo(function SearchResult({ className, manageResultDataL ); @@ -137,7 +137,9 @@ export default memo(function SearchResult({ className, manageResultDataL />
) : null} -
{renderTable}
+ + {renderTable} +
); }); From ddfe42100a8ada4961e984451f8b175eab436b9e Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 16 Jul 2023 20:48:10 +0800 Subject: [PATCH 0341/1069] support function trigger view --- .../domain/api/service/FunctionService.java | 22 ++ .../domain/api/service/ProcedureService.java | 16 ++ .../domain/api/service/TriggerService.java | 17 ++ .../domain/api/service/ViewService.java | 22 ++ .../domain/core/impl/FunctionServiceImpl.java | 15 ++ .../core/impl/ProcedureServiceImpl.java | 16 ++ .../domain/core/impl/TriggerServiceImpl.java | 15 ++ .../domain/core/impl/ViewServiceImpl.java | 16 ++ .../controller/rdb/DatabaseController.java | 93 +++++++++ .../controller/rdb/FunctionController.java | 27 +++ .../controller/rdb/ProcedureController.java | 24 +++ .../api/controller/rdb/RdbDdlController.java | 3 + .../api/controller/rdb/SchemaController.java | 90 ++++++++ .../api/controller/rdb/TableController.java | 197 ++++++++++++++++++ .../api/controller/rdb/TriggerController.java | 27 +++ .../api/controller/rdb/ViewController.java | 35 ++++ .../main/java/ai/chat2db/spi/MetaData.java | 2 +- .../chat2db/spi/jdbc/DefaultMetaService.java | 2 +- 18 files changed, 637 insertions(+), 2 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java new file mode 100644 index 000000000..779cc4a56 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Function; +import jakarta.validation.constraints.NotEmpty; + +/** + * author jipengfei + * date 2021/9/23 15:22 + */ +public interface FunctionService { + + /** + * Querying all functions under a schema. + * + * @param databaseName + * @return + */ + ListResult functions(@NotEmpty String databaseName, String schemaName); + + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java new file mode 100644 index 000000000..3e7004e3c --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Procedure; +import jakarta.validation.constraints.NotEmpty; + +public interface ProcedureService { + + /** + * Querying all procedures under a schema. + * + * @param databaseName + * @return + */ + ListResult procedures(@NotEmpty String databaseName, String schemaName); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java new file mode 100644 index 000000000..e051b93b5 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Trigger; +import jakarta.validation.constraints.NotEmpty; + +public interface TriggerService { + + /** + * Querying all triggers under a schema. + * + * @param databaseName + * @return + */ + ListResult triggers(@NotEmpty String databaseName, String schemaName); + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java new file mode 100644 index 000000000..b79fe9c3e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Table; +import jakarta.validation.constraints.NotEmpty; + +/** + * author jipengfei + * date 2021/9/23 15:22 + */ +public interface ViewService { + + /** + * Querying all views under a schema. + * + * @param databaseName + * @return + */ + ListResult
views(@NotEmpty String databaseName, String schemaName); + + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java new file mode 100644 index 000000000..2844055bb --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.service.FunctionService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.sql.Chat2DBContext; +import org.springframework.stereotype.Service; + +@Service +public class FunctionServiceImpl implements FunctionService { + @Override + public ListResult functions(String databaseName, String schemaName) { + return ListResult.of(Chat2DBContext.getMetaData().functions(databaseName, schemaName)); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java new file mode 100644 index 000000000..8b17720c8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.service.ProcedureService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.sql.Chat2DBContext; +import org.springframework.stereotype.Service; + +@Service +public class ProcedureServiceImpl implements ProcedureService { + + @Override + public ListResult procedures(String databaseName, String schemaName) { + return ListResult.of(Chat2DBContext.getMetaData().procedures(databaseName, schemaName)); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java new file mode 100644 index 000000000..d72174b70 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.service.TriggerService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.sql.Chat2DBContext; +import org.springframework.stereotype.Service; + +@Service +public class TriggerServiceImpl implements TriggerService { + @Override + public ListResult triggers(String databaseName, String schemaName) { + return ListResult.of(Chat2DBContext.getMetaData().triggers(databaseName, schemaName)); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java new file mode 100644 index 000000000..150791fbb --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.service.ViewService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.sql.Chat2DBContext; +import org.springframework.stereotype.Service; + +@Service +public class ViewServiceImpl implements ViewService { + + @Override + public ListResult
views(String databaseName, String schemaName) { + return ListResult.of(Chat2DBContext.getMetaData().views(databaseName, schemaName)); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java new file mode 100644 index 000000000..e8a4e46f5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java @@ -0,0 +1,93 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.param.DatabaseOperationParam; +import ai.chat2db.server.domain.api.param.MetaDataQueryParam; +import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.domain.api.service.DlTemplateService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.UpdateDatabaseRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.MetaSchemaVO; +import ai.chat2db.spi.model.MetaSchema; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/rdb/datbase") +@RestController +public class DatabaseController { + + @Autowired + private TableService tableService; + + @Autowired + private DlTemplateService dlTemplateService; + + @Autowired + private RdbWebConverter rdbWebConverter; + + @Autowired + private DatabaseService databaseService; + /** + * 查询数据库里包含的database_schema_list + * + * @param request + * @return + */ + @GetMapping("/database_schema_list") + public DataResult databaseSchemaList(@Valid DataSourceBaseRequest request) { + MetaDataQueryParam queryParam = MetaDataQueryParam.builder().dataSourceId(request.getDataSourceId()).refresh( + request.isRefresh()).build(); + DataResult result = databaseService.queryDatabaseSchema(queryParam); + MetaSchemaVO schemaDto2vo = rdbWebConverter.metaSchemaDto2vo(result.getData()); + return DataResult.of(schemaDto2vo); + } + + + /** + * 删除数据库 + * + * @param request + * @return + */ + @PostMapping("/delete_database") + public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest request) { + DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); + return databaseService.deleteDatabase(param); + } + + /** + * 创建database + * + * @param request + * @return + */ + @PostMapping("/create_database") + public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest request) { + DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); + return databaseService.createDatabase(param); + } + + + /** + * 修改database + * + * @param request + * @return + */ + @PostMapping("/modify_database") + public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { + DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()) + .newDatabaseName(request.getNewDatabaseName()).build(); + return databaseService.modifyDatabase(param); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java new file mode 100644 index 000000000..aaad10328 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.service.FunctionService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.spi.model.Function; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/rdb/function") +@RestController +public class FunctionController { + + @Autowired + private FunctionService functionService; + + + @GetMapping("/list") + public ListResult list(@Valid TableBriefQueryRequest request) { + return functionService.functions(request.getDatabaseName(), request.getSchemaName()); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java new file mode 100644 index 000000000..3dd2e28bc --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java @@ -0,0 +1,24 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.service.ProcedureService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.spi.model.Procedure; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/rdb/procedure") +@RestController +public class ProcedureController { + + private ProcedureService procedureService; + + @GetMapping("/list") + public ListResult list(@Valid TableBriefQueryRequest request) { + return procedureService.procedures(request.getDatabaseName(), request.getSchemaName()); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java index 0c0eb29ee..f76ae22e2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java @@ -45,6 +45,7 @@ @ConnectionInfoAspect @RequestMapping("/api/rdb/ddl") @RestController +@Deprecated public class RdbDdlController { @Autowired @@ -183,6 +184,8 @@ public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request return databaseService.modifySchema(param); } + + /** * 查询当前DB下的表columns * d diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java new file mode 100644 index 000000000..3ffd95312 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java @@ -0,0 +1,90 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import java.util.List; + +import ai.chat2db.server.domain.api.param.SchemaOperationParam; +import ai.chat2db.server.domain.api.param.SchemaQueryParam; +import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.domain.api.service.DlTemplateService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.UpdateSchemaRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.SchemaVO; +import ai.chat2db.spi.model.Schema; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +public class SchemaController { + + + @Autowired + private TableService tableService; + + @Autowired + private DlTemplateService dlTemplateService; + + @Autowired + private RdbWebConverter rdbWebConverter; + + @Autowired + private DatabaseService databaseService; + + /** + * 查询数据库里包含的schema_list + * + * @param request + * @return + */ + @GetMapping("/list") + public ListResult list(@Valid DataSourceBaseRequest request) { + SchemaQueryParam queryParam = SchemaQueryParam.builder().dataBaseName(request.getDatabaseName()).build(); + ListResult tableColumns = databaseService.querySchema(queryParam); + List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); + return ListResult.of(tableVOS); + } + + /** + * 删除schema + * + * @param request + * @return + */ + @PostMapping("/delete_schema") + public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { + SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()).build(); + return databaseService.deleteSchema(param); + } + + /** + * 创建schema + * + * @param request + * @return + */ + @PostMapping("/create_schema") + public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest request) { + SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()).build(); + return databaseService.createSchema(param); + } + + /** + * 创建database + * + * @param request + * @return + */ + @PostMapping("/modify_schema") + public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { + SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) + .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); + return databaseService.modifySchema(param); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java new file mode 100644 index 000000000..2be6f0623 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -0,0 +1,197 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import java.util.List; + +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.service.DatabaseService; +import ai.chat2db.server.domain.api.service.DlTemplateService; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableCreateDdlQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableModifySqlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableUpdateDdlQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; +import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; +import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import com.google.common.collect.Lists; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/rdb/table") +@RestController +public class TableController { + + @Autowired + private TableService tableService; + + @Autowired + private DlTemplateService dlTemplateService; + + @Autowired + private RdbWebConverter rdbWebConverter; + + @Autowired + private DatabaseService databaseService; + + + /** + * 查询当前DB下的表列表 + * + * @param request + * @return + */ + @GetMapping("/list") + public WebPageResult list(@Valid TableBriefQueryRequest request) { + TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(false); + tableSelector.setIndexList(false); + PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); + List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); + return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), + request.getPageSize()); + } + + + /** + * 查询当前DB下的表columns + * d + * + * @param request + * @return + */ + @GetMapping("/column_list") + public ListResult columnList(@Valid TableDetailQueryRequest request) { + TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); + List tableColumns = tableService.queryColumns(queryParam); + List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); + return ListResult.of(tableVOS); + } + + /** + * 查询当前DB下的表index + * + * @param request + * @return + */ + @GetMapping("/index_list") + public ListResult indexList(@Valid TableDetailQueryRequest request) { + TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); + List tableIndices = tableService.queryIndexes(queryParam); + List indexVOS = rdbWebConverter.indexDto2vo(tableIndices); + return ListResult.of(indexVOS); + } + + /** + * 查询当前DB下的表key + * + * @param request + * @return + */ + @GetMapping("/key_list") + public ListResult keyList(@Valid TableDetailQueryRequest request) { + // TODO 增加查询key实现 + return ListResult.of(Lists.newArrayList()); + } + + /** + * 导出建表语句 + * + * @param request + * @return + */ + @GetMapping("/export") + public DataResult export(@Valid DdlExportRequest request) { + ShowCreateTableParam param = rdbWebConverter.ddlExport2showCreate(request); + return tableService.showCreateTable(param); + } + + /** + * 建表语句样例 + * + * @param request + * @return + */ + @GetMapping("/create/example") + public DataResult createExample(@Valid TableCreateDdlQueryRequest request) { + return tableService.createTableExample(request.getDbType()); + } + + /** + * 更新表语句样例 + * + * @param request + * @return + */ + @GetMapping("/update/example") + public DataResult updateExample(@Valid TableUpdateDdlQueryRequest request) { + return tableService.alterTableExample(request.getDbType()); + } + + /** + * 获取表下列和索引等信息 + * + * @param request + * @return + */ + @GetMapping("/query") + public DataResult query(@Valid TableDetailQueryRequest request) { + TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(true); + tableSelector.setIndexList(true); + DataResult
tableDTODataResult = tableService.query(queryParam, tableSelector); + TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); + return DataResult.of(tableVO); + } + + /** + * 获取修改表的sql语句 + * + * @param request + * @return + */ + @GetMapping("/modify/sql") + public ListResult modifySql(@Valid TableModifySqlRequest request) { + return tableService.buildSql( + rdbWebConverter.tableRequest2param(request.getOldTable()), + rdbWebConverter.tableRequest2param(request.getNewTable())) + .map(rdbWebConverter::dto2vo); + } + + /** + * 删除表 + * + * @param request + * @return + */ + @PostMapping("/delete") + public ActionResult delete(@Valid @RequestBody TableDeleteRequest request) { + DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); + return tableService.drop(dropParam); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java new file mode 100644 index 000000000..da7bcb979 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.service.TriggerService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.spi.model.Trigger; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/rdb/trigger") +@RestController +public class TriggerController { + + @Autowired + private TriggerService triggerService; + + + @GetMapping("/list") + public ListResult list(@Valid TableBriefQueryRequest request) { + return triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java new file mode 100644 index 000000000..f2daa2ab3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import java.util.List; + +import ai.chat2db.server.domain.api.service.ViewService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.spi.model.Table; +import jakarta.validation.Valid; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/rdb/view") +@RestController +public class ViewController { + @Autowired + private ViewService viewService; + + + @Autowired + private RdbWebConverter rdbWebConverter; + + @GetMapping("/list") + public ListResult list(@Valid TableBriefQueryRequest request) { + ListResult
tableDTOPageResult = viewService.views(request.getDatabaseName(), request.getSchemaName()); + List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); + return ListResult.of(tableVOS); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 56c7ed3dd..512a21168 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -51,7 +51,7 @@ public interface MetaData { * @param databaseName * @return */ - List views(@NotEmpty String databaseName, String schemaName); + List
views(@NotEmpty String databaseName, String schemaName); /** * Querying all functions under a schema. diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 2629e23df..cb7729d83 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -56,7 +56,7 @@ public List
tables(String databaseName, String schemaName, String tableNa } @Override - public List views(String databaseName, String schemaName) { + public List
views(String databaseName, String schemaName) { return SQLExecutor.getInstance().tables(databaseName, schemaName, null, new String[]{"VIEW"}); } From 968e7c7775f9905797d6161619015b88c626406a Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 16 Jul 2023 21:10:48 +0800 Subject: [PATCH 0342/1069] support function trigger view --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b61f2d449..b1db96b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.0.3 +# 2.0.4 ## ⭐ New Features - Support DB2 database - Support renaming after console saving From e02c504592dc940724cdfd303576b3a90fea76f2 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 16 Jul 2023 21:15:28 +0800 Subject: [PATCH 0343/1069] add --- README.md | 10 +++++----- README_CN.md | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index ab562478f..804884e13 100644 --- a/README.md +++ b/README.md @@ -48,11 +48,11 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 ## ⏬ Download and Install | Description | Download | -| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe) | -| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg) | -| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg) | -| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar) | +| ----------- |--------------------------------------------------------------------------------------------------------------------------------------------------------| +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB%20Setup%202.0.4.exe](https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB%20Setup%202.0.4.exe) | +| MacOS ARM64 | [https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4-arm64.dmg) | +| MacOS X64 | [https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4.dmg](https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4.dmg) | +| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.4/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.4/chat2db-server-start.jar) | ## 🚀 Supported databases diff --git a/README_CN.md b/README_CN.md index 685fa5e5d..c9087e72e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -53,12 +53,12 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 ## ⏬ 下载安装 -| 描述 | 下载地址 | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Windows | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB%20Setup%202.0.2.exe) | -| MacOS ARM64 (Apple 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2-arm64.dmg) | -| MacOS X64 (Intel 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg](https://oss-chat2db.alibaba.com/release/2.0.2/Chat2DB-2.0.2.dmg) | -| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.2/chat2db-server-start.jar) | +| 描述 | 下载地址 | +| ------------------------ |--------------------------------------------------------------------------------------------------------------------------------------------------------| +| Windows | [https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB%20Setup%202.0.4.exe](https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB%20Setup%202.0.4.exe) | +| MacOS ARM64 (Apple 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4-arm64.dmg](https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4-arm64.dmg) | +| MacOS X64 (Intel 芯片) | [https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4.dmg](https://oss-chat2db.alibaba.com/release/2.0.4/Chat2DB-2.0.4.dmg) | +| Jar 包 | [https://oss-chat2db.alibaba.com/release/2.0.4/chat2db-server-start.jar](https://oss-chat2db.alibaba.com/release/2.0.4/chat2db-server-start.jar) | ## 🚀 支持的数据库 From 3fe8a1ea8e25af9817fa5e7733a2987848ee520b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 16 Jul 2023 22:04:31 +0800 Subject: [PATCH 0344/1069] readme --- README.md | 2 ++ README_CN.md | 2 ++ chat2db-client/readme.md | 14 +++++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ab562478f..5f696a802 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,8 @@ $ git clone git@github.com:alibaba/Chat2DB.git - Front-End debug ```bash +node version must be 16 or later +Be sure to use yarn $ cd Chat2DB/chat2db-client $ yarn $ yarn run start:web diff --git a/README_CN.md b/README_CN.md index 685fa5e5d..188a6bdd9 100644 --- a/README_CN.md +++ b/README_CN.md @@ -157,6 +157,8 @@ $ git clone git@github.com:chat2db/Chat2DB.git - 前端调试 ```bash +node版本必须为16及以上 +一定要用yarn $ cd Chat2DB/chat2db-client $ yarn $ yarn run start:web diff --git a/chat2db-client/readme.md b/chat2db-client/readme.md index 9a9cdb332..0e3c8be6e 100644 --- a/chat2db-client/readme.md +++ b/chat2db-client/readme.md @@ -9,9 +9,17 @@ 目录结构 tree ./ -L 2 -I node_modules ## 启动项目 - 0. 强制使用yarn,因为环境变量、lock文件只维护了yarn,npm/pnpm可能会产生意想不到的bug - 1. `yarn` - 2. `yarn run start:web` + 强制使用yarn,因为环境变量、lock文件只维护了yarn,npm/pnpm可能会产生意想不到的bug + node版本要求16以上 + `npm i -g yarn` + `yarn` + `yarn run build:web:prod` + `cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front` (复制打包结果到指定目录。windows可能命令不一样,可以手动复制下) + 之后就可以启动后端了 `mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml` + + 启动前端项目调试 + `yarn run start:web` + 注意:因为electron包比较难下载,如果yarn时electron下载失败或超时,可以删除掉chat2db-client/package.json下的electron,再次yarn ## TS书写规范 1. 所有的interfase 与 type 必须已I开头 From 2ed08a917dbb961f7f4edbb3bc125c4715bc351d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 17 Jul 2023 08:52:14 +0800 Subject: [PATCH 0345/1069] feat: Lowered version of electron --- chat2db-client/package.json | 2 +- chat2db-client/yarn.lock | 596 +++++++++++++++++++----------------- 2 files changed, 324 insertions(+), 274 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 78d8c263a..03c659f71 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -53,7 +53,7 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", - "electron": "^25.2.0", + "electron": "^22.0.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 298b52a12..6c716d1dd 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -42,7 +42,7 @@ dependencies: "@ctrl/tinycolor" "^3.4.0" -"@ant-design/cssinjs@^1.10.1", "@ant-design/cssinjs@^1.11.0", "@ant-design/cssinjs@^1.9.1": +"@ant-design/cssinjs@^1.10.1", "@ant-design/cssinjs@^1.11.1", "@ant-design/cssinjs@^1.9.1": version "1.11.1" resolved "https://registry.npmmirror.com/@ant-design/cssinjs/-/cssinjs-1.11.1.tgz#d6e12c30c60abfefa3ed3d700a67d93fb4329cc5" integrity sha512-ya0wpkOzBTdQX4u2h6xpluflKPPQuq7LtvJQ9ThDXwu6t67CNFr6SJCEvkuQ9+4rU89VhYMP4IUaTaqYgtsBTQ== @@ -87,14 +87,14 @@ resolved "https://registry.npmmirror.com/@ant-design/moment-webpack-plugin/-/moment-webpack-plugin-0.0.3.tgz#2524f513b2f0b223b94b99626be281d0a334123f" integrity sha512-MLm1FUpg02fP615ShQnCUN9la2E4RylDxKyolkGqAWTIHO4HyGM0A5x71AMALEyP/bC+UEEWBGSQ+D4/8hQ+ww== -"@ant-design/pro-card@2.5.4": - version "2.5.4" - resolved "https://registry.npmmirror.com/@ant-design/pro-card/-/pro-card-2.5.4.tgz#413ad2efe5d4d1add964e063357d052ba9c90cc5" - integrity sha512-xrgcFv2hjioMu5rOGNNJ4BpizqlvhmE2FjNcgagsJYpe+k8YXd24LWlfTzKalFAwTp8AWahZSsaMIImeI4slEw== +"@ant-design/pro-card@2.5.5": + version "2.5.5" + resolved "https://registry.npmmirror.com/@ant-design/pro-card/-/pro-card-2.5.5.tgz#9b3850f8f62e06e4941c5b6bd76188562b03bc14" + integrity sha512-X1+DnZO9DzrGowOF2j1zq+vtc+qdNECx762uqw73vtSgL9EY+fq5pQmascqIpzR6MyxSREmUr1/0md8he8bzkg== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-provider" "2.11.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" classnames "^2.3.2" omit.js "^2.0.2" @@ -102,44 +102,44 @@ rc-util "^5.4.0" "@ant-design/pro-components@^2.0.1": - version "2.6.4" - resolved "https://registry.npmmirror.com/@ant-design/pro-components/-/pro-components-2.6.4.tgz#f533b3c8789973953a9bf4e9f03c7a8e429e2977" - integrity sha512-6y4PvKtlj910t0blK1y8HzJ3zjdG7Rr3cXqG8aGm4bAazl0a/Qe4o9OPiL48TXG18AyatvWKvZV3aOQ8fym8pA== - dependencies: - "@ant-design/pro-card" "2.5.4" - "@ant-design/pro-descriptions" "2.4.4" - "@ant-design/pro-field" "2.10.4" - "@ant-design/pro-form" "2.15.0" - "@ant-design/pro-layout" "7.16.0" - "@ant-design/pro-list" "2.5.4" - "@ant-design/pro-provider" "2.11.0" + version "2.6.5" + resolved "https://registry.npmmirror.com/@ant-design/pro-components/-/pro-components-2.6.5.tgz#396e1c339fdfb7b9c92060967e2c1351b6f62d20" + integrity sha512-aC+2RT3pss3E6lfe8TQPV9DcukbcFXog5NQTIkkW/Byes5rUou8NC05UWXQrTxdUm9Z8iPbX116GhZ50MI7h/A== + dependencies: + "@ant-design/pro-card" "2.5.5" + "@ant-design/pro-descriptions" "2.4.5" + "@ant-design/pro-field" "2.10.5" + "@ant-design/pro-form" "2.15.1" + "@ant-design/pro-layout" "7.16.1" + "@ant-design/pro-list" "2.5.5" + "@ant-design/pro-provider" "2.11.1" "@ant-design/pro-skeleton" "2.1.6" - "@ant-design/pro-table" "3.9.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-table" "3.9.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.16.3" -"@ant-design/pro-descriptions@2.4.4": - version "2.4.4" - resolved "https://registry.npmmirror.com/@ant-design/pro-descriptions/-/pro-descriptions-2.4.4.tgz#4032fd4399b8649f80ef4fe2d4975713b110c994" - integrity sha512-g07nuQkcLA4GaIiS81jFcDvTtDNVI2/GwAeETalrP5sgaH3q1IuQtwoAj4UwtXCM5bKt8stc7KEz9oQI3z9Dcg== +"@ant-design/pro-descriptions@2.4.5": + version "2.4.5" + resolved "https://registry.npmmirror.com/@ant-design/pro-descriptions/-/pro-descriptions-2.4.5.tgz#19b3327c8d7a5bee56777fd1ef5ad91b05636455" + integrity sha512-sLuyj2p7+CO++t+4jMnlJ6f3zLjtF4ALnbRPhq2DfKks5HbOKWx6K184lM/uX6C4MLsioUHJSqQH+5M9QGn3uQ== dependencies: - "@ant-design/pro-field" "2.10.4" - "@ant-design/pro-form" "2.15.0" + "@ant-design/pro-field" "2.10.5" + "@ant-design/pro-form" "2.15.1" "@ant-design/pro-skeleton" "2.1.6" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" rc-resize-observer "^0.2.3" rc-util "^5.0.6" use-json-comparison "^1.0.5" -"@ant-design/pro-field@2.10.4": - version "2.10.4" - resolved "https://registry.npmmirror.com/@ant-design/pro-field/-/pro-field-2.10.4.tgz#4752ef2e07f66e4941650934a73e7d153e36f852" - integrity sha512-P8lyVk2F9WTumestswI1rP7/nPv/TZm1EMTImTyQQGl2C/dat2Z5YIHLqWLNDJdicdpwNKYrYRJsmLfCGW+7kg== +"@ant-design/pro-field@2.10.5": + version "2.10.5" + resolved "https://registry.npmmirror.com/@ant-design/pro-field/-/pro-field-2.10.5.tgz#cb3fc406f60ad5e10e06f5cb34c6748b507378b0" + integrity sha512-wid+aEdCmiBcur4lslDbyI1j+J+A/EnaMnxA0xu+q8ontbm9NcHyO/klo4njDQ5tsGLATmKqLmJlyAXry9ENRg== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-provider" "2.11.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" "@chenshuai2144/sketch-color" "^1.0.8" classnames "^2.3.2" @@ -149,15 +149,15 @@ rc-util "^5.4.0" swr "^2.0.0" -"@ant-design/pro-form@2.15.0": - version "2.15.0" - resolved "https://registry.npmmirror.com/@ant-design/pro-form/-/pro-form-2.15.0.tgz#a0df517146335cc21b26cf71c517996c0c85ef71" - integrity sha512-Dzr7SfSN4zvdXpD6bhJztVrCgPH3vGQbBRyvs4d29LspfwzOoEKcp+wI3IH5eOUpT3xSL/FZ5LIxTYmlYrE6Hg== +"@ant-design/pro-form@2.15.1": + version "2.15.1" + resolved "https://registry.npmmirror.com/@ant-design/pro-form/-/pro-form-2.15.1.tgz#05d5faa42505dbecbefd0b6f98a588962e5bab3b" + integrity sha512-IWsrb8Cah2InW115a23OqIIVxUHqSvzzZXRw+u1CoTscQ7DZeIEzuBxDTdLKRuYR0UfeRr2pDfpp/sersAfN5g== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-field" "2.10.4" - "@ant-design/pro-provider" "2.11.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-field" "2.10.5" + "@ant-design/pro-provider" "2.11.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" "@chenshuai2144/sketch-color" "^1.0.7" "@umijs/use-params" "^1.0.9" @@ -169,14 +169,14 @@ use-json-comparison "^1.0.5" use-media-antd-query "^1.1.0" -"@ant-design/pro-layout@7.16.0": - version "7.16.0" - resolved "https://registry.npmmirror.com/@ant-design/pro-layout/-/pro-layout-7.16.0.tgz#74edb4773ee3f9b1224931cf4d5874b819d8a39a" - integrity sha512-i1XsvpsLGkMaIGaRX0XQaeR/wMRLSE76UVT0IV/yTMfRZEv28myWvuY7hnfrD0kMtDgobIilBohLkPLY5SX+aw== +"@ant-design/pro-layout@7.16.1": + version "7.16.1" + resolved "https://registry.npmmirror.com/@ant-design/pro-layout/-/pro-layout-7.16.1.tgz#cc24e463609d74b4b8ea8e8305725b65e6adc9ca" + integrity sha512-RUWsJoFgcC5bWUfsNwZxXwFZE5SHxFtNf4ZDOa2eznKGht2jUJNS+ur297dOvWGQk6G9nNswsbv/K0irZwYpQA== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-provider" "2.11.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" "@umijs/route-utils" "^4.0.0" "@umijs/use-params" "^1.0.9" @@ -191,16 +191,16 @@ use-media-antd-query "^1.1.0" warning "^4.0.3" -"@ant-design/pro-list@2.5.4": - version "2.5.4" - resolved "https://registry.npmmirror.com/@ant-design/pro-list/-/pro-list-2.5.4.tgz#6e1582838ffbac1acdcb28016ac012d12155f457" - integrity sha512-02sq49sczWt7mHf1EYog8NNdlGVNar0dBJN8Od+qEDn7e3szNeprUrJ1Qs+N21pCbCpEI0p49fu40eZrZReA8g== +"@ant-design/pro-list@2.5.5": + version "2.5.5" + resolved "https://registry.npmmirror.com/@ant-design/pro-list/-/pro-list-2.5.5.tgz#3a66e66c5c482d2b51983090ec462a3a38bd3f92" + integrity sha512-t7736GGymS1Z0iCp5kcl6jsY0lFNnHbH5A1JrOZ+8gKa9LHIbX7n9CResLwVc6jtjBFcz8RkrYaMcmpK2HhCyg== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-card" "2.5.4" - "@ant-design/pro-field" "2.10.4" - "@ant-design/pro-table" "3.9.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-card" "2.5.5" + "@ant-design/pro-field" "2.10.5" + "@ant-design/pro-table" "3.9.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" classnames "^2.3.2" dayjs "^1.11.9" @@ -208,12 +208,12 @@ rc-util "^4.19.0" use-media-antd-query "^1.1.0" -"@ant-design/pro-provider@2.11.0": - version "2.11.0" - resolved "https://registry.npmmirror.com/@ant-design/pro-provider/-/pro-provider-2.11.0.tgz#41e45c1a5858edcc3b7e2cbd0064dfc5d0cfb0d3" - integrity sha512-y9/mkonB7Kg92aXexyPzkvOj9BT5+ZOREcBkNOW+jsOiz8AUOD212z0J4Zvf1KkoDQUJBqOiCE5oV8Y1DVkJbg== +"@ant-design/pro-provider@2.11.1": + version "2.11.1" + resolved "https://registry.npmmirror.com/@ant-design/pro-provider/-/pro-provider-2.11.1.tgz#063b1157766a571fd2c06a6db4f52a5590976ffd" + integrity sha512-A7zXZ+58IGVuIAGvo8Hia9Wz0TFfFMo+tIv16Pu2RF9sMdGg4pE5M87qT7+45TkKodWpnpBd4cIte3jfY5v/LQ== dependencies: - "@ant-design/cssinjs" "^1.11.0" + "@ant-design/cssinjs" "^1.11.1" "@babel/runtime" "^7.18.0" "@ctrl/tinycolor" "^3.4.0" rc-util "^5.0.1" @@ -227,17 +227,17 @@ "@babel/runtime" "^7.18.0" use-media-antd-query "^1.1.0" -"@ant-design/pro-table@3.9.0": - version "3.9.0" - resolved "https://registry.npmmirror.com/@ant-design/pro-table/-/pro-table-3.9.0.tgz#a7d07b326c75eb7d8f172c70e7c3b3440ac4330c" - integrity sha512-BwNP9NrPeOSV5ns1SS1mfwHUsFc3Xu/cEIZnLle7bm4vGwR0cxfSTtLoPOlkrLxoVuHG0p5Y44PkOBO4kb30sg== +"@ant-design/pro-table@3.9.1": + version "3.9.1" + resolved "https://registry.npmmirror.com/@ant-design/pro-table/-/pro-table-3.9.1.tgz#cd133aa0a25655fbd52825055c8ec24e979722a8" + integrity sha512-7HmVlNCU3N1Wx5japnjjb8/1a6Qlf9pcYlMq2AaBaPg3+KCKMtOMw5Y1VpXxQQ80Iayzk6CKVu7n33UTcVQxlA== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-card" "2.5.4" - "@ant-design/pro-field" "2.10.4" - "@ant-design/pro-form" "2.15.0" - "@ant-design/pro-provider" "2.11.0" - "@ant-design/pro-utils" "2.12.3" + "@ant-design/pro-card" "2.5.5" + "@ant-design/pro-field" "2.10.5" + "@ant-design/pro-form" "2.15.1" + "@ant-design/pro-provider" "2.11.1" + "@ant-design/pro-utils" "2.12.4" "@babel/runtime" "^7.18.0" "@dnd-kit/core" "^6.0.8" "@dnd-kit/sortable" "^7.0.2" @@ -249,13 +249,13 @@ rc-util "^5.0.1" use-json-comparison "^1.0.5" -"@ant-design/pro-utils@2.12.3": - version "2.12.3" - resolved "https://registry.npmmirror.com/@ant-design/pro-utils/-/pro-utils-2.12.3.tgz#e943d0c9921588ab98b14bbb0c9db24a6385f508" - integrity sha512-NM0QCltIa1Iht5AKHX90IsHeiJSXJkZ7u2YZoSWG9Mvc71huiKyzc9pgxnJRKBsSZrVMfe3jeeK419pJvaXdnA== +"@ant-design/pro-utils@2.12.4": + version "2.12.4" + resolved "https://registry.npmmirror.com/@ant-design/pro-utils/-/pro-utils-2.12.4.tgz#a6cfdb6f579eac9fcb2e9a064e7e59cc037670c8" + integrity sha512-cNAZ2+PGbl5FfsUWnlChu3MgdGc58JTelv6vIevZCPcrIEt2AN4TmDXvUpXaaOrmW5IIXs070oWQQnCfBzPaVA== dependencies: "@ant-design/icons" "^5.0.0" - "@ant-design/pro-provider" "2.11.0" + "@ant-design/pro-provider" "2.11.1" "@babel/runtime" "^7.18.0" classnames "^2.3.2" dayjs "^1.11.9" @@ -287,9 +287,9 @@ integrity sha512-dlR6LdS+0SzOAPx/TPRhnoi7hE251OVeT2Snw0RguNbBSbjUHdWr0l3vcUUDg26rEysT89kCbtw1lVorBXLLCg== "@babel/cli@^7.21.0": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/cli/-/cli-7.22.6.tgz#63f5be2a0abd587ccfbdc93424fa85f43142cc53" - integrity sha512-Be3/RfEDmkMRGT1+ru5nTkfcvWz5jDOYg1V9rXqTz2u9Qt96O1ryboGvxVBp7wOnYWDB8DNHIWb6DThrpudfOw== + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/cli/-/cli-7.22.9.tgz#501b3614aeda7399371f6d5991404f069b059986" + integrity sha512-nb2O7AThqRo7/E53EGiuAkMaRbb7J5Qp3RvN+dmua1U+kydm0oznkhqbTEG15yk26G/C3yL6OdZjzgl+DMXVVA== dependencies: "@jridgewell/trace-mapping" "^0.3.17" commander "^4.0.1" @@ -309,10 +309,10 @@ dependencies: "@babel/highlight" "^7.22.5" -"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544" - integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg== +"@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" + integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== "@babel/core@7.21.0": version "7.21.0" @@ -336,25 +336,25 @@ semver "^6.3.0" "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.17.9", "@babel/core@^7.19.6", "@babel/core@^7.21.0", "@babel/core@^7.21.4": - version "7.22.8" - resolved "https://registry.npmmirror.com/@babel/core/-/core-7.22.8.tgz#386470abe884302db9c82e8e5e87be9e46c86785" - integrity sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw== + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/core/-/core-7.22.9.tgz#bd96492c68822198f33e8a256061da3cf391f58f" + integrity sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.22.5" - "@babel/generator" "^7.22.7" - "@babel/helper-compilation-targets" "^7.22.6" - "@babel/helper-module-transforms" "^7.22.5" + "@babel/generator" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" + "@babel/helper-module-transforms" "^7.22.9" "@babel/helpers" "^7.22.6" "@babel/parser" "^7.22.7" "@babel/template" "^7.22.5" "@babel/traverse" "^7.22.8" "@babel/types" "^7.22.5" - "@nicolo-ribaudo/semver-v6" "^6.3.3" convert-source-map "^1.7.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.2" + semver "^6.3.1" "@babel/eslint-parser@7.19.1": version "7.19.1" @@ -365,10 +365,10 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.0" -"@babel/generator@^7.21.0", "@babel/generator@^7.22.7": - version "7.22.7" - resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.22.7.tgz#a6b8152d5a621893f2c9dacf9a4e286d520633d5" - integrity sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ== +"@babel/generator@^7.21.0", "@babel/generator@^7.22.7", "@babel/generator@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/generator/-/generator-7.22.9.tgz#572ecfa7a31002fa1de2a9d91621fd895da8493d" + integrity sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw== dependencies: "@babel/types" "^7.22.5" "@jridgewell/gen-mapping" "^0.3.2" @@ -389,40 +389,40 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz#e30d61abe9480aa5a83232eb31c111be922d2e52" - integrity sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA== +"@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz#f9d0a7aaaa7cd32a3f31c9316a69f5a9bcacb892" + integrity sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw== dependencies: - "@babel/compat-data" "^7.22.6" + "@babel/compat-data" "^7.22.9" "@babel/helper-validator-option" "^7.22.5" - "@nicolo-ribaudo/semver-v6" "^6.3.3" browserslist "^4.21.9" lru-cache "^5.1.1" + semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.5": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz#58564873c889a6fea05a538e23f9f6d201f10950" - integrity sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ== +"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.5", "@babel/helper-create-class-features-plugin@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" + integrity sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-function-name" "^7.22.5" "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/helper-replace-supers" "^7.22.5" + "@babel/helper-replace-supers" "^7.22.9" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/helper-split-export-declaration" "^7.22.6" - "@nicolo-ribaudo/semver-v6" "^6.3.3" + semver "^6.3.1" "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5": - version "7.22.6" - resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz#87afd63012688ad792de430ceb3b6dc28e4e7a40" - integrity sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA== + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6" + integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@nicolo-ribaudo/semver-v6" "^6.3.3" regexpu-core "^5.3.1" + semver "^6.3.1" "@babel/helper-define-polyfill-provider@^0.4.1": version "0.4.1" @@ -469,19 +469,16 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-module-transforms@^7.21.0", "@babel/helper-module-transforms@^7.21.2", "@babel/helper-module-transforms@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef" - integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw== +"@babel/helper-module-transforms@^7.21.0", "@babel/helper-module-transforms@^7.21.2", "@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" + integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-module-imports" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" - "@babel/types" "^7.22.5" "@babel/helper-optimise-call-expression@^7.22.5": version "7.22.5" @@ -496,26 +493,22 @@ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== "@babel/helper-remap-async-to-generator@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2" - integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g== + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82" + integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-environment-visitor" "^7.22.5" - "@babel/helper-wrap-function" "^7.22.5" - "@babel/types" "^7.22.5" + "@babel/helper-wrap-function" "^7.22.9" -"@babel/helper-replace-supers@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc" - integrity sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg== +"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779" + integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg== dependencies: "@babel/helper-environment-visitor" "^7.22.5" "@babel/helper-member-expression-to-functions" "^7.22.5" "@babel/helper-optimise-call-expression" "^7.22.5" - "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" - "@babel/types" "^7.22.5" "@babel/helper-simple-access@^7.20.2", "@babel/helper-simple-access@^7.22.5": version "7.22.5" @@ -531,7 +524,7 @@ dependencies: "@babel/types" "^7.22.5" -"@babel/helper-split-export-declaration@^7.22.5", "@babel/helper-split-export-declaration@^7.22.6": +"@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.npmmirror.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== @@ -553,14 +546,13 @@ resolved "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== -"@babel/helper-wrap-function@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06" - integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw== +"@babel/helper-wrap-function@^7.22.9": + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.9.tgz#189937248c45b0182c1dcf32f3444ca153944cb9" + integrity sha512-sZ+QzfauuUEfxSEjKFmi3qDSHgLsTPK/pEpoD/qonZKOtTPTLbf59oabPQ4rKekt9lFcj/hTZaOhWwFYrgjk+Q== dependencies: "@babel/helper-function-name" "^7.22.5" "@babel/template" "^7.22.5" - "@babel/traverse" "^7.22.5" "@babel/types" "^7.22.5" "@babel/helpers@^7.21.0", "@babel/helpers@^7.22.6": @@ -1203,12 +1195,12 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-transform-typescript@^7.22.5": - version "7.22.5" - resolved "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.5.tgz#5c0f7adfc1b5f38c4dbc8f79b1f0f8074134bd7d" - integrity sha512-SMubA9S7Cb5sGSFFUlqxyClTA9zWJ8qGQrppNUm05LtFuN1ELRFNndkix4zUJrC9F+YivWwa1dHMSyo0e0N9dA== + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.9.tgz#91e08ad1eb1028ecc62662a842e93ecfbf3c7234" + integrity sha512-BnVR1CpKiuD0iobHPaM1iLvcwPYN2uVFAqoLVSpEDKWuOikoCv5HbKLxclhKYUXlWkX86DoZGtqI4XhbOsyrMg== dependencies: "@babel/helper-annotate-as-pure" "^7.22.5" - "@babel/helper-create-class-features-plugin" "^7.22.5" + "@babel/helper-create-class-features-plugin" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" @@ -1244,12 +1236,12 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/preset-env@^7.20.2": - version "7.22.7" - resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.22.7.tgz#a1ef34b64a80653c22ce4d9c25603cfa76fc168a" - integrity sha512-1whfDtW+CzhETuzYXfcgZAh8/GFMeEbz0V5dVgya8YeJyCU6Y/P2Gnx4Qb3MylK68Zu9UiwUvbPMPTpFAOJ+sQ== + version "7.22.9" + resolved "https://registry.npmmirror.com/@babel/preset-env/-/preset-env-7.22.9.tgz#57f17108eb5dfd4c5c25a44c1977eba1df310ac7" + integrity sha512-wNi5H/Emkhll/bqPjsjQorSykrlfY5OWakd6AulLvMEytpKasMVUpVy8RL4qBIBs5Ac6/5i0/Rv0b/Fg6Eag/g== dependencies: - "@babel/compat-data" "^7.22.6" - "@babel/helper-compilation-targets" "^7.22.6" + "@babel/compat-data" "^7.22.9" + "@babel/helper-compilation-targets" "^7.22.9" "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-option" "^7.22.5" "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5" @@ -1323,11 +1315,11 @@ "@babel/plugin-transform-unicode-sets-regex" "^7.22.5" "@babel/preset-modules" "^0.1.5" "@babel/types" "^7.22.5" - "@nicolo-ribaudo/semver-v6" "^6.3.3" babel-plugin-polyfill-corejs2 "^0.4.4" babel-plugin-polyfill-corejs3 "^0.8.2" babel-plugin-polyfill-regenerator "^0.5.1" core-js-compat "^3.31.0" + semver "^6.3.1" "@babel/preset-modules@^0.1.5": version "0.1.5" @@ -1375,7 +1367,7 @@ dependencies: regenerator-runtime "^0.13.11" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.18.0", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.21.0", "@babel/runtime@^7.22.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.7.7", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.22.6" resolved "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438" integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ== @@ -1391,7 +1383,7 @@ "@babel/parser" "^7.22.5" "@babel/types" "^7.22.5" -"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8", "@babel/traverse@^7.4.5": +"@babel/traverse@^7.21.0", "@babel/traverse@^7.21.2", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8", "@babel/traverse@^7.4.5": version "7.22.8" resolved "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e" integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw== @@ -2039,10 +2031,10 @@ resolved "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== -"@rc-component/color-picker@~1.2.0": - version "1.2.0" - resolved "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.2.0.tgz#964c86e85f0791703c7f1ec842e7476bcb41954d" - integrity sha512-IitJ6RWGHs7btI1AqzGPrehr5bueWLGDUyMKwDwvFunfSDo/o8g/95kUG55vC5EYLM0ZJ3SDfw45OrW5KAx3oA== +"@rc-component/color-picker@~1.4.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@rc-component/color-picker/-/color-picker-1.4.0.tgz#dbeb1b552adf4a35948f76e4ff887c8e1af24342" + integrity sha512-hNlJQnrT/vFwpS5+OhoFw7LHcBbmEBxS1RugJuCuL5FBqp9pMIAy2E8uqg87MvxqIsAccwd5r+vvaLterT+t7g== dependencies: "@babel/runtime" "^7.10.1" "@ctrl/tinycolor" "^3.6.0" @@ -2229,26 +2221,26 @@ dependencies: remove-accents "0.4.2" -"@tanstack/query-core@4.29.19": - version "4.29.19" - resolved "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-4.29.19.tgz#49ccbd0606633d1e55baf3b91ab7cc7aef411b1d" - integrity sha512-uPe1DukeIpIHpQi6UzIgBcXsjjsDaLnc7hF+zLBKnaUlh7jFE/A+P8t4cU4VzKPMFB/C970n/9SxtpO5hmIRgw== +"@tanstack/query-core@4.29.25": + version "4.29.25" + resolved "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-4.29.25.tgz#605d357968a740544af6754004eed1dfd4587cb8" + integrity sha512-DI4y4VC6Uw4wlTpOocEXDky69xeOScME1ezLKsj+hOk7DguC9fkqXtp6Hn39BVb9y0b5IBrY67q6kIX623Zj4Q== "@tanstack/react-query-devtools@^4.24.10": - version "4.29.19" - resolved "https://registry.npmmirror.com/@tanstack/react-query-devtools/-/react-query-devtools-4.29.19.tgz#b6f337c91313388d3f04c890449005d7bb355322" - integrity sha512-rL2xqTPr+7gJvVGwyq8E8CWqqw950N4lZ6ffJeNX0qqymKHxHW1FM6nZaYt7Aufs/bXH0m1L9Sj3kDGQbp0rwg== + version "4.29.25" + resolved "https://registry.npmmirror.com/@tanstack/react-query-devtools/-/react-query-devtools-4.29.25.tgz#89d2ce3848ff4ff8873978e63ec5a063dbf63616" + integrity sha512-XlrGUqmjv1O+6Ny23rAiyNSWYKep90SKT3IixDQRnIuTGaZej+hVCOh7wZSxq6qkzadIvsblc4SLtyJsOiIXBQ== dependencies: "@tanstack/match-sorter-utils" "^8.7.0" superjson "^1.10.0" use-sync-external-store "^1.2.0" "@tanstack/react-query@^4.24.10": - version "4.29.19" - resolved "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-4.29.19.tgz#6ba187f2d0ea36ae83ff1f67068f53c88ce7b228" - integrity sha512-XiTIOHHQ5Cw1WUlHaD4fmVUMhoWjuNJlAeJGq7eM4BraI5z7y8WkZO+NR8PSuRnQGblpuVdjClQbDFtwxTtTUw== + version "4.29.25" + resolved "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-4.29.25.tgz#64df3260b65760fbd3c81ffae23b7b3802c71aa6" + integrity sha512-c1+Ezu+XboYrdAMdusK2fTdRqXPMgPAnyoTrzHOZQqr8Hqz6PNvV9DSKl8agUo6nXX4np7fdWabIprt+838dLg== dependencies: - "@tanstack/query-core" "4.29.19" + "@tanstack/query-core" "4.29.25" use-sync-external-store "^1.2.0" "@tootallnate/once@2": @@ -2425,14 +2417,14 @@ integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== "@types/node@*": - version "20.4.1" - resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.1.tgz#a6033a8718653c50ac4962977e14d0f984d9527d" - integrity sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg== + version "20.4.2" + resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" + integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== -"@types/node@^18.11.18": - version "18.16.19" - resolved "https://registry.npmmirror.com/@types/node/-/node-18.16.19.tgz#cb03fca8910fdeb7595b755126a8a78144714eea" - integrity sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA== +"@types/node@^16.11.26": + version "16.18.38" + resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.38.tgz#1dcdb6c54d02b323f621213745f2e44af30c73e6" + integrity sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ== "@types/parse-json@^4.0.0": version "4.0.0" @@ -2453,16 +2445,16 @@ integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== "@types/react-dom@^18.0.11": - version "18.2.6" - resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.6.tgz#ad621fa71a8db29af7c31b41b2ea3d8a6f4144d1" - integrity sha512-2et4PDvg6PVCyS7fuTc4gPoksV58bW0RwSxWKcPRcHZf0PRUGq03TKcD/rUHe3azfV6/5/biUBJw+HhCQjaP0A== + version "18.2.7" + resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.2.7.tgz#67222a08c0a6ae0a0da33c3532348277c70abb63" + integrity sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA== dependencies: "@types/react" "*" "@types/react@*", "@types/react@^18.0.33": - version "18.2.14" - resolved "https://registry.npmmirror.com/@types/react/-/react-18.2.14.tgz#fa7a6fecf1ce35ca94e74874f70c56ce88f7a127" - integrity sha512-A0zjq+QN/O0Kpe30hA1GidzyFjatVvrpIvWLxD+xv67Vt91TWWgco9IvrJBkeyHm1trGaFS/FSGqPlhyeZRm0g== + version "18.2.15" + resolved "https://registry.npmmirror.com/@types/react/-/react-18.2.15.tgz#14792b35df676c20ec3cf595b262f8c615a73066" + integrity sha512-oEjE7TQt1fFTFSbf8kkNuc798ahTUzn3Le67/PWjE8MAfYAD/qB7O8hSTcromLFqHCt9bcdOg5GXMokzTjJ5SA== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -3023,9 +3015,9 @@ react-refresh "^0.14.0" "@xmldom/xmldom@^0.8.8": - version "0.8.8" - resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.8.tgz#d0d11511cbc1de77e53342ad1546a4d487d6ea72" - integrity sha512-0LNz4EY8B/8xXY86wMrQ4tz6zEHZv9ehFMJPm8u2gq5lQ71cfRKdaKyxfJAx5aUoyzx0qzgURblTisPGgz3d+Q== + version "0.8.9" + resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be" + integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA== acorn@^8.8.2: version "8.10.0" @@ -3124,9 +3116,9 @@ antd-dayjs-webpack-plugin@^1.0.6: integrity sha512-UlK3BfA0iE2c5+Zz/Bd2iPAkT6cICtrKG4/swSik5MZweBHtgmu1aUQCHvICdiv39EAShdZy/edfP6mlkS/xXg== antd@^5.6.0: - version "5.6.4" - resolved "https://registry.npmmirror.com/antd/-/antd-5.6.4.tgz#689d74ba61181ba6ea87a0c5d249d30d116ce602" - integrity sha512-ttAN5vk6yUybDCe5WFloEb49dyLwyec+FJlvopfZFSkScHX2OBbfpPlCQ50Bpp2u5P/eqN6EQUM4PsE4MPslAA== + version "5.7.0" + resolved "https://registry.npmmirror.com/antd/-/antd-5.7.0.tgz#2762e505c1fad16db871e7133fe551b4946a7089" + integrity sha512-jUu6wfoN633rKsTSJcfVtXYTMdVu885LoYm2A52Vur8gnYfP3GzekCuGpTXZKGZRwGLNs3D2HbpaHciusF+2Gg== dependencies: "@ant-design/colors" "^7.0.0" "@ant-design/cssinjs" "^1.10.1" @@ -3134,7 +3126,7 @@ antd@^5.6.0: "@ant-design/react-slick" "~1.0.0" "@babel/runtime" "^7.18.3" "@ctrl/tinycolor" "^3.6.0" - "@rc-component/color-picker" "~1.2.0" + "@rc-component/color-picker" "~1.4.0" "@rc-component/mutate-observer" "^1.0.0" "@rc-component/tour" "~1.8.0" "@rc-component/trigger" "^1.13.0" @@ -3149,15 +3141,15 @@ antd@^5.6.0: rc-drawer "~6.2.0" rc-dropdown "~4.1.0" rc-field-form "~1.34.0" - rc-image "~5.17.1" - rc-input "~1.0.4" - rc-input-number "~7.4.0" - rc-mentions "~2.3.0" - rc-menu "~9.9.2" + rc-image "~7.0.0-2" + rc-input "~1.1.0" + rc-input-number "~8.0.0" + rc-mentions "~2.5.0" + rc-menu "~9.10.0" rc-motion "^2.7.3" rc-notification "~5.0.4" rc-pagination "~3.5.0" - rc-picker "~3.8.2" + rc-picker "~3.10.0" rc-progress "~3.4.1" rc-rate "~2.12.0" rc-resize-observer "^1.2.0" @@ -3167,10 +3159,10 @@ antd@^5.6.0: rc-steps "~6.0.0" rc-switch "~4.1.0" rc-table "~7.32.1" - rc-tabs "~12.7.0" - rc-textarea "~1.2.2" + rc-tabs "~12.9.0" + rc-textarea "~1.3.2" rc-tooltip "~6.0.0" - rc-tree "~5.7.4" + rc-tree "~5.7.6" rc-tree-select "~5.9.0" rc-upload "~4.3.0" rc-util "^5.32.0" @@ -3301,6 +3293,18 @@ array.prototype.tosorted@^1.1.1: es-shim-unscopables "^1.0.0" get-intrinsic "^1.1.3" +arraybuffer.prototype.slice@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz#9b5ea3868a6eebc30273da577eb888381c0044bb" + integrity sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + asar@^3.1.0: version "3.2.0" resolved "https://registry.npmmirror.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221" @@ -3843,9 +3847,9 @@ camelize@^1.0.0: integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ== caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503: - version "1.0.30001515" - resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001515.tgz#418aefeed9d024cd3129bfae0ccc782d4cb8f12b" - integrity sha512-eEFDwUOZbE24sb+Ecsx3+OvNETqjWIdabMy52oOkIgcUtAsQifjUG9q4U9dgTHJM2mfk4uEPxc0+xuFdJ629QA== + version "1.0.30001516" + resolved "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001516.tgz#621b1be7d85a8843ee7d210fd9d87b52e3daab3a" + integrity sha512-Wmec9pCBY8CWbmI4HsjBeQLqDTqV91nFVR83DnZpYyRnPI1wePDsTg0bGLPC5VU/3OIZV1fmxEea1b+tFKe86g== chalk@^2.0.0: version "2.4.2" @@ -4724,17 +4728,17 @@ electron-reload@^2.0.0-alpha.1: chokidar "^3.5.2" electron-to-chromium@^1.4.431: - version "1.4.455" - resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.455.tgz#81fe4353ac970eb971c07088c8da8b7f6280ddc9" - integrity sha512-8tgdX0Odl24LtmLwxotpJCVjIndN559AvaOtd67u+2mo+IDsgsTF580NB+uuDCqsHw8yFg53l5+imFV9Fw3cbA== + version "1.4.461" + resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.461.tgz#6b14af66042732bf883ab63a4d82cac8f35eb252" + integrity sha512-1JkvV2sgEGTDXjdsaQCeSwYYuhLRphRpc+g6EHTFELJXEiznLt3/0pZ9JuAOQ5p2rI3YxKTbivtvajirIfhrEQ== -electron@^25.2.0: - version "25.2.0" - resolved "https://registry.npmmirror.com/electron/-/electron-25.2.0.tgz#ff832d88f78481a82cf9feb72e605ec43553d4ba" - integrity sha512-I/rhcW2sV2fyiveVSBr2N7v5ZiCtdGY0UiNCDZgk2fpSC+irQjbeh7JT2b4vWmJ2ogOXBjqesrN9XszTIG6DHg== +electron@^22.0.0: + version "22.3.17" + resolved "https://registry.npmmirror.com/electron/-/electron-22.3.17.tgz#90a75f78cc761ed536d8210dd001e142fca78691" + integrity sha512-mo9qD1pOkiibvH+pgETsq9RZF0p3O5ACwxzjk3E2ozMYb9cnJenZyE3jxbs4WqzDCFi+rsm6WWahw3hlPhANXw== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^18.11.18" + "@types/node" "^16.11.26" extract-zip "^2.0.1" elliptic@^6.5.3: @@ -4819,17 +4823,18 @@ error-stack-parser@^2.0.6: stackframe "^1.3.4" es-abstract@^1.19.0, es-abstract@^1.20.4: - version "1.21.2" - resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff" - integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg== + version "1.22.1" + resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.22.1.tgz#8b4e5fc5cefd7f1660f0f8e1a52900dfbc9d9ccc" + integrity sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw== dependencies: array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.1" available-typed-arrays "^1.0.5" call-bind "^1.0.2" es-set-tostringtag "^2.0.1" es-to-primitive "^1.2.1" function.prototype.name "^1.1.5" - get-intrinsic "^1.2.0" + get-intrinsic "^1.2.1" get-symbol-description "^1.0.0" globalthis "^1.0.3" gopd "^1.0.1" @@ -4849,14 +4854,18 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: object-inspect "^1.12.3" object-keys "^1.1.1" object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" + regexp.prototype.flags "^1.5.0" + safe-array-concat "^1.0.0" safe-regex-test "^1.0.0" string.prototype.trim "^1.2.7" string.prototype.trimend "^1.0.6" string.prototype.trimstart "^1.0.6" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" typed-array-length "^1.0.4" unbox-primitive "^1.0.2" - which-typed-array "^1.1.9" + which-typed-array "^1.1.10" es-get-iterator@^1.1.2: version "1.1.3" @@ -5383,7 +5392,7 @@ get-caller-file@^2.0.5: resolved "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0: +get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1: version "1.2.1" resolved "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82" integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw== @@ -6425,9 +6434,9 @@ keyboardevents-areequal@^0.2.1: integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== keyv@^4.0.0: - version "4.5.2" - resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.2.tgz#0e310ce73bf7851ec702f2eaf46ec4e3805cce56" - integrity sha512-5MHbFaKn8cNSmVW7BYnijeAVlE4cYA/SVkifVgrh7yotnfhKmjuXpDKjrABLnT0SfHWV21P8ow07OGfRrNDg8g== + version "4.5.3" + resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== dependencies: json-buffer "3.0.1" @@ -7620,9 +7629,9 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^ integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.7: - version "8.4.25" - resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.25.tgz#4a133f5e379eda7f61e906c3b1aaa9b81292726f" - integrity sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw== + version "8.4.26" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.4.26.tgz#1bc62ab19f8e1e5463d98cf74af39702a00a9e94" + integrity sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw== dependencies: nanoid "^3.3.6" picocolors "^1.0.0" @@ -7634,9 +7643,9 @@ prettier-plugin-organize-imports@^2: integrity sha512-R8o23sf5iVL/U71h9SFUdhdOEPsi3nm42FD/oDYIZ2PQa4TNWWuWecxln6jlIQzpZTDMUeO1NicJP6lLn2TtRw== prettier-plugin-organize-imports@^3.2.2: - version "3.2.2" - resolved "https://registry.npmmirror.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.2.tgz#91993365e017daa5d0d28d8183179834224d8dd1" - integrity sha512-e97lE6odGSiHonHJMTYC0q0iLXQyw0u5z/PJpvP/3vRy6/Zi9kLBwFAbEGjDzIowpjQv8b+J04PDamoUSQbzGA== + version "3.2.3" + resolved "https://registry.npmmirror.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz#6b0141ac71f7ee9a673ce83e95456319e3a7cf0d" + integrity sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg== prettier-plugin-packagejson@2.4.3: version "2.4.3" @@ -7891,68 +7900,69 @@ rc-dropdown@~4.1.0: rc-util "^5.17.0" rc-field-form@~1.34.0: - version "1.34.1" - resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.34.1.tgz#31be2ca12c7e37a4873e776dc7aee27965df53d8" - integrity sha512-oohdrjUHYWzY4H5EOw/9xk324oatZOKiCfo3FwnK9G/LswoqflWoxeaAGMkjI5Ug4YxSq80fehoJjVYApSheYA== + version "1.34.2" + resolved "https://registry.npmmirror.com/rc-field-form/-/rc-field-form-1.34.2.tgz#8463b79a44842a341899195f364e952c401ab7f1" + integrity sha512-BdciU5C7dBO51/9ZKcMvK2f8zaaO12Lt1eBhlAo8nNv+6htlNcgY9DAkUlZ7gfyWjnCc1Oo4hHIXau1m6tLw1A== dependencies: "@babel/runtime" "^7.18.0" async-validator "^4.1.0" rc-util "^5.32.2" -rc-image@~5.17.1: - version "5.17.1" - resolved "https://registry.npmmirror.com/rc-image/-/rc-image-5.17.1.tgz#71835b12c30fcef533de0dbbbaf13caa86454612" - integrity sha512-oR4eviLyQxd/5A7pn843w2/Z1wuBA27L2lS4agq0sjl2z97ssNIVEzRzgwgB0ZxVZG/qSu9Glit2Zgzb/n+blQ== +rc-image@~7.0.0-2: + version "7.0.0" + resolved "https://registry.npmmirror.com/rc-image/-/rc-image-7.0.0.tgz#cad2105dd1708304328bc9c178f7953cb0198403" + integrity sha512-pOr/LYthg5a+R2LDlFPv8u2ndX4aJQNghWCiWxflmLglC3p0uts/NIWLAituQOKvV1wO1aFI1CZtLMT7jrU3vA== dependencies: "@babel/runtime" "^7.11.2" "@rc-component/portal" "^1.0.2" classnames "^2.2.6" rc-dialog "~9.1.0" rc-motion "^2.6.2" - rc-util "^5.0.6" + rc-util "^5.34.1" -rc-input-number@~7.4.0: - version "7.4.2" - resolved "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-7.4.2.tgz#7c52d26b986461aa16e486d469dc0476d97c6ea3" - integrity sha512-yGturTw7WGP+M1GbJ+UTAO7L4buxeW6oilhL9Sq3DezsRS8/9qec4UiXUbeoiX9bzvRXH11JvgskBtxSp4YSNg== +rc-input-number@~8.0.0: + version "8.0.1" + resolved "https://registry.npmmirror.com/rc-input-number/-/rc-input-number-8.0.1.tgz#77b66977c353e496b21f5e31bdd10217c214b510" + integrity sha512-b5LIQ9hmqPI/5NwoRQsQXftrrmuXxcaO9pftFnq4AkjPVtfCG+sqcGEIBmXC/YkS4p6R6dwqclpQLFJyMYx7DA== dependencies: "@babel/runtime" "^7.10.1" "@rc-component/mini-decimal" "^1.0.1" classnames "^2.2.5" + rc-input "~1.1.0" rc-util "^5.28.0" -rc-input@~1.0.0, rc-input@~1.0.4: - version "1.0.4" - resolved "https://registry.npmmirror.com/rc-input/-/rc-input-1.0.4.tgz#2f2c73c884f41e80685bb2eb7b9d5533e8540a77" - integrity sha512-clY4oneVHRtKHYf/HCxT/MO+4BGzCIywSNLosXWOm7fcQAS0jQW7n0an8Raa8JMB8kpxc8m28p7SNwFZmlMj6g== +rc-input@~1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/rc-input/-/rc-input-1.1.0.tgz#14951ad5b9d77b756b6ddb5088cca464a60c1454" + integrity sha512-izuNXPABQPh4KD7ANFcTrIGp9EZU0FkjTw6AvwCQ/rGPrdDsUTHLsp/Wju/kzGMLJFJWKNF3smbmXRNO23DtXA== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" rc-util "^5.18.1" -rc-mentions@~2.3.0: - version "2.3.0" - resolved "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.3.0.tgz#bb457c9664093be82baf33628b145f7c2bd49577" - integrity sha512-gNpsSKsBHSXvyAA1ZowVTqXSWUIw7+OI9wmjL87KcYURvtm9nDo8R0KtOc2f1PT7q9McUpFzhm6AvQdIly0aRA== +rc-mentions@~2.5.0: + version "2.5.0" + resolved "https://registry.npmmirror.com/rc-mentions/-/rc-mentions-2.5.0.tgz#8b936e497e0deb922f40df46e42efc3f596ec207" + integrity sha512-rERXsbUTNVrb5T/iDC0ki/SRGWJnOVraDy6O25Us3FSpuUZ3uq2TPZB4fRk0Hss5kyiEPzz2sprhkI4b+F4jUw== dependencies: - "@babel/runtime" "^7.10.1" + "@babel/runtime" "^7.22.5" "@rc-component/trigger" "^1.5.0" classnames "^2.2.6" - rc-input "~1.0.0" - rc-menu "~9.9.0" - rc-textarea "~1.2.0" + rc-input "~1.1.0" + rc-menu "~9.10.0" + rc-textarea "~1.3.0" rc-util "^5.22.5" -rc-menu@~9.9.0, rc-menu@~9.9.2: - version "9.9.2" - resolved "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.9.2.tgz#733aa5b794bd801577726e448b6cfeda0436e1e5" - integrity sha512-kVJwaQn5VUu6DIddxd/jz3QupTPg0tNYq+mpFP8wYsRF5JgzPA9fPVw+CfwlTPwA1w7gzEY42S8pj6M3uev5CQ== +rc-menu@~9.10.0: + version "9.10.0" + resolved "https://registry.npmmirror.com/rc-menu/-/rc-menu-9.10.0.tgz#5e0982e26786d67c8ebdba50406b197884c749a7" + integrity sha512-g27kpXaAoJh/fkPZF65/d4V+w4DhDeqomBdPcGnkFAcJnEM4o21TnVccrBUoDedLKzC7wJRw1Q7VTqEsfEufmw== dependencies: "@babel/runtime" "^7.10.1" "@rc-component/trigger" "^1.6.2" classnames "2.x" rc-motion "^2.4.3" - rc-overflow "^1.2.8" + rc-overflow "^1.3.1" rc-util "^5.27.0" rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.0, rc-motion@^2.6.1, rc-motion@^2.6.2, rc-motion@^2.7.3: @@ -7974,7 +7984,7 @@ rc-notification@~5.0.4: rc-motion "^2.6.0" rc-util "^5.20.1" -rc-overflow@^1.0.0, rc-overflow@^1.2.8: +rc-overflow@^1.0.0, rc-overflow@^1.3.1: version "1.3.1" resolved "https://registry.npmmirror.com/rc-overflow/-/rc-overflow-1.3.1.tgz#03224cf90c66aa570eb0deeb4eff6cc96401e979" integrity sha512-RY0nVBlfP9CkxrpgaLlGzkSoh9JhjJLu6Icqs9E7CW6Ewh9s0peF9OHIex4OhfoPsR92LR0fN6BlCY9Z4VoUtA== @@ -7993,10 +8003,10 @@ rc-pagination@~3.5.0: classnames "^2.2.1" rc-util "^5.32.2" -rc-picker@~3.8.2: - version "3.8.2" - resolved "https://registry.npmmirror.com/rc-picker/-/rc-picker-3.8.2.tgz#1dc377a628cd94416e03974483daa36940a411b0" - integrity sha512-q6jnMwBoOi6tFA4xohrKIhzq80Fc3dH0Kiw5VRx6Tf1db7y27PBFCLwu6f66niXidZKD8F4R0M9VIui/jkL4cg== +rc-picker@~3.10.0: + version "3.10.0" + resolved "https://registry.npmmirror.com/rc-picker/-/rc-picker-3.10.0.tgz#d34e659d88782eb1eac5939a1be0d6ba508f6f42" + integrity sha512-Euki50qtEct6ByOeYlnA4TLs/LcXz7BAYS4cmCTKJ3dWg2sNTVtredLdbS9aJ/9fhMacxGAYAlcQJpQx+av43A== dependencies: "@babel/runtime" "^7.10.1" "@rc-component/trigger" "^1.5.0" @@ -8102,27 +8112,27 @@ rc-table@~7.32.1: rc-resize-observer "^1.1.0" rc-util "^5.27.1" -rc-tabs@~12.7.0: - version "12.7.1" - resolved "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-12.7.1.tgz#6bfd11cc7b2bec08600eb0aba41966b230c38906" - integrity sha512-NrltXEYIyiDP5JFu85NQwc9eR+7e50r/6MNXYDyG1EMIFNc7BgDppzdpnD3nW4NHYWw5wLIThCURGib48OCTBg== +rc-tabs@~12.9.0: + version "12.9.0" + resolved "https://registry.npmmirror.com/rc-tabs/-/rc-tabs-12.9.0.tgz#6d9af43d8ad2c47be00c75bee92417a4842d29d2" + integrity sha512-2HnVowgMVrq0DfQtyu4mCd9E6pXlWNdM6VaDvOOHMsLYqPmpY+7zBqUC6YrrQ9xYXHciTS0e7TtjOHIvpVCHLQ== dependencies: "@babel/runtime" "^7.11.2" classnames "2.x" rc-dropdown "~4.1.0" - rc-menu "~9.9.0" + rc-menu "~9.10.0" rc-motion "^2.6.2" rc-resize-observer "^1.0.0" rc-util "^5.16.0" -rc-textarea@~1.2.0, rc-textarea@~1.2.2: - version "1.2.3" - resolved "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.2.3.tgz#bdaea2931ad2571583e9e27e627b8a9b5dbe7de7" - integrity sha512-YvN8IskIVBRRzcS4deT0VAMim31+T3IoVX4yoCJ+b/iVCvw7yf0usR7x8OaHiUOUoURKcn/3lfGjmtzplcy99g== +rc-textarea@~1.3.0, rc-textarea@~1.3.2: + version "1.3.2" + resolved "https://registry.npmmirror.com/rc-textarea/-/rc-textarea-1.3.2.tgz#f7d8efec3b69e02346fa810a9ed0a7e32308bc2a" + integrity sha512-6+lq161wBLEr3ZM9IGxIWmuc7odKVobjtwQeEGHi1jiqqL9bFEipFedi4kA5RuVUgyVklDqYkK0MHdhtBb46yg== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" - rc-input "~1.0.4" + rc-input "~1.1.0" rc-resize-observer "^1.0.0" rc-util "^5.27.0" @@ -8146,10 +8156,10 @@ rc-tree-select@~5.9.0: rc-tree "~5.7.0" rc-util "^5.16.1" -rc-tree@~5.7.0, rc-tree@~5.7.4: - version "5.7.8" - resolved "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.7.8.tgz#778599d9a1052f25e2588dba7fddaf66257651b5" - integrity sha512-Ei+wID0SWA8BNCdEMO6UMblHs/jnSRDqz7csWXZ0o5VB08iDhxVnF+VHYTGDsJ9pARJ2xEXfjyTksOkEx5R4RQ== +rc-tree@~5.7.0, rc-tree@~5.7.6: + version "5.7.9" + resolved "https://registry.npmmirror.com/rc-tree/-/rc-tree-5.7.9.tgz#e0df730ffbba1df95901fd3b108267288056e162" + integrity sha512-1hKkToz/EVjJlMVwmZnpXeLXt/1iQMsaAq9m+GNkUbK746gkc7QpJXSN/TzjhTI5Hi+LOSlrMaXLMT0bHPqILQ== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -8186,9 +8196,9 @@ rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.15.0, rc-util@^5.16.0 react-is "^16.12.0" rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2: - version "3.5.2" - resolved "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.5.2.tgz#5e1028869bae900eacbae6788d4eca7210736006" - integrity sha512-sE2G9hTPjVmatQni8OP2Kx33+Oth6DMKm67OblBBmgMBJDJQOOFpSGH7KZ6Pm85rrI2IGxDRXZCr0QhYOH2pfQ== + version "3.5.3" + resolved "https://registry.npmmirror.com/rc-virtual-list/-/rc-virtual-list-3.5.3.tgz#84f82d3257f6c520106a6285558dfc764c41c076" + integrity sha512-rG6IuD4EYM8K6oZ8Shu2BC/CmcTdqng4yBWkc/5fjWhB20bl6QwR2Upyt7+MxvfscoVm8zOQY+tcpEO5cu4GaQ== dependencies: "@babel/runtime" "^7.20.0" classnames "^2.2.6" @@ -8416,7 +8426,7 @@ regenerator-transform@^0.15.1: dependencies: "@babel/runtime" "^7.8.4" -regexp.prototype.flags@^1.4.3: +regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0: version "1.5.0" resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb" integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA== @@ -8607,6 +8617,16 @@ rxjs@^7.8.1: dependencies: tslib "^2.1.0" +safe-array-concat@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.0.0.tgz#2064223cba3c08d2ee05148eedbc563cd6d84060" + integrity sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.0" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -8691,7 +8711,7 @@ semver@^5.6.0: resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.2.0, semver@^6.3.0: +semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== @@ -9106,9 +9126,9 @@ styled-components@6.0.0-rc.0: supports-color "^5.5.0" styled-components@^6.0.1: - version "6.0.3" - resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.0.3.tgz#03863133b8340912f52be623e3262ba9eb87ccc0" - integrity sha512-qEyWvDK4CYCyDckNIruRJIcQSvcUR3dVEw/fwxu1v0LFzUMPr2uf5PhXHp17FkGK+S4TkglOS+XIealo1MssQA== + version "6.0.4" + resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.0.4.tgz#55bb3a1197daf8075ae8b345b57eb03f2570d51e" + integrity sha512-lRJt4vg8hKJhlVG+VKz8QEqPCXKyTryZZ59odyK0UC0HHV3u/mshWTfSay8NpkN0Xijw1iN9r0Leld3dcCcp/w== dependencies: "@babel/cli" "^7.21.0" "@babel/core" "^7.21.0" @@ -9404,6 +9424,36 @@ type@^2.7.2: resolved "https://registry.npmmirror.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== +typed-array-buffer@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" + integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + is-typed-array "^1.1.10" + +typed-array-byte-length@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" + integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== + dependencies: + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + +typed-array-byte-offset@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" + integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== + dependencies: + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + for-each "^0.3.3" + has-proto "^1.0.1" + is-typed-array "^1.1.10" + typed-array-length@^1.0.4: version "1.0.4" resolved "https://registry.npmmirror.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" @@ -9689,7 +9739,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.1" is-weakset "^2.0.1" -which-typed-array@^1.1.9: +which-typed-array@^1.1.10, which-typed-array@^1.1.9: version "1.1.10" resolved "https://registry.npmmirror.com/which-typed-array/-/which-typed-array-1.1.10.tgz#74baa2789991905c2076abb317103b866c64e69e" integrity sha512-uxoA5vLUfRPdjCuJ1h5LlYdmTLbYfums398v3WLkM+i/Wltl2/XyZpQWKbN++ck5L64SR/grOHqtXCUKmlZPNA== From 3ea76339200ddd0384e193efd3f66707d9dc6a1e Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 17 Jul 2023 17:11:39 +0800 Subject: [PATCH 0346/1069] Update DatabaseServiceImpl.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复schema查询问题 --- .../domain/core/impl/DatabaseServiceImpl.java | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 47c47134e..c489473d4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -1,8 +1,7 @@ package ai.chat2db.server.domain.core.impl; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; +import java.util.concurrent.CountDownLatch; import ai.chat2db.server.domain.api.param.DatabaseOperationParam; import ai.chat2db.server.domain.api.param.DatabaseQueryAllParam; @@ -18,6 +17,8 @@ import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.sql.Chat2DBContext; +import cn.hutool.core.thread.ThreadUtil; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -28,6 +29,7 @@ * @version DataSourceCoreServiceImpl.java, v 0.1 2022年09月23日 15:51 moji Exp $ * @date 2022/09/23 */ +@Slf4j @Service public class DatabaseServiceImpl implements DatabaseService { @@ -48,19 +50,25 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); List databases = Chat2DBContext.getMetaData().databases(); if (!CollectionUtils.isEmpty(databases)) { - List schemaList = Chat2DBContext.getMetaData().schemas(null); - if (databases.size() == 1) { - databases.get(0).setSchemas(schemaList); - metaSchema.setDatabases(databases); - } else { - Map> schemaMap = schemaList.stream().collect( - Collectors.groupingBy( - schema -> schema.getDatabaseName() != null ? schema.getDatabaseName() : "")); - for (Database dataBase : databases) { - dataBase.setSchemas(schemaMap.get(dataBase.getName())); - } - metaSchema.setDatabases(databases); + + CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); + for (Database database : databases) { + ThreadUtil.execute(() -> { + try { + List schemaList = Chat2DBContext.getMetaData().schemas(database.getName()); + database.setSchemas(schemaList); + countDownLatch.countDown(); + } catch (Exception e) { + log.error("queryDatabaseSchema error", e); + } + }); + } + try { + countDownLatch.await(); + } catch (InterruptedException e) { + log.error("queryDatabaseSchema error", e); } + } else { List schemas = Chat2DBContext.getMetaData().schemas(null); metaSchema.setSchemas(schemas); @@ -71,7 +79,6 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { return DataResult.of(ms); } - @Override public ActionResult deleteDatabase(DatabaseOperationParam param) { Chat2DBContext.getDBManage().dropDatabase(param.getDatabaseName()); From 163c9d44474e7b199a4c4765faf2d5befbad221d Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 17 Jul 2023 18:00:53 +0800 Subject: [PATCH 0347/1069] Update DatabaseServiceImpl.java --- .../domain/core/impl/DatabaseServiceImpl.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index c489473d4..79ca9552b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -13,10 +13,12 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; import cn.hutool.core.thread.ThreadUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -45,32 +47,31 @@ public ListResult querySchema(SchemaQueryParam param) { @Override public DataResult queryDatabaseSchema(MetaDataQueryParam param) { + MetaSchema metaSchema = new MetaSchema(); + MetaData metaData = Chat2DBContext.getMetaData(); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); MetaSchema ms = CacheManage.get(getDataSourceKey(param.getDataSourceId()), MetaSchema.class, (key) -> param.isRefresh(), (key) -> { - MetaSchema metaSchema = new MetaSchema(); - List databases = Chat2DBContext.getMetaData().databases(); + List databases = metaData.databases(); if (!CollectionUtils.isEmpty(databases)) { - - CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); + // CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); for (Database database : databases) { - ThreadUtil.execute(() -> { - try { - List schemaList = Chat2DBContext.getMetaData().schemas(database.getName()); - database.setSchemas(schemaList); - countDownLatch.countDown(); - } catch (Exception e) { - log.error("queryDatabaseSchema error", e); - } - }); - } - try { - countDownLatch.await(); - } catch (InterruptedException e) { - log.error("queryDatabaseSchema error", e); + //ThreadUtil.execute(() -> { + try { + // Chat2DBContext.putContext(connectInfo); + database.setSchemas(metaData.schemas(database.getName())); + // countDownLatch.countDown(); + } catch (Exception e) { + log.error("queryDatabaseSchema error", e); + } finally { + //Chat2DBContext.removeContext(); + } + // }); } + metaSchema.setDatabases(databases); } else { - List schemas = Chat2DBContext.getMetaData().schemas(null); + List schemas = metaData.schemas(null); metaSchema.setSchemas(schemas); } return metaSchema; From 07bce36a11bd876530ee2c3c43902e81de4ae3ab Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 17 Jul 2023 20:51:06 +0800 Subject: [PATCH 0348/1069] add --- .../plugin/clickhouse/ClickHouseDBManage.java | 41 ++----------- .../ai/chat2db/plugin/db2/DB2DBManage.java | 43 ++----------- .../ai/chat2db/plugin/db2/DB2MetaData.java | 11 ++-- .../java/ai/chat2db/plugin/dm/DMDBManage.java | 42 ++----------- .../java/ai/chat2db/plugin/dm/DMMetaData.java | 9 +-- .../java/ai/chat2db/plugin/h2/H2DBManage.java | 42 ++----------- .../java/ai/chat2db/plugin/h2/H2Meta.java | 16 +++-- .../ai/chat2db/plugin/hive/HiveDBManage.java | 42 ++----------- .../plugin/kingbase/KingBaseDBManage.java | 43 ++----------- .../chat2db/plugin/mariadb/MariaDBManage.java | 42 ++----------- .../chat2db/plugin/mongodb/MongodbManage.java | 41 +------------ .../plugin/mongodb/MongodbMetaData.java | 1 + .../chat2db/plugin/mysql/MysqlDBManage.java | 44 +++---------- .../chat2db/plugin/mysql/MysqlMetaData.java | 9 +-- .../plugin/oceanbase/OceanBaseDBManage.java | 41 +------------ .../chat2db/plugin/oracle/OracleDBManage.java | 41 +------------ .../chat2db/plugin/oracle/OracleMetaData.java | 9 +-- .../plugin/postgresql/PostgreSQLDBManage.java | 49 +++------------ .../plugin/postgresql/PostgreSQLMetaData.java | 11 ++-- .../chat2db/plugin/presto/PrestoDBManage.java | 42 +------------ .../chat2db/plugin/redis/RedisDBManage.java | 41 +------------ .../chat2db/plugin/redis/RedisMetaData.java | 23 +++---- .../chat2db/plugin/sqlite/SqliteDBManage.java | 42 +------------ .../chat2db/plugin/sqlite/SqliteMetaData.java | 15 ++--- .../plugin/sqlserver/SqlServerDBManage.java | 43 ++----------- .../plugin/sqlserver/SqlServerMetaData.java | 11 ++-- .../domain/core/impl/ConsoleServiceImpl.java | 2 +- .../core/impl/DataSourceServiceImpl.java | 12 ++-- .../domain/core/impl/DatabaseServiceImpl.java | 53 ++++++++-------- .../core/impl/DlTemplateServiceImpl.java | 2 +- .../domain/core/impl/FunctionServiceImpl.java | 2 +- .../core/impl/ProcedureServiceImpl.java | 2 +- .../domain/core/impl/TableServiceImpl.java | 16 ++--- .../domain/core/impl/TriggerServiceImpl.java | 2 +- .../domain/core/impl/ViewServiceImpl.java | 2 +- .../main/java/ai/chat2db/spi/DBManage.java | 19 +++--- .../main/java/ai/chat2db/spi/MetaData.java | 49 +++++++++++---- .../ai/chat2db/spi/jdbc/DefaultDBManage.java | 21 +++---- .../chat2db/spi/jdbc/DefaultMetaService.java | 44 ++++++------- .../ai/chat2db/spi/sql/Chat2DBContext.java | 4 +- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 61 ++++++++++--------- 41 files changed, 292 insertions(+), 793 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java index d12cb5841..57bdcff97 100644 --- a/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-clickhouse/src/main/java/ai/chat2db/plugin/clickhouse/ClickHouseDBManage.java @@ -1,47 +1,18 @@ package ai.chat2db.plugin.clickhouse; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class ClickHouseDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { +public class ClickHouseDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE IF EXISTS " + databaseName + "." + tableName; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> null); } diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java index dcbd20fb0..874034247 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/DB2DBManage.java @@ -1,47 +1,16 @@ package ai.chat2db.plugin.db2; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class DB2DBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } +public class DB2DBManage extends DefaultDBManage implements DBManage { @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " + tableName; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } 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 f1cfd0dd2..7ee3e8443 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 @@ -1,11 +1,12 @@ package ai.chat2db.plugin.db2; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.sql.SQLExecutor; -import java.sql.SQLException; - public class DB2MetaData extends DefaultMetaService implements MetaData { private String functionSQL = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" @@ -37,17 +38,17 @@ public class DB2MetaData extends DefaultMetaService implements MetaData { @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { System.out.println(functionSQL); - SQLExecutor.getInstance().executeSql(functionSQL.replace("tableSchema", schemaName), resultSet -> null); + SQLExecutor.getInstance().executeSql(connection, functionSQL.replace("tableSchema", schemaName), resultSet -> null); } catch (Exception e) { //log.error("创建函数失败", e); } String ddlSql = "SELECT " + schemaName + ".ufn_GetCreateTableScript('" + schemaName + "', '" + tableName + "') AS sql"; - return SQLExecutor.getInstance().executeSql(ddlSql, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection, ddlSql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); diff --git a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java index 672a59c6b..a04f8a834 100644 --- a/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-dm/src/main/java/ai/chat2db/plugin/dm/DMDBManage.java @@ -1,47 +1,17 @@ package ai.chat2db.plugin.dm; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class DMDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { +public class DMDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE IF EXISTS " +tableName; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } 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 59ca3909e..cebcf5ffc 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 @@ -1,19 +1,20 @@ package ai.chat2db.plugin.dm; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SqlUtils; -import java.sql.SQLException; - public class DMMetaData extends DefaultMetaService implements MetaData { - public String tableDDL(String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String selectObjectDDLSQL = String.format( "select dbms_metadata.get_ddl(%s, %s, %s) AS \"sql\" from dual", SqlUtils.formatSQLString("TABLE"), SqlUtils.formatSQLString(tableName), SqlUtils.formatSQLString(schemaName)); - return SQLExecutor.getInstance().executeSql(selectObjectDDLSQL, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,selectObjectDDLSQL, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java index c5555b0a0..0e02f8ddf 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java @@ -1,47 +1,17 @@ package ai.chat2db.plugin.h2; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class H2DBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { +public class H2DBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " +tableName; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java index 28623b2ab..3c5a6d3fb 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java @@ -1,10 +1,5 @@ package ai.chat2db.plugin.h2; -import ai.chat2db.spi.MetaData; -import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.sql.SQLExecutor; -import jakarta.validation.constraints.NotEmpty; - import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; @@ -13,16 +8,19 @@ import java.util.List; import java.util.Map; +import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.jdbc.DefaultMetaService; +import jakarta.validation.constraints.NotEmpty; + public class H2Meta extends DefaultMetaService implements MetaData { @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - return getDDL(databaseName, schemaName, tableName); + public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { + return getDDL(connection,databaseName, schemaName, tableName); } - private String getDDL(String databaseName, String schemaName, String tableName) { + private String getDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { - Connection connection = SQLExecutor.getInstance().getConnection(); // 查询表结构信息 ResultSet columns = connection.getMetaData().getColumns(databaseName, schemaName, tableName, null); List columnDefinitions = new ArrayList<>(); diff --git a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java index 0480cc2be..054d7c1a2 100644 --- a/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-hive/src/main/java/ai/chat2db/plugin/hive/HiveDBManage.java @@ -1,47 +1,17 @@ package ai.chat2db.plugin.hive; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class HiveDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { +public class HiveDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "drop table if exists " +tableName; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java index 65cb32971..4ec375586 100644 --- a/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-kingbase/src/main/java/ai/chat2db/plugin/kingbase/KingBaseDBManage.java @@ -1,47 +1,16 @@ package ai.chat2db.plugin.kingbase; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class KingBaseDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } +public class KingBaseDBManage extends DefaultDBManage implements DBManage { @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "drop table if exists " +tableName; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java index 47862c21a..dccb20e11 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBManage.java @@ -1,47 +1,17 @@ package ai.chat2db.plugin.mariadb; +import java.sql.Connection; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class MariaDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { +public class MariaDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE " +tableName ; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbManage.java b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbManage.java index b0080eee8..334343116 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbManage.java @@ -1,45 +1,8 @@ package ai.chat2db.plugin.mongodb; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; -public class MongodbManage implements DBManage { - @Override - public void connectDatabase(String database) { +public class MongodbManage extends DefaultDBManage implements DBManage { - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } } diff --git a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java index 12210cea9..9cc1b7e48 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-mongodb/src/main/java/ai/chat2db/plugin/mongodb/MongodbMetaData.java @@ -3,6 +3,7 @@ import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; + public class MongodbMetaData extends DefaultMetaService implements MetaData { } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java index 35843b7b9..0728b822d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/MysqlDBManage.java @@ -1,58 +1,32 @@ package ai.chat2db.plugin.mysql; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; import org.springframework.util.StringUtils; -import java.sql.SQLException; - -public class MysqlDBManage implements DBManage { +public class MysqlDBManage extends DefaultDBManage implements DBManage { @Override - public void connectDatabase(String database) { + public void connectDatabase(Connection connection, String database) { if (StringUtils.isEmpty(database)) { return; } try { - SQLExecutor.getInstance().execute("use `" + database + "`;"); + SQLExecutor.getInstance().execute(connection,"use `" + database + "`;"); } catch (SQLException e) { throw new RuntimeException(e); } } - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE "+ format(tableName); - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } public static String format(String tableName) { 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 0b9c808b4..d98f6b7b2 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 @@ -1,18 +1,19 @@ package ai.chat2db.plugin.mysql; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; -import java.sql.SQLException; - public class MysqlMetaData extends DefaultMetaService implements MetaData { @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { + public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { String sql = "SHOW CREATE TABLE " + format(databaseName) + "." + format( tableName); - return SQLExecutor.getInstance().executeSql(sql, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("Create Table"); diff --git a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseDBManage.java b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseDBManage.java index c280a5878..0753639c5 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-oceanbase/src/main/java/ai/chat2db/plugin/oceanbase/OceanBaseDBManage.java @@ -1,45 +1,8 @@ package ai.chat2db.plugin.oceanbase; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; -public class OceanBaseDBManage implements DBManage { - @Override - public void connectDatabase(String database) { +public class OceanBaseDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java index 0c8167b7d..09f96f401 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java @@ -1,45 +1,8 @@ package ai.chat2db.plugin.oracle; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; -public class OracleDBManage implements DBManage { - @Override - public void connectDatabase(String database) { +public class OracleDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } } 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 e7043d32f..bb9b87f82 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 @@ -1,17 +1,18 @@ package ai.chat2db.plugin.oracle; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.sql.SQLExecutor; -import java.sql.SQLException; - public class OracleMetaData extends DefaultMetaService implements MetaData { @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "select dbms_metadata.get_ddl('TABLE','"+tableName+"') as sql from dual," + "user_tables where table_name = '" + tableName + "'"; - return SQLExecutor.getInstance().executeSql(sql, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java index e254d0093..f2f2ed9db 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java @@ -1,54 +1,19 @@ package ai.chat2db.plugin.postgresql; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.DBManage; -import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.sql.ConnectInfo; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -import java.sql.SQLException; - -public class PostgreSQLDBManage implements DBManage { +public class PostgreSQLDBManage extends DefaultDBManage implements DBManage { @Override - public void connectDatabase(String database) { + public void connectDatabase(Connection connection, String database) { try { - SQLExecutor.getInstance().execute("SELECT pg_database_size('"+database+"');"); + SQLExecutor.getInstance().execute(connection,"SELECT pg_database_size('"+database+"');"); } catch (SQLException e) { throw new RuntimeException(e); } } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } } 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 e50df862c..bc30988fe 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 @@ -1,11 +1,12 @@ package ai.chat2db.plugin.postgresql; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.sql.SQLExecutor; -import java.sql.SQLException; - public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { private String functionSQL = " CREATE OR REPLACE FUNCTION showcreatetable(namespace character varying, tablename character " @@ -163,10 +164,10 @@ public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { + " $BODY$ LANGUAGE plpgsql"; @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { - SQLExecutor.getInstance().executeSql(functionSQL.replaceFirst("tableSchema", schemaName), resultSet -> null); + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { + SQLExecutor.getInstance().executeSql(connection,functionSQL.replaceFirst("tableSchema", schemaName), resultSet -> null); String ddlSql = "select showcreatetable('" + schemaName + "','" + tableName + "') as sql"; - return SQLExecutor.getInstance().executeSql(ddlSql, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,ddlSql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); diff --git a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoDBManage.java b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoDBManage.java index c6068aabd..57c08f316 100644 --- a/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-presto/src/main/java/ai/chat2db/plugin/presto/PrestoDBManage.java @@ -1,45 +1,7 @@ package ai.chat2db.plugin.presto; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; -public class PrestoDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } +public class PrestoDBManage extends DefaultDBManage implements DBManage { } diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java index f908b9997..b9761c5d4 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisDBManage.java @@ -1,45 +1,8 @@ package ai.chat2db.plugin.redis; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; -public class RedisDBManage implements DBManage { - @Override - public void connectDatabase(String database) { +public class RedisDBManage extends DefaultDBManage implements DBManage { - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } } diff --git a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java index 95a2b35f7..aaf726019 100644 --- a/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-redis/src/main/java/ai/chat2db/plugin/redis/RedisMetaData.java @@ -1,28 +1,23 @@ package ai.chat2db.plugin.redis; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.sql.SQLExecutor; -import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; - public class RedisMetaData extends DefaultMetaService implements MetaData { - @Override - public String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { - return ""; - } - @Override - public List databases() { + public List databases(Connection connection) { List databases = new ArrayList<>(); - return SQLExecutor.getInstance().executeSql("config get databases", resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,"config get databases", resultSet -> { try { if (resultSet.next()) { Object count = resultSet.getObject(2); @@ -41,8 +36,8 @@ public List databases() { } @Override - public List
tables(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().executeSql("scan 0 MATCH * COUNT 1000", resultSet -> { + public List
tables(Connection connection, String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().executeSql(connection,"scan 0 MATCH * COUNT 1000", resultSet -> { List
tables = new ArrayList<>(); try { while (resultSet.next()) { diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteDBManage.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteDBManage.java index cc6d276a8..6130d7a65 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/SqliteDBManage.java @@ -1,45 +1,7 @@ package ai.chat2db.plugin.sqlite; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; -public class SqliteDBManage implements DBManage { - @Override - public void connectDatabase(String database) { - - } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } +public class SqliteDBManage extends DefaultDBManage implements DBManage { } 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 29201c160..872e63fa2 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 @@ -1,5 +1,9 @@ package ai.chat2db.plugin.sqlite; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.List; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; @@ -7,14 +11,11 @@ import ai.chat2db.spi.sql.SQLExecutor; import com.google.common.collect.Lists; -import java.sql.SQLException; -import java.util.List; - public class SqliteMetaData extends DefaultMetaService implements MetaData { @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "SELECT sql FROM sqlite_master WHERE type='table' AND name='" + tableName + "'"; - return SQLExecutor.getInstance().executeSql(sql, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); @@ -26,12 +27,12 @@ public String tableDDL(String databaseName, String schemaName, String tableName) }); } @Override - public List databases() { + public List databases(Connection connection) { return Lists.newArrayList(Database.builder().name("main").build()); } @Override - public List schemas(String databaseName) { + public List schemas(Connection connection,String databaseName) { return Lists.newArrayList(); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java index ae17b7927..2b1d73256 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java @@ -1,52 +1,19 @@ package ai.chat2db.plugin.sqlserver; +import java.sql.Connection; import java.sql.SQLException; import ai.chat2db.spi.DBManage; +import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.SQLExecutor; -public class SqlServerDBManage implements DBManage { +public class SqlServerDBManage extends DefaultDBManage implements DBManage { @Override - public void connectDatabase(String database) { + public void connectDatabase(Connection connection, String database) { try { - SQLExecutor.getInstance().execute("use [" + database + "];"); + SQLExecutor.getInstance().execute(connection,"use [" + database + "];"); } catch (SQLException e) { throw new RuntimeException(e); } } - - @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { - - } - - @Override - public void createDatabase(String databaseName) { - - } - - @Override - public void dropDatabase(String databaseName) { - - } - - @Override - public void createSchema(String databaseName, String schemaName) { - - } - - @Override - public void dropSchema(String databaseName, String schemaName) { - - } - - @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { - - } - - @Override - public void dropTable(String databaseName, String schemaName, String tableName) { - - } } 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 6c9dd952e..4fe9defb0 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 @@ -1,11 +1,12 @@ package ai.chat2db.plugin.sqlserver; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.sql.SQLExecutor; -import java.sql.SQLException; - public class SqlServerMetaData extends DefaultMetaService implements MetaData { private String functionSQL = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" @@ -37,17 +38,17 @@ public class SqlServerMetaData extends DefaultMetaService implements MetaData { @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { System.out.println(functionSQL); - SQLExecutor.getInstance().executeSql(functionSQL.replace("tableSchema", schemaName), resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,functionSQL.replace("tableSchema", schemaName), resultSet -> null); } catch (Exception e) { //log.error("创建函数失败", e); } String ddlSql = "SELECT " + schemaName + ".ufn_GetCreateTableScript('" + schemaName + "', '" + tableName + "') AS sql"; - return SQLExecutor.getInstance().executeSql(ddlSql, resultSet -> { + return SQLExecutor.getInstance().executeSql(connection,ddlSql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java index 161319429..e1c8b62a5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ConsoleServiceImpl.java @@ -18,7 +18,7 @@ public class ConsoleServiceImpl implements ConsoleService { @Override public ActionResult createConsole(ConsoleConnectParam param) { - Chat2DBContext.getDBManage().connectDatabase(param.getDatabaseName()); + Chat2DBContext.getDBManage().connectDatabase(Chat2DBContext.getConnection(),param.getDatabaseName()); return ActionResult.isSuccess(); } 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 159e8ab63..56f0ac35d 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 @@ -16,16 +16,14 @@ import ai.chat2db.server.domain.core.converter.DataSourceConverter; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.DataSourceConnect; -import ai.chat2db.spi.model.Database; -import ai.chat2db.spi.model.KeyValue; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; - +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.DataSourceConnect; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; @@ -137,7 +135,7 @@ public ActionResult preConnect(DataSourcePreConnectParam param) { public ListResult connect(Long id) { DatabaseQueryAllParam queryAllParam = new DatabaseQueryAllParam(); queryAllParam.setDataSourceId(id); - List databases = Chat2DBContext.getMetaData().databases(); + List databases = Chat2DBContext.getMetaData().databases(Chat2DBContext.getConnection()); return ListResult.of(databases); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 79ca9552b..6d29cbcbb 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.core.impl; +import java.sql.Connection; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -18,7 +19,6 @@ import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.sql.ConnectInfo; import cn.hutool.core.thread.ThreadUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -37,41 +37,44 @@ public class DatabaseServiceImpl implements DatabaseService { @Override public ListResult queryAll(DatabaseQueryAllParam param) { - return ListResult.of(Chat2DBContext.getMetaData().databases()); + return ListResult.of(Chat2DBContext.getMetaData().databases(Chat2DBContext.getConnection())); } @Override public ListResult querySchema(SchemaQueryParam param) { - return ListResult.of(Chat2DBContext.getMetaData().schemas(param.getDataBaseName())); + return ListResult.of( + Chat2DBContext.getMetaData().schemas(Chat2DBContext.getConnection(), param.getDataBaseName())); } @Override public DataResult queryDatabaseSchema(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); MetaData metaData = Chat2DBContext.getMetaData(); - ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + Connection connection = Chat2DBContext.getConnection(); MetaSchema ms = CacheManage.get(getDataSourceKey(param.getDataSourceId()), MetaSchema.class, (key) -> param.isRefresh(), (key) -> { - List databases = metaData.databases(); + List databases = metaData.databases(Chat2DBContext.getConnection()); if (!CollectionUtils.isEmpty(databases)) { - // CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); + CountDownLatch countDownLatch = ThreadUtil.newCountDownLatch(databases.size()); for (Database database : databases) { - //ThreadUtil.execute(() -> { - try { - // Chat2DBContext.putContext(connectInfo); - database.setSchemas(metaData.schemas(database.getName())); - // countDownLatch.countDown(); - } catch (Exception e) { - log.error("queryDatabaseSchema error", e); - } finally { - //Chat2DBContext.removeContext(); - } - // }); + ThreadUtil.execute(() -> { + try { + database.setSchemas(metaData.schemas(connection, database.getName())); + countDownLatch.countDown(); + } catch (Exception e) { + log.error("queryDatabaseSchema error", e); + } + }); + } + try { + countDownLatch.await(); + } catch (InterruptedException e) { + log.error("queryDatabaseSchema error", e); } metaSchema.setDatabases(databases); } else { - List schemas = metaData.schemas(null); + List schemas = metaData.schemas(Chat2DBContext.getConnection(), null); metaSchema.setSchemas(schemas); } return metaSchema; @@ -82,39 +85,39 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { @Override public ActionResult deleteDatabase(DatabaseOperationParam param) { - Chat2DBContext.getDBManage().dropDatabase(param.getDatabaseName()); + Chat2DBContext.getDBManage().dropDatabase(Chat2DBContext.getConnection(),param.getDatabaseName()); return ActionResult.isSuccess(); } @Override public ActionResult createDatabase(DatabaseOperationParam param) { - Chat2DBContext.getDBManage().createDatabase(param.getDatabaseName()); + Chat2DBContext.getDBManage().createDatabase(Chat2DBContext.getConnection(),param.getDatabaseName()); return ActionResult.isSuccess(); } @Override public ActionResult modifyDatabase(DatabaseOperationParam param) { - Chat2DBContext.getDBManage().modifyDatabase(param.getDatabaseName(), param.getNewDatabaseName()); + Chat2DBContext.getDBManage().modifyDatabase(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getNewDatabaseName()); return ActionResult.isSuccess(); } @Override public ActionResult deleteSchema(SchemaOperationParam param) { - Chat2DBContext.getDBManage().dropSchema(param.getDatabaseName(), param.getSchemaName()); + Chat2DBContext.getDBManage().dropSchema(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName()); return ActionResult.isSuccess(); } @Override public ActionResult createSchema(SchemaOperationParam param) { - Chat2DBContext.getDBManage().createSchema(param.getDatabaseName(), param.getSchemaName()); + Chat2DBContext.getDBManage().createSchema(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName()); return ActionResult.isSuccess(); } @Override public ActionResult modifySchema(SchemaOperationParam param) { - Chat2DBContext.getDBManage().modifySchema(param.getDatabaseName(), param.getSchemaName(), + Chat2DBContext.getDBManage().modifySchema(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getNewSchemaName()); return ActionResult.isSuccess(); } -} +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 7ec75babd..8d4388b9d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -155,7 +155,7 @@ public DataResult count(DlCountParam param) { private ExecuteResult execute(String sql) { ExecuteResult executeResult; try { - executeResult = SQLExecutor.getInstance().execute(sql); + executeResult = SQLExecutor.getInstance().execute(Chat2DBContext.getConnection(),sql); } catch (SQLException e) { log.warn("执行sql:{}异常", sql, e); executeResult = ExecuteResult.builder() diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java index 2844055bb..58f961a1e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java @@ -10,6 +10,6 @@ public class FunctionServiceImpl implements FunctionService { @Override public ListResult functions(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().functions(databaseName, schemaName)); + return ListResult.of(Chat2DBContext.getMetaData().functions(Chat2DBContext.getConnection(),databaseName, schemaName)); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java index 8b17720c8..34f2bb015 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java @@ -11,6 +11,6 @@ public class ProcedureServiceImpl implements ProcedureService { @Override public ListResult procedures(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().procedures(databaseName, schemaName)); + return ListResult.of(Chat2DBContext.getMetaData().procedures(Chat2DBContext.getConnection(),databaseName, schemaName)); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 8d018d6a4..6c80c2a60 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -53,14 +53,14 @@ public class TableServiceImpl implements TableService { @Override public DataResult showCreateTable(ShowCreateTableParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - String ddl = metaSchema.tableDDL(param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + String ddl = metaSchema.tableDDL(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); return DataResult.of(ddl); } @Override public ActionResult drop(DropParam param) { DBManage metaSchema = Chat2DBContext.getDBManage(); - metaSchema.dropTable(param.getDatabaseName(), param.getTableSchema(), param.getTableName()); + metaSchema.dropTable(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getTableSchema(), param.getTableName()); return ActionResult.isSuccess(); } @@ -79,13 +79,13 @@ public DataResult alterTableExample(String dbType) { @Override public DataResult
query(TableQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); - List
tables = metaSchema.tables(param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + List
tables = metaSchema.tables(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName()); if (!CollectionUtils.isEmpty(tables)) { Table table = tables.get(0); table.setIndexList( - metaSchema.indexes(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.indexes(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName())); table.setColumnList( - metaSchema.columns(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.columns(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName())); return DataResult.of(table); } return DataResult.of(null); @@ -104,7 +104,7 @@ public PageResult
pageQuery(TablePageQueryParam param, TableSelector sele List
list = CacheManage.getList(tableKey, Table.class, (key) -> param.isRefresh(), (key) -> - metaSchema.tables(param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.tables(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName())); list = pinTable(list, param); if (CollectionUtils.isEmpty(list)) { @@ -144,13 +144,13 @@ private List
pinTable(List
list, TablePageQueryParam param) { @Override public List queryColumns(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - return metaSchema.columns(param.getDatabaseName(), param.getSchemaName(), param.getTableName(), null); + return metaSchema.columns(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName(), null); } @Override public List queryIndexes(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - return metaSchema.indexes(param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + return metaSchema.indexes(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName()); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java index d72174b70..90b167ff6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java @@ -10,6 +10,6 @@ public class TriggerServiceImpl implements TriggerService { @Override public ListResult triggers(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().triggers(databaseName, schemaName)); + return ListResult.of(Chat2DBContext.getMetaData().triggers(Chat2DBContext.getConnection(),databaseName, schemaName)); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java index 150791fbb..b9145046e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java @@ -11,6 +11,6 @@ public class ViewServiceImpl implements ViewService { @Override public ListResult
views(String databaseName, String schemaName) { - return ListResult.of(Chat2DBContext.getMetaData().views(databaseName, schemaName)); + return ListResult.of(Chat2DBContext.getMetaData().views(Chat2DBContext.getConnection(),databaseName, schemaName)); } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java index 5291fb659..67def3b8f 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java @@ -1,6 +1,7 @@ - package ai.chat2db.spi; +import java.sql.Connection; + import jakarta.validation.constraints.NotEmpty; /** @@ -12,28 +13,28 @@ public interface DBManage { /** * @param database */ - void connectDatabase(String database); + void connectDatabase(Connection connection,String database); /** * 修改数据库名称 * @param databaseName * @param newDatabaseName */ - void modifyDatabase(String databaseName, String newDatabaseName); + void modifyDatabase(Connection connection,String databaseName, String newDatabaseName); /** * 创建数据库 * @param databaseName */ - void createDatabase(String databaseName); + void createDatabase(Connection connection,String databaseName); /** * 删除数据库 * @param databaseName */ - void dropDatabase(String databaseName); + void dropDatabase(Connection connection,String databaseName); @@ -42,14 +43,14 @@ public interface DBManage { * @param databaseName * @param schemaName */ - void createSchema(String databaseName, String schemaName); + void createSchema(Connection connection,String databaseName, String schemaName); /** * 删除schema * @param databaseName * @param schemaName */ - void dropSchema(String databaseName, String schemaName); + void dropSchema(Connection connection,String databaseName, String schemaName); /** * 修改schema @@ -57,7 +58,7 @@ public interface DBManage { * @param schemaName * @param newSchemaName */ - void modifySchema(String databaseName, String schemaName, String newSchemaName); + void modifySchema(Connection connection,String databaseName, String schemaName, String newSchemaName); /** @@ -67,5 +68,5 @@ public interface DBManage { * @param tableName * @return */ - void dropTable(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); + void dropTable(Connection connection,@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 512a21168..6f2967625 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -1,6 +1,6 @@ - package ai.chat2db.spi; +import java.sql.Connection; import java.util.List; import ai.chat2db.spi.model.*; @@ -15,92 +15,117 @@ public interface MetaData { /** * Query all databases. + * @param connection * * @return */ - List databases(); + List databases(Connection connection); /** * Querying all schemas under a database * + * @param connection * @param databaseName * @return */ - List schemas(String databaseName); + List schemas(Connection connection, String databaseName); /** * Querying DDL information * + * @param connection * @param databaseName * @param tableName * @return */ - String tableDDL(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); + String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String tableName); /** * Querying all table under a schema. * + * @param connection * @param databaseName + * @param schemaName + * @param tableName * @return */ - List
tables(@NotEmpty String databaseName, String schemaName, String tableName); + List
tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName); /** * Querying all views under a schema. * + * @param connection * @param databaseName + * @param schemaName * @return */ - List
views(@NotEmpty String databaseName, String schemaName); + List
views(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all functions under a schema. * + * @param connection * @param databaseName + * @param schemaName * @return */ - List functions(@NotEmpty String databaseName, String schemaName); + List functions(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all triggers under a schema. * + * @param connection * @param databaseName + * @param schemaName * @return */ - List triggers(@NotEmpty String databaseName, String schemaName); + List triggers(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all procedures under a schema. * + * @param connection + * @param schemaName * @param databaseName * @return */ - List procedures(@NotEmpty String databaseName, String schemaName); + List procedures(Connection connection, @NotEmpty String databaseName, String schemaName); /** * Querying all columns under a table. * + * @param connection * @param databaseName + * @param schemaName + * @param tableName * @return */ - List columns(@NotEmpty String databaseName, String schemaName, + List columns(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); /** * Querying all columns under a table. * + * @param connection * @param databaseName + * @param schemaName + * @param tableName + * @param columnName * @return */ - List columns(@NotEmpty String databaseName, String schemaName, String tableName, + List columns(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName, String columnName); /** * Querying all indexes under a table. * + * @param connection + * @param databaseName * @param databaseName * @return */ - List indexes(@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); + List indexes(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String tableName); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java index e77fcb762..612ecac1c 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java @@ -1,7 +1,6 @@ - package ai.chat2db.spi.jdbc; -import java.sql.SQLException; +import java.sql.Connection; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.sql.SQLExecutor; @@ -13,43 +12,43 @@ public class DefaultDBManage implements DBManage { @Override - public void connectDatabase(String database) { + public void connectDatabase(Connection connection,String database) { } @Override - public void modifyDatabase(String databaseName, String newDatabaseName) { + public void modifyDatabase(Connection connection,String databaseName, String newDatabaseName) { } @Override - public void createDatabase(String databaseName) { + public void createDatabase(Connection connection,String databaseName) { } @Override - public void dropDatabase(String databaseName) { + public void dropDatabase(Connection connection,String databaseName) { } @Override - public void createSchema(String databaseName, String schemaName) { + public void createSchema(Connection connection,String databaseName, String schemaName) { } @Override - public void dropSchema(String databaseName, String schemaName) { + public void dropSchema(Connection connection,String databaseName, String schemaName) { } @Override - public void modifySchema(String databaseName, String schemaName, String newSchemaName) { + public void modifySchema(Connection connection,String databaseName, String schemaName, String newSchemaName) { } @Override - public void dropTable(String databaseName, String schemaName, String tableName) { + public void dropTable(Connection connection,String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE "+ tableName ; - SQLExecutor.getInstance().executeSql(sql, resultSet -> null); + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index cb7729d83..4999c88fe 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -1,6 +1,6 @@ - package ai.chat2db.spi.jdbc; +import java.sql.Connection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -23,15 +23,15 @@ */ public class DefaultMetaService implements MetaData { @Override - public List databases() { - List dataBases = SQLExecutor.getInstance().databases(); + public List databases(Connection connection) { + List dataBases = SQLExecutor.getInstance().databases(connection); return dataBases.stream().map(str -> Database.builder().name(str).build()).collect(Collectors.toList()); } @Override - public List schemas(String databaseName) { - List> maps = SQLExecutor.getInstance().schemas(databaseName, null); + public List schemas(Connection connection,String databaseName) { + List> maps = SQLExecutor.getInstance().schemas(connection, databaseName, null); return maps.stream().map(map -> map2Schema(map)).collect(Collectors.toList()); } @@ -46,48 +46,48 @@ private Schema map2Schema(Map map) { } @Override - public String tableDDL(String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection,String databaseName, String schemaName, String tableName) { return null; } @Override - public List
tables(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(databaseName, schemaName, tableName, new String[]{"TABLE"}); + public List
tables(Connection connection,String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().tables(connection,databaseName, schemaName, tableName, new String[]{"TABLE"}); } @Override - public List
views(String databaseName, String schemaName) { - return SQLExecutor.getInstance().tables(databaseName, schemaName, null, new String[]{"VIEW"}); + public List
views(Connection connection,String databaseName, String schemaName) { + return SQLExecutor.getInstance().tables(connection,databaseName, schemaName, null, new String[]{"VIEW"}); } @Override - public List functions(String databaseName, String schemaName) { - return SQLExecutor.getInstance().functions(databaseName, schemaName); + public List functions(Connection connection,String databaseName, String schemaName) { + return SQLExecutor.getInstance().functions(connection,databaseName, schemaName); } @Override - public List triggers(String databaseName, String schemaName) { + public List triggers(Connection connection,String databaseName, String schemaName) { return null; } @Override - public List procedures(String databaseName, String schemaName) { - return SQLExecutor.getInstance().procedures(databaseName, schemaName); + public List procedures(Connection connection,String databaseName, String schemaName) { + return SQLExecutor.getInstance().procedures(connection,databaseName, schemaName); } @Override - public List columns(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().columns(databaseName, schemaName, tableName, null); + public List columns(Connection connection,String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().columns(connection,databaseName, schemaName, tableName, null); } @Override - public List columns(String databaseName, String schemaName, String tableName, - String columnName) { - return SQLExecutor.getInstance().columns(databaseName, schemaName, tableName, columnName); + public List columns(Connection connection,String databaseName, String schemaName, String tableName, + String columnName) { + return SQLExecutor.getInstance().columns(connection,databaseName, schemaName, tableName, columnName); } @Override - public List indexes(String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().indexes(databaseName, schemaName, tableName); + public List indexes(Connection connection,String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().indexes(connection,databaseName, schemaName, tableName); } } \ No newline at end of file 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 660633f0a..fe31dfdd7 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 @@ -138,7 +138,7 @@ private static Connection setConnectInfoThreadLocal(ConnectInfo connectInfo) { connectInfo.setSession(session); connectInfo.setConnection(connection); if (StringUtils.isNotBlank(connectInfo.getDatabaseName())) { - PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBManage().connectDatabase( + PLUGIN_MAP.get(getConnectInfo().getDbType()).getDBManage().connectDatabase(connection, connectInfo.getDatabaseName()); } return connection; @@ -181,4 +181,4 @@ public static void removeContext() { } } -} \ No newline at end of file +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index bbea8af8b..05ec209fc 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -48,27 +48,27 @@ public static SQLExecutor getInstance() { return INSTANCE; } - public Connection getConnection() throws SQLException { - return Chat2DBContext.getConnection(); - } + //public Connection connection throws SQLException { + // return Chat2DBContext.connection; + //} public void close() { } /** * 执行sql - * + * @param connection * @param sql * @param function * @return */ - public R executeSql(String sql, Function function) { + public R executeSql(Connection connection,String sql, Function function) { if (StringUtils.isEmpty(sql)) { return null; } log.info("execute:{}", sql); - try (Statement stmt = getConnection().createStatement();) { + try (Statement stmt = connection.createStatement();) { boolean query = stmt.execute(sql); // 代表是查询 if (query) { @@ -148,22 +148,23 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL /** * 执行sql * + * @param connection * @param sql * @return * @throws SQLException */ - public ExecuteResult execute(String sql) throws SQLException { - return execute(sql, getConnection()); + public ExecuteResult execute(Connection connection,String sql) throws SQLException { + return execute(sql, connection); } /** * 获取所有的数据库 - * + * @param connection * @return */ - public List databases() { + public List databases(Connection connection) { List tables = Lists.newArrayList(); - try (ResultSet resultSet = getConnection().getMetaData().getCatalogs();) { + try (ResultSet resultSet = connection.getMetaData().getCatalogs();) { if (resultSet != null) { while (resultSet.next()) { tables.add(resultSet.getString("TABLE_CAT")); @@ -177,15 +178,15 @@ public List databases() { /** * 获取所有的schema - * + * @param connection * @param databaseName * @param schemaName * @return */ - public List> schemas(String databaseName, String schemaName) { + public List> schemas(Connection connection,String databaseName, String schemaName) { List> schemaList = Lists.newArrayList(); if (StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)) { - try (ResultSet resultSet = getConnection().getMetaData().getSchemas()) { + try (ResultSet resultSet = connection.getMetaData().getSchemas()) { if (resultSet != null) { while (resultSet.next()) { Map map = new HashMap<>(); @@ -199,7 +200,7 @@ public List> schemas(String databaseName, String schemaName) } return schemaList; } - try (ResultSet resultSet = getConnection().getMetaData().getSchemas(databaseName, schemaName)) { + try (ResultSet resultSet = connection.getMetaData().getSchemas(databaseName, schemaName)) { if (resultSet != null) { while (resultSet.next()) { Map map = new HashMap<>(); @@ -216,17 +217,17 @@ public List> schemas(String databaseName, String schemaName) /** * 获取所有的数据库表 - * + * @param connection * @param databaseName * @param schemaName * @param tableName * @param types * @return */ - public List
tables(String databaseName, String schemaName, String tableName, String types[]) { + public List
tables(Connection connection,String databaseName, String schemaName, String tableName, String types[]) { List
tables = Lists.newArrayList(); int n = 0; - try (ResultSet resultSet = getConnection().getMetaData().getTables(databaseName, schemaName, tableName, + try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, types)) { if (resultSet != null) { while (resultSet.next()) { @@ -245,16 +246,16 @@ public List
tables(String databaseName, String schemaName, String tableNa /** * 获取所有的数据库表列 - * + * @param connection * @param databaseName * @param schemaName * @param tableName * @param columnName * @return */ - public List columns(String databaseName, String schemaName, String tableName, String columnName) { + public List columns(Connection connection,String databaseName, String schemaName, String tableName, String columnName) { List tableColumns = Lists.newArrayList(); - try (ResultSet resultSet = getConnection().getMetaData().getColumns(databaseName, schemaName, tableName, + try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, columnName)) { if (resultSet != null) { while (resultSet.next()) { @@ -269,15 +270,15 @@ public List columns(String databaseName, String schemaName, String /** * 获取所有的数据库表索引 - * + * @param connection * @param databaseName * @param schemaName * @param tableName * @return */ - public List indexes(String databaseName, String schemaName, String tableName) { + public List indexes(Connection connection,String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); - try (ResultSet resultSet = getConnection().getMetaData().getIndexInfo(databaseName, schemaName, tableName, + try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, false)) { List tableIndexColumns = Lists.newArrayList(); @@ -307,15 +308,15 @@ public List indexes(String databaseName, String schemaName, String t /** * 获取所有的函数 - * + * @param connection * @param databaseName * @param schemaName * @return */ - public List functions(String databaseName, + public List functions(Connection connection,String databaseName, String schemaName) { List functions = Lists.newArrayList(); - try (ResultSet resultSet = getConnection().getMetaData().getFunctions(databaseName, schemaName, null);) { + try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { while (resultSet != null && resultSet.next()) { functions.add(buildFunction(resultSet)); } @@ -327,14 +328,14 @@ public List functions(String databaseName, /** * 获取所有的存储过程 - * + * @param connection * @param databaseName * @param schemaName * @return */ - public List procedures(String databaseName, String schemaName) { + public List procedures(Connection connection,String databaseName, String schemaName) { List procedures = Lists.newArrayList(); - try (ResultSet resultSet = getConnection().getMetaData().getProcedures(databaseName, schemaName, null)) { + try (ResultSet resultSet = connection.getMetaData().getProcedures(databaseName, schemaName, null)) { while (resultSet != null && resultSet.next()) { procedures.add(buildProcedure(resultSet)); } From 210c287a1c7155e4f8bc64e4043b8cfec6604de1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 17 Jul 2023 22:56:27 +0800 Subject: [PATCH 0349/1069] =?UTF-8?q?feat:=20=E5=88=87=E6=8D=A2=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Iconfont/index.less | 10 +- chat2db-client/src/i18n/en-us/setting.ts | 2 +- chat2db-client/src/i18n/zh-cn/setting.ts | 2 +- .../src/pages/main/connection/index.less | 2 +- .../src/pages/main/connection/index.tsx | 63 +++---- .../src/pages/main/dashboard/index.less | 2 +- chat2db-client/src/pages/main/index.less | 1 + .../components/WorkspaceHeader/index.less | 29 ++++ .../components/WorkspaceHeader/index.tsx | 134 +++++++++++++++ .../components/WorkspaceLeft/index.tsx | 157 +++++++++--------- .../src/pages/main/workspace/index.less | 11 +- .../src/pages/main/workspace/index.tsx | 34 ++-- 12 files changed, 311 insertions(+), 136 deletions(-) create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 0d539e8d1..f38b078ab 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,9 +1,9 @@ +/* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { - font-family: 'iconfont'; - /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), - url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_nrs2mjb99ok.woff2?t=1689421827817') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_nrs2mjb99ok.woff?t=1689421827817') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_nrs2mjb99ok.ttf?t=1689421827817') format('truetype'); } .iconfont { diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index e81618b68..96ae965d1 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -2,7 +2,7 @@ export default { 'setting.title.setting': 'Setting', 'setting.nav.basic': 'Basic', 'setting.nav.customAi': 'Custom Ai', - 'setting.nav.proxy': 'Proxy', + 'setting.nav.proxy': 'Service Path', 'setting.nav.aboutUs': 'About Us', 'setting.title.backgroundColor': 'Background Color', 'setting.title.themeColor': 'Theme Color', diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 4bbe225de..fece84929 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -2,7 +2,7 @@ export default { 'setting.title.setting': '设置', 'setting.nav.basic': '基础设置', 'setting.nav.customAi': '自定义AI', - 'setting.nav.proxy': '代理设置', + 'setting.nav.proxy': '服务端地址', 'setting.nav.aboutUs': '关于我们', 'setting.title.backgroundColor': '背景色', 'setting.title.themeColor': '主题色', diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 8c8c84db2..ef9902d19 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -14,7 +14,7 @@ width: 220px; overflow: hidden; background-color: var(--color-bg-elevated); - border: 1px solid var(--color-border-secondary); + border-right: 1px solid var(--color-border-secondary); border-top: 0px; border-bottom: 0px; } diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 437f4e8bf..5449f342d 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -37,7 +37,7 @@ function Connections(props: IProps) { getConnectionList(); }, []); - const getConnectionList = async () => { + const getConnectionList = () => { dispatch({ type: 'connection/fetchConnectionList', }); @@ -133,15 +133,18 @@ function Connections(props: IProps) {
{i18n('connection.title.connections')}
{renderMenu()} - + { + curConnection && Object.keys(curConnection).length && + + }
{curConnection && Object.keys(curConnection).length ? ( @@ -159,29 +162,29 @@ function Connections(props: IProps) { />
) : ( -
- {databaseTypeList.map((t) => { - return ( -
-
-
-
- -
- {t.name} -
-
- +
+ {databaseTypeList.map((t) => { + return ( +
+
+
+
+
+ {t.name} +
+
+
- ); - })} - {Array.from({ length: 20 }).map((t, index) => { - return
; - })} -
- )} +
+ ); + })} + {Array.from({ length: 20 }).map((t, index) => { + return
; + })} +
+ )}
); diff --git a/chat2db-client/src/pages/main/dashboard/index.less b/chat2db-client/src/pages/main/dashboard/index.less index 4b956cbec..4f03ab657 100644 --- a/chat2db-client/src/pages/main/dashboard/index.less +++ b/chat2db-client/src/pages/main/dashboard/index.less @@ -10,7 +10,7 @@ width: 220px; height: 100%; overflow: hidden; - border: 1px solid var(--color-border-secondary); + border-right: 1px solid var(--color-border-secondary); border-top: 0px; border-bottom: 0px; } diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 24286c770..a5a342c72 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -16,6 +16,7 @@ align-items: center; width: 68px; background-color: var(--color-bg-elevated); + border-right: 1px solid var(--color-border-secondary); user-select: none; overflow: hidden; } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less new file mode 100644 index 000000000..5872b205f --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -0,0 +1,29 @@ +@import '../../../../../styles/var.less'; + +.workspaceHeader{ + flex-shrink: 0; + display: flex; + align-items: center; + padding-left: 10px; + background-color: var(--color-bg-elevated); + border-bottom: 1px solid var(--color-border-secondary); +} + +.crumbsItem{ + display: flex; + align-items: center; + height: 28px; + cursor: pointer; + &:hover{ + color: var(--color-primary); + } + + .typeIcon{ + margin-right: 6px; + } + + .arrow{ + margin: 0px 10px; + } +} + diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx new file mode 100644 index 000000000..2763d1613 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -0,0 +1,134 @@ +import React, { memo, useEffect, useMemo, useState } from 'react'; +import { connect } from 'umi'; +import { IConnectionModelType } from '@/models/connection'; +import { IWorkspaceModelType } from '@/models/workspace'; +import styles from './index.less'; +import classnames from 'classnames'; +import { Cascader } from 'antd'; +import Iconfont from '@/components/Iconfont'; +import { treeConfig } from '../Tree/treeConfig'; +import { TreeNodeType } from '@/constants'; +import { useSafeState } from 'ahooks'; + + +interface IProps { + className?: string; + cascaderOptions: any; + connectionModel: IConnectionModelType['state']; + workspaceModel: IWorkspaceModelType['state']; + dispatch: any; +} + +const WorkspaceHeader = memo((props) => { + const { className, cascaderOptions, connectionModel, workspaceModel, dispatch } = props; + const { connectionList, curConnection } = connectionModel; + const { curWorkspaceParams } = workspaceModel; + const [curSchemaOptions, setCurSchemaOptions] = useState([]); + + const connectionListOptions = useMemo(() => { + return connectionList?.map(t => { + return { + value: t.id, + label: t.alias + } + }) + }, [connectionList]) + + useEffect(() => { + getConnectionList(); + }, []); + + const getConnectionList = () => { + dispatch({ + type: 'connection/fetchConnectionList', + }); + }; + + function connectionChange(id: any, data: any) { + connectionList.map(t => { + if (t.id === id[0]) { + dispatch({ + type: 'connection/setCurConnection', + payload: t + }); + } + }) + } + + const databaseChange: any = (valueArr: any, selectedOptions: any) => { + + const curWorkspaceParams = { + dataSourceId: curConnection?.id, + databaseSourceName: curConnection?.alias, + databaseName: selectedOptions[0].value, + schemaName: selectedOptions?.[0]?.next?.[0]?.value, + databaseType: curConnection?.type, + }; + + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: curWorkspaceParams, + }); + + setCurSchemaOptions(selectedOptions[0].next) + }; + + const schemaChange: any = (valueArr: any, selectedOptions: any) => { + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: { ...curWorkspaceParams, schemaName: selectedOptions[0].value }, + }); + } + + return
+ +
+ +
{curConnection?.alias}
+ +
+
+ + +
+ +
{curWorkspaceParams.databaseName}
+ { + !!curSchemaOptions.length && + } +
+
+ { + !!curSchemaOptions.length && + +
+ +
{}
+
+
+ } + +
+}) + +export default connect( + ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ + connectionModel: connection, + workspaceModel: workspace, + }), +)(WorkspaceHeader); 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 666d4d8ad..3f8ad1e8d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -81,9 +81,9 @@ const WorkspaceLeft = memo(function (props) { return (
-
+ {/*
-
+
*/} @@ -109,82 +109,81 @@ interface IProps { dispatch: any; } -const RenderSelectDatabase = dvaModel(function (props: IProps) { - const { connectionModel, workspaceModel, dispatch, cascaderOptions } = props; - const { curWorkspaceParams } = workspaceModel; - const { curConnection } = connectionModel; - const [currentSelectedName, setCurrentSelectedName] = useState(''); - const [cascaderLoading, setCascaderLoading] = useState(false) - - - useEffect(() => { - if (curWorkspaceParams) { - const { databaseName, schemaName, databaseSourceName } = curWorkspaceParams; - const currentSelectedArr = [databaseSourceName, databaseName, schemaName].filter((t) => t); - setCurrentSelectedName(currentSelectedArr.join('/')); - } - }, [curWorkspaceParams]); - - const onChange: any = (valueArr: any, selectedOptions: any) => { - let labelArr: string[] = []; - labelArr = selectedOptions.map((t: any) => { - return t.label; - }); - - const curWorkspaceParams = { - dataSourceId: curConnection?.id, - databaseSourceName: curConnection?.alias, - databaseName: labelArr[0], - schemaName: labelArr[1], - databaseType: curConnection?.type, - }; - - dispatch({ - type: 'workspace/setCurWorkspaceParams', - payload: curWorkspaceParams, - }); - }; - - const dropdownRender = (menus: React.ReactNode) =>
{menus}
; - - function handleRefresh() { - setCascaderLoading(true) - dispatch({ - type: 'workspace/fetchDatabaseAndSchema', - payload: { - dataSourceId: curConnection?.id, - refresh: true - }, - callback: () => { - setCascaderLoading(false) - } - }); - } - - return ( -
- -
-
- {currentSelectedName || {i18n('workspace.cascader.placeholder')}}{' '} -
- -
-
-
-
- {cascaderLoading ? : } -
-
-
- ); -}); +// const RenderSelectDatabase = dvaModel(function (props: IProps) { +// const { connectionModel, workspaceModel, dispatch, cascaderOptions } = props; +// const { curWorkspaceParams } = workspaceModel; +// const { curConnection } = connectionModel; +// const [currentSelectedName, setCurrentSelectedName] = useState(''); +// const [cascaderLoading, setCascaderLoading] = useState(false); + +// useEffect(() => { +// if (curWorkspaceParams) { +// const { databaseName, schemaName, databaseSourceName } = curWorkspaceParams; +// const currentSelectedArr = [databaseSourceName, databaseName, schemaName].filter((t) => t); +// setCurrentSelectedName(currentSelectedArr.join('/')); +// } +// }, [curWorkspaceParams]); + +// const onChange: any = (valueArr: any, selectedOptions: any) => { +// let labelArr: string[] = []; +// labelArr = selectedOptions.map((t: any) => { +// return t.label; +// }); + +// const curWorkspaceParams = { +// dataSourceId: curConnection?.id, +// databaseSourceName: curConnection?.alias, +// databaseName: labelArr[0], +// schemaName: labelArr[1], +// databaseType: curConnection?.type, +// }; + +// dispatch({ +// type: 'workspace/setCurWorkspaceParams', +// payload: curWorkspaceParams, +// }); +// }; + +// const dropdownRender = (menus: React.ReactNode) =>
{menus}
; + +// function handleRefresh() { +// setCascaderLoading(true) +// dispatch({ +// type: 'workspace/fetchDatabaseAndSchema', +// payload: { +// dataSourceId: curConnection?.id, +// refresh: true +// }, +// callback: () => { +// setCascaderLoading(false) +// } +// }); +// } + +// return ( +//
+// +//
+//
+// {currentSelectedName || {i18n('workspace.cascader.placeholder')}}{' '} +//
+// +//
+//
+//
+//
+// {cascaderLoading ? : } +//
+//
+//
+// ); +// }); const RenderTableBox = dvaModel(function (props: any) { const { workspaceModel, dispatch, tableLoading } = props; @@ -375,7 +374,7 @@ const RenderSaveBox = dvaModel(function (props: any) { type: 'workspace/fetchGetSavedConsole', payload: { status: ConsoleStatus.RELEASE, - orderByDesc: false, + orderByDesc: Boolean, ...curWorkspaceParams }, callback: (res: any) => { diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index 98c6f5caa..a8df84de1 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -1,15 +1,22 @@ @import '../../../styles/var.less'; -.box { +.workspace { width: 100%; height: 100%; + display: flex; + flex-direction: column; +} + +.workspaceMain{ + flex: 1; + } .boxLeft { width: 220px; height: 100%; overflow: hidden; - border: 1px solid var(--color-border-secondary); + border-right: 1px solid var(--color-border-secondary); border-top: 0px; border-bottom: 0px; } diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index f471acb4e..603389b49 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -4,10 +4,12 @@ import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; import WorkspaceRight from './components/WorkspaceRight'; +import WorkspaceHeader from './components/WorkspaceHeader'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import LoadingContent from '@/components/Loading/LoadingContent'; import { ConsoleOpenedStatus } from '@/constants'; +import Iconfont from '@/components/Iconfont'; interface IProps { className?: string; @@ -47,7 +49,7 @@ function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType['state'] return { value: t.name, label: t.name, - children: schemasList, + next: schemasList, }; }); } else if (databaseAndSchema?.schemas) { @@ -70,15 +72,12 @@ const workspace = memo((props) => { useEffect(() => { if (pageLoading === true) { - setLoading(true) + setLoading(true); } else { - setLoading(false) + setLoading(false); } }, [pageLoading]) - console.log('pageLoading', pageLoading) - - const cascaderOptions = useMemo(() => { if (!databaseAndSchema) { return @@ -113,7 +112,6 @@ const workspace = memo((props) => { clearData(); }, [curConnection]); - useEffect(() => { if (curWorkspaceParams.dataSourceId) { getConsoleList(); @@ -162,17 +160,21 @@ const workspace = memo((props) => { }, }); } + return ( - -
- -
-
- -
-
-
+
+ + +
+ +
+
+ +
+
+
+ ); }); From b82b18c77d0b3564a8a8207608b9d2f64d6d3477 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 17 Jul 2023 23:28:46 +0800 Subject: [PATCH 0350/1069] style --- .../src/components/Iconfont/index.less | 6 ++-- chat2db-client/src/constants/database.ts | 26 +++++++-------- chat2db-client/src/models/connection.ts | 5 ++- .../components/WorkspaceHeader/index.less | 18 +++++++++++ .../components/WorkspaceHeader/index.tsx | 32 +++++++++++++------ .../src/pages/main/workspace/index.less | 3 +- .../src/pages/main/workspace/index.tsx | 10 +++--- 7 files changed, 67 insertions(+), 33 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index f38b078ab..15f70156a 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,9 +1,9 @@ /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_nrs2mjb99ok.woff2?t=1689421827817') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_nrs2mjb99ok.woff?t=1689421827817') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_nrs2mjb99ok.ttf?t=1689421827817') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.woff2?t=1689606758277') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.woff?t=1689606758277') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.ttf?t=1689606758277') format('truetype'); } .iconfont { diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index 725295980..bd3c12247 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -77,19 +77,19 @@ export const databaseMap: { icon: '\ue655', }, [DatabaseTypeCode.PRESTO]: { - name: 'Presto', - img: moreDBLogo, - code: DatabaseTypeCode.PRESTO, - port: 8080, - icon: '\ue60b', - }, - [DatabaseTypeCode.DB2]: { - name: 'DB2', - img: moreDBLogo, - code: DatabaseTypeCode.DB2, - port: 50000, - icon: '\ue60a', - }, + name: 'Presto', + img: moreDBLogo, + code: DatabaseTypeCode.PRESTO, + // port: 8080, + icon: '\ue60b', + }, + [DatabaseTypeCode.DB2]: { + name: 'DB2', + img: moreDBLogo, + code: DatabaseTypeCode.DB2, + // port: 50000, + icon: '\ue60a', + }, [DatabaseTypeCode.OCEANBASE]: { name: 'OceanBase', img: moreDBLogo, diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index 013b7503b..bafbac78b 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -48,13 +48,16 @@ const ConnectionModel: IConnectionModelType = { }, effects: { - *fetchConnectionList(_, { call, put }) { + *fetchConnectionList({ callback }, { call, put }) { try { const res = (yield connectionService.getList({ pageNo: 1, pageSize: 999 })) as IPageResponse; yield put({ type: 'setConnectionList', payload: res.data, }); + if (callback && typeof callback === 'function') { + callback(res); + } } catch { diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less index 5872b205f..85989aea0 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -9,6 +9,22 @@ border-bottom: 1px solid var(--color-border-secondary); } +.databaseLogo{ + margin-right: 10px; + overflow: hidden; + height: 18px; + width: 18px; + display: flex; + justify-content: center; + align-items: center; + .refreshBox{ + cursor: pointer; + } + .spin{ + transform: scale(0.6); + } +} + .crumbsItem{ display: flex; align-items: center; @@ -23,7 +39,9 @@ } .arrow{ + font-size: 10px; margin: 0px 10px; + transform: translateY(1px); } } 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 2763d1613..78a6f503e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -4,10 +4,9 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import styles from './index.less'; import classnames from 'classnames'; -import { Cascader } from 'antd'; +import { Cascader, Spin } from 'antd'; import Iconfont from '@/components/Iconfont'; -import { treeConfig } from '../Tree/treeConfig'; -import { TreeNodeType } from '@/constants'; +import { databaseMap } from '@/constants'; import { useSafeState } from 'ahooks'; @@ -24,7 +23,7 @@ const WorkspaceHeader = memo((props) => { const { connectionList, curConnection } = connectionModel; const { curWorkspaceParams } = workspaceModel; const [curSchemaOptions, setCurSchemaOptions] = useState([]); - + const [cascaderLoading, setCascaderLoading] = useState(false); const connectionListOptions = useMemo(() => { return connectionList?.map(t => { return { @@ -39,8 +38,14 @@ const WorkspaceHeader = memo((props) => { }, []); const getConnectionList = () => { + setCascaderLoading(true) dispatch({ type: 'connection/fetchConnectionList', + callback: () => { + setTimeout(() => { + setCascaderLoading(false) + }, 200); + } }); }; @@ -80,7 +85,19 @@ const WorkspaceHeader = memo((props) => { }); } + function handelRefresh() { + getConnectionList() + } + return
+
+ {curConnection?.type ? +
+ {cascaderLoading ? : } +
+ : + } +
((props) => { bordered={false} >
-
{curConnection?.alias}
- +
@@ -101,7 +117,6 @@ const WorkspaceHeader = memo((props) => { bordered={false} >
-
{curWorkspaceParams.databaseName}
{ !!curSchemaOptions.length && @@ -117,8 +132,7 @@ const WorkspaceHeader = memo((props) => { bordered={false} >
- -
{}
+
{curWorkspaceParams.schemaName}
} diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index a8df84de1..f723c886c 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -8,8 +8,7 @@ } .workspaceMain{ - flex: 1; - + height: 100%; } .boxLeft { diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 603389b49..43c142853 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -162,9 +162,9 @@ const workspace = memo((props) => { } return ( - -
- +
+ +
@@ -173,8 +173,8 @@ const workspace = memo((props) => {
-
- + +
); }); From 897ab601dd31b0bb86a27581541fc4527fb15011 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Tue, 18 Jul 2023 20:18:34 +0800 Subject: [PATCH 0351/1069] - Default return alias for returned results [Issue #270](https://github.com/chat2db/Chat2DB/issues/270) --- CHANGELOG.md | 8 ++ .../java/ai/chat2db/spi/sql/SQLExecutor.java | 29 ++++--- .../ai/chat2db/spi/util/ResultSetUtils.java | 75 +++++++++++-------- 3 files changed, 70 insertions(+), 42 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b1db96b3b..a25c7422a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 2.0.4 + +## 🐞 Bug Fixes +- Default return alias for returned results [Issue #270](https://github.com/chat2db/Chat2DB/issues/270) + +## 🐞 问题修复 +- 返回结果默认返回别名 [Issue #270](https://github.com/chat2db/Chat2DB/issues/270) + # 2.0.4 ## ⭐ New Features - Support DB2 database diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 05ec209fc..7dd50c3e2 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -14,6 +14,7 @@ import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.spi.model.*; +import ai.chat2db.spi.util.ResultSetUtils; import cn.hutool.core.date.TimeInterval; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; @@ -57,13 +58,14 @@ public void close() { /** * 执行sql + * * @param connection * @param sql * @param function * @return */ - public R executeSql(Connection connection,String sql, Function function) { + public R executeSql(Connection connection, String sql, Function function) { if (StringUtils.isEmpty(sql)) { return null; } @@ -115,7 +117,7 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL headerList.add(Header.builder() .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(resultSetMetaData.getColumnName(i)) + .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) .build()); } @@ -153,12 +155,13 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL * @return * @throws SQLException */ - public ExecuteResult execute(Connection connection,String sql) throws SQLException { + public ExecuteResult execute(Connection connection, String sql) throws SQLException { return execute(sql, connection); } /** * 获取所有的数据库 + * * @param connection * @return */ @@ -178,12 +181,13 @@ public List databases(Connection connection) { /** * 获取所有的schema + * * @param connection * @param databaseName * @param schemaName * @return */ - public List> schemas(Connection connection,String databaseName, String schemaName) { + public List> schemas(Connection connection, String databaseName, String schemaName) { List> schemaList = Lists.newArrayList(); if (StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)) { try (ResultSet resultSet = connection.getMetaData().getSchemas()) { @@ -217,6 +221,7 @@ public List> schemas(Connection connection,String databaseNa /** * 获取所有的数据库表 + * * @param connection * @param databaseName * @param schemaName @@ -224,7 +229,8 @@ public List> schemas(Connection connection,String databaseNa * @param types * @return */ - public List
tables(Connection connection,String databaseName, String schemaName, String tableName, String types[]) { + public List
tables(Connection connection, String databaseName, String schemaName, String tableName, + String types[]) { List
tables = Lists.newArrayList(); int n = 0; try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, @@ -246,6 +252,7 @@ public List
tables(Connection connection,String databaseName, String sche /** * 获取所有的数据库表列 + * * @param connection * @param databaseName * @param schemaName @@ -253,7 +260,8 @@ public List
tables(Connection connection,String databaseName, String sche * @param columnName * @return */ - public List columns(Connection connection,String databaseName, String schemaName, String tableName, String columnName) { + public List columns(Connection connection, String databaseName, String schemaName, String tableName, + String columnName) { List tableColumns = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, columnName)) { @@ -270,13 +278,14 @@ public List columns(Connection connection,String databaseName, Stri /** * 获取所有的数据库表索引 + * * @param connection * @param databaseName * @param schemaName * @param tableName * @return */ - public List indexes(Connection connection,String databaseName, String schemaName, String tableName) { + public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, false, @@ -308,12 +317,13 @@ public List indexes(Connection connection,String databaseName, Strin /** * 获取所有的函数 + * * @param connection * @param databaseName * @param schemaName * @return */ - public List functions(Connection connection,String databaseName, + public List functions(Connection connection, String databaseName, String schemaName) { List functions = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { @@ -328,12 +338,13 @@ public List functions(Connection connection,Strin /** * 获取所有的存储过程 + * * @param connection * @param databaseName * @param schemaName * @return */ - public List procedures(Connection connection,String databaseName, String schemaName) { + public List procedures(Connection connection, String databaseName, String schemaName) { List procedures = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getProcedures(databaseName, schemaName, null)) { while (resultSet != null && resultSet.next()) { diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java index b11b10dfe..2d11f3be0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java @@ -2,6 +2,7 @@ package ai.chat2db.spi.util; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLException; import ai.chat2db.spi.model.*; @@ -16,12 +17,12 @@ public static ai.chat2db.spi.model.Function buildFunction(ResultSet resultSet) { ai.chat2db.spi.model.Function function = new ai.chat2db.spi.model.Function(); try { - function.setDatabaseName(getString(resultSet,"FUNCTION_CAT")); - function.setSchemaName(getString(resultSet,"FUNCTION_SCHEM")); - function.setFunctionName(getString(resultSet,"FUNCTION_NAME")); - function.setRemarks(getString(resultSet,"REMARKS")); + function.setDatabaseName(getString(resultSet, "FUNCTION_CAT")); + function.setSchemaName(getString(resultSet, "FUNCTION_SCHEM")); + function.setFunctionName(getString(resultSet, "FUNCTION_NAME")); + function.setRemarks(getString(resultSet, "REMARKS")); function.setFunctionType(resultSet.getShort("FUNCTION_TYPE")); - function.setSpecificName(getString(resultSet,"SPECIFIC_NAME")); + function.setSpecificName(getString(resultSet, "SPECIFIC_NAME")); } catch (SQLException e) { throw new RuntimeException(e); } @@ -31,12 +32,12 @@ public static ai.chat2db.spi.model.Function buildFunction(ResultSet resultSet) { public static Procedure buildProcedure(ResultSet resultSet) { Procedure procedure = new Procedure(); try { - procedure.setDatabaseName(getString(resultSet,"PROCEDURE_CAT")); - procedure.setSchemaName(getString(resultSet,"PROCEDURE_SCHEM")); - procedure.setProcedureName(getString(resultSet,"PROCEDURE_NAME")); - procedure.setRemarks(getString(resultSet,"REMARKS")); + procedure.setDatabaseName(getString(resultSet, "PROCEDURE_CAT")); + procedure.setSchemaName(getString(resultSet, "PROCEDURE_SCHEM")); + procedure.setProcedureName(getString(resultSet, "PROCEDURE_NAME")); + procedure.setRemarks(getString(resultSet, "REMARKS")); procedure.setProcedureType(resultSet.getShort("PROCEDURE_TYPE")); - procedure.setSpecificName(getString(resultSet,"SPECIFIC_NAME")); + procedure.setSpecificName(getString(resultSet, "SPECIFIC_NAME")); } catch (SQLException e) { throw new RuntimeException(e); } @@ -45,31 +46,31 @@ public static Procedure buildProcedure(ResultSet resultSet) { public static TableIndexColumn buildTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); - tableIndexColumn.setColumnName(getString(resultSet,"COLUMN_NAME")); - tableIndexColumn.setIndexName(getString(resultSet,"INDEX_NAME")); - tableIndexColumn.setAscOrDesc(getString(resultSet,"ASC_OR_DESC")); + tableIndexColumn.setColumnName(getString(resultSet, "COLUMN_NAME")); + tableIndexColumn.setIndexName(getString(resultSet, "INDEX_NAME")); + tableIndexColumn.setAscOrDesc(getString(resultSet, "ASC_OR_DESC")); tableIndexColumn.setCardinality(resultSet.getLong("CARDINALITY")); tableIndexColumn.setPages(resultSet.getLong("PAGES")); - tableIndexColumn.setFilterCondition(getString(resultSet,"FILTER_CONDITION")); - tableIndexColumn.setIndexQualifier(getString(resultSet,"INDEX_QUALIFIER")); + tableIndexColumn.setFilterCondition(getString(resultSet, "FILTER_CONDITION")); + tableIndexColumn.setIndexQualifier(getString(resultSet, "INDEX_QUALIFIER")); // tableIndexColumn.setIndexType(resultSet.getShort("TYPE")); tableIndexColumn.setNonUnique(resultSet.getBoolean("NON_UNIQUE")); tableIndexColumn.setOrdinalPosition(resultSet.getShort("ORDINAL_POSITION")); - tableIndexColumn.setDatabaseName(getString(resultSet,"TABLE_CAT")); - tableIndexColumn.setSchemaName(getString(resultSet,"TABLE_SCHEM")); - tableIndexColumn.setTableName(getString(resultSet,"TABLE_NAME")); + tableIndexColumn.setDatabaseName(getString(resultSet, "TABLE_CAT")); + tableIndexColumn.setSchemaName(getString(resultSet, "TABLE_SCHEM")); + tableIndexColumn.setTableName(getString(resultSet, "TABLE_NAME")); return tableIndexColumn; } public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { TableColumn tableColumn = new TableColumn(); - tableColumn.setDatabaseName(getString(resultSet,"TABLE_CAT")); - tableColumn.setSchemaName(getString(resultSet,"TABLE_SCHEM")); - tableColumn.setTableName(getString(resultSet,"TABLE_NAME")); - tableColumn.setName(getString(resultSet,"COLUMN_NAME")); - tableColumn.setComment(getString(resultSet,"REMARKS")); - tableColumn.setDefaultValue(getString(resultSet,"COLUMN_DEF")); - tableColumn.setTypeName(getString(resultSet,"TYPE_NAME")); + tableColumn.setDatabaseName(getString(resultSet, "TABLE_CAT")); + tableColumn.setSchemaName(getString(resultSet, "TABLE_SCHEM")); + tableColumn.setTableName(getString(resultSet, "TABLE_NAME")); + tableColumn.setName(getString(resultSet, "COLUMN_NAME")); + tableColumn.setComment(getString(resultSet, "REMARKS")); + tableColumn.setDefaultValue(getString(resultSet, "COLUMN_DEF")); + tableColumn.setTypeName(getString(resultSet, "TYPE_NAME")); tableColumn.setColumnSize(resultSet.getInt("COLUMN_SIZE")); tableColumn.setDataType(resultSet.getInt("DATA_TYPE")); tableColumn.setNullable(resultSet.getInt("NULLABLE") == 1); @@ -85,22 +86,30 @@ public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { public static Table buildTable(ResultSet resultSet) throws SQLException { Table table = new Table(); - table.setName(getString(resultSet,"TABLE_NAME")); - table.setComment(getString(resultSet,"REMARKS")); - table.setDatabaseName(getString(resultSet,"TABLE_CAT")); - table.setSchemaName(getString(resultSet,"TABLE_SCHEM")); - table.setType(getString(resultSet,"TABLE_TYPE")); + table.setName(getString(resultSet, "TABLE_NAME")); + table.setComment(getString(resultSet, "REMARKS")); + table.setDatabaseName(getString(resultSet, "TABLE_CAT")); + table.setSchemaName(getString(resultSet, "TABLE_SCHEM")); + table.setType(getString(resultSet, "TABLE_TYPE")); return table; } - private static String getString(ResultSet resultSet,String name){ - if(resultSet == null){ + private static String getString(ResultSet resultSet, String name) { + if (resultSet == null) { return null; } try { return resultSet.getString(name); - }catch (Exception e){ + } catch (Exception e) { return null; } } + + public static String getColumnName(ResultSetMetaData resultSetMetaData, int column) throws SQLException { + String columnLabel = resultSetMetaData.getColumnLabel(column); + if (columnLabel != null) { + return columnLabel; + } + return resultSetMetaData.getColumnName(column); + } } \ No newline at end of file From 711de1f4ba9e3d54e5674c954455c5697779c1f8 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 18 Jul 2023 21:34:49 +0800 Subject: [PATCH 0352/1069] update exception --- .../java/ai/chat2db/spi/sql/IDriverManager.java | 14 +++----------- 1 file changed, 3 insertions(+), 11 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 e5a849588..83ae4e2be 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 @@ -84,24 +84,16 @@ public static Connection getConnection(String url, Properties info, DriverConfig driverEntry = getJDBCDriver(driver); } try { - Connection con = driverEntry.getDriver().connect(url, info); - if (con != null) { - return con; - } + return driverEntry.getDriver().connect(url, info); } catch (SQLException var7) { Connection con = tryConnectionAgain(driverEntry, url, info); if (con != null) { return con; } else { - throw var7; + throw new SQLException("Cannot create connection (" + var7.getMessage() + ")", "08001", + var7); } } - - if (reason != null) { - throw reason; - } else { - throw new SQLException("No suitable driver found for " + url, "08001"); - } } private static Connection tryConnectionAgain(DriverEntry driverEntry, String url, From ac3245d991867b4b32c6dacd67a7fc8f20cca4d4 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 18 Jul 2023 22:12:00 +0800 Subject: [PATCH 0353/1069] Update README.md --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 804884e13..7293490eb 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

Chat2DB

+截屏2023-07-18 22 10 56

Chat2DB

@@ -83,8 +83,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 ## 🌰 Demo ### Create data source - - + ### Data source management From 4ee88bdb58a8869efaa3f5489e952c2589934579 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 18 Jul 2023 22:15:19 +0800 Subject: [PATCH 0354/1069] =?UTF-8?q?=E6=B2=A1=E6=9C=89=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=97=B6=E8=87=AA=E5=8A=A8=E9=80=89=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/WorkspaceHeader/index.less | 23 ++- .../components/WorkspaceHeader/index.tsx | 182 +++++++++++------- 2 files changed, 135 insertions(+), 70 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less index 85989aea0..da1cb564a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -5,8 +5,11 @@ display: flex; align-items: center; padding-left: 10px; + font-size: 14px; background-color: var(--color-bg-elevated); border-bottom: 1px solid var(--color-border-secondary); + font-weight: bold; + } .databaseLogo{ @@ -17,6 +20,10 @@ display: flex; justify-content: center; align-items: center; + font-weight: normal; + i{ + font-size: 16px; + } .refreshBox{ cursor: pointer; } @@ -28,8 +35,9 @@ .crumbsItem{ display: flex; align-items: center; - height: 28px; + height: 36px; cursor: pointer; + &:hover{ color: var(--color-primary); } @@ -45,3 +53,16 @@ } } +.refreshIcon{ + margin-left: 20px; + cursor: pointer; +} + +.noConnectionModal{ + display: flex; + justify-content: space-between; + + .mainText{ + + } +} 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 78a6f503e..6f56c4da3 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -2,9 +2,10 @@ import React, { memo, useEffect, useMemo, useState } from 'react'; import { connect } from 'umi'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; +import { IMainPageType } from '@/models/mainPage'; import styles from './index.less'; import classnames from 'classnames'; -import { Cascader, Spin } from 'antd'; +import { Cascader, Spin, Modal, Button } from 'antd'; import Iconfont from '@/components/Iconfont'; import { databaseMap } from '@/constants'; import { useSafeState } from 'ahooks'; @@ -15,16 +16,48 @@ interface IProps { cascaderOptions: any; connectionModel: IConnectionModelType['state']; workspaceModel: IWorkspaceModelType['state']; + mainPageModel: IMainPageType['state']; dispatch: any; } const WorkspaceHeader = memo((props) => { - const { className, cascaderOptions, connectionModel, workspaceModel, dispatch } = props; + const { className, cascaderOptions, connectionModel, workspaceModel, mainPageModel, dispatch } = props; const { connectionList, curConnection } = connectionModel; const { curWorkspaceParams } = workspaceModel; + const { curPage } = mainPageModel; const [curSchemaOptions, setCurSchemaOptions] = useState([]); const [cascaderLoading, setCascaderLoading] = useState(false); + const [noConnectionModal, setNoConnectionModal] = useState(false); + + const databaseChange: any = (valueArr: any, selectedOptions: any) => { + + const curWorkspaceParams = { + dataSourceId: curConnection?.id, + databaseSourceName: curConnection?.alias, + databaseName: selectedOptions[0].value, + schemaName: selectedOptions?.[0]?.next?.[0]?.value, + databaseType: curConnection?.type, + }; + + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: curWorkspaceParams, + }); + + setCurSchemaOptions(selectedOptions[0].next || []) + }; + + const schemaChange: any = (valueArr: any, selectedOptions: any) => { + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: { ...curWorkspaceParams, schemaName: selectedOptions[0].value }, + }); + } + const connectionListOptions = useMemo(() => { + if (!curConnection && connectionList.length) { + connectionChange([connectionList[0].id], [connectionList[0]]); + } return connectionList?.map(t => { return { value: t.id, @@ -34,9 +67,21 @@ const WorkspaceHeader = memo((props) => { }, [connectionList]) useEffect(() => { - getConnectionList(); + if (curConnection?.id) { + getConnectionList(); + } }, []); + + + useEffect(() => { + if (curPage === 'workspace' && !connectionList.length) { + setNoConnectionModal(true) + return + } + setNoConnectionModal(false) + }, [curPage]) + const getConnectionList = () => { setCascaderLoading(true) dispatch({ @@ -60,89 +105,88 @@ const WorkspaceHeader = memo((props) => { }) } - const databaseChange: any = (valueArr: any, selectedOptions: any) => { - - const curWorkspaceParams = { - dataSourceId: curConnection?.id, - databaseSourceName: curConnection?.alias, - databaseName: selectedOptions[0].value, - schemaName: selectedOptions?.[0]?.next?.[0]?.value, - databaseType: curConnection?.type, - }; - - dispatch({ - type: 'workspace/setCurWorkspaceParams', - payload: curWorkspaceParams, - }); - - setCurSchemaOptions(selectedOptions[0].next) - }; - - const schemaChange: any = (valueArr: any, selectedOptions: any) => { - dispatch({ - type: 'workspace/setCurWorkspaceParams', - payload: { ...curWorkspaceParams, schemaName: selectedOptions[0].value }, - }); - } - function handelRefresh() { - getConnectionList() + getConnectionList(); } - return
-
- {curConnection?.type ? -
- {cascaderLoading ? : } -
- : - } -
- -
-
{curConnection?.alias}
- -
-
+ return <> - -
-
{curWorkspaceParams.databaseName}
- { - !!curSchemaOptions.length && - } + {curConnection && !!connectionList.length && curWorkspaceParams &&
+
+ {curConnection?.type ? +
+ {cascaderLoading ? : } +
+ : + }
- - { - !!curSchemaOptions.length &&
-
{curWorkspaceParams.schemaName}
+
{curConnection?.alias}
+
- } -
+ +
+
{curWorkspaceParams.databaseName}
+ { + !!curSchemaOptions.length && + } +
+
+ { + !!curSchemaOptions.length && + +
+
{curWorkspaceParams.schemaName}
+
+
+ } + +
} + } + keyboard={false} + maskClosable={false} + title='温馨提示' + footer={[]} + > +
+
+ 您当前还没有创建任何连接 +
+ +
+
+ }) export default connect( - ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ + ({ connection, workspace, mainPage }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, mainPage: IMainPageType }) => ({ connectionModel: connection, workspaceModel: workspace, + mainPageModel: mainPage, }), )(WorkspaceHeader); From 763296ee7deb6c697d9bb9d0d47c71f63ecbc1bc Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 18 Jul 2023 22:21:50 +0800 Subject: [PATCH 0355/1069] Update README.md --- README.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 332181372..03c2a9add 100644 --- a/README.md +++ b/README.md @@ -84,15 +84,16 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 ### Create data source - +crete datasource + ### Data source management - +2 ### SQL console - +2 ### AI intelligent assistant @@ -107,7 +108,8 @@ Option 1 (recommended): To use the ChatSql function of OPENAI, two conditions mu - You need an OPENAI_API_KEY. - The client's network can connect to the OPENAI website, and for users in China, a VPN is required. Note: If the local VPN is not fully effective, the network connectivity can be ensured by setting the network proxy HOST and PORT in the client. - +3 + Option 2 (recommended): We provide a unified proxy service. @@ -116,15 +118,16 @@ Option 2 (recommended): We provide a unified proxy service. To facilitate users' quick use of AI capabilities, you can scan the QR code below to follow our WeChat public account and apply for our custom API_KEY. - +4 + ### CONFIGURE CUSTOM AI - Customized AI can be any LLM that you deployed, such as ChatGLM、ChatGPT、ERNIE Bot、Tongyi Qianwen, and so on. However, the customized interface need to conform to the protocol definition. Otherwise, secondary development may be required. Two DEMOs are provided in the code, the configuration is as shown below. In specific use, you can refer to the DEMO interface to write a custom interface, or directly perform secondary development in the DEMO interface. - DEMO for configuring customized stream output interface. - - DEMO for configuring customized non-stream output interface. - +5 + ## 📦 Docker installation From aaeb5bd3c37021efc12aa20541f3f081c8add979 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 18 Jul 2023 22:27:16 +0800 Subject: [PATCH 0356/1069] Update README.md update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03c2a9add..9cd6a75e8 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 ### AI intelligent assistant - +![image](https://github.com/chat2db/Chat2DB/assets/22975773/2dfc4aaa-c5a3-42c3-bc61-28ebc237a27b) ## 🔥 AI Configuration From 04c172eaec39bf456dfd85bfa2e7acb5a6209937 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 18 Jul 2023 22:27:28 +0800 Subject: [PATCH 0357/1069] =?UTF-8?q?fix:=E8=BF=9E=E6=8E=A5=E8=A2=AB?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/WorkspaceHeader/index.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) 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 6f56c4da3..7e3fabeb3 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -67,28 +67,27 @@ const WorkspaceHeader = memo((props) => { }, [connectionList]) useEffect(() => { - if (curConnection?.id) { - getConnectionList(); - } - }, []); - - - - useEffect(() => { - if (curPage === 'workspace' && !connectionList.length) { - setNoConnectionModal(true) - return - } - setNoConnectionModal(false) - }, [curPage]) + getConnectionList(); + }, [curPage]); const getConnectionList = () => { setCascaderLoading(true) dispatch({ type: 'connection/fetchConnectionList', - callback: () => { + callback: (res: any) => { setTimeout(() => { setCascaderLoading(false) + if (curPage === 'workspace' && !res.data?.length) { + setNoConnectionModal(true) + return + } + setNoConnectionModal(false) + if (curConnection?.id && res.data.length) { + const flag = res.data.findIndex((t: any) => t.id === curConnection?.id) + if (flag === -1) { + connectionChange([res.data[0].id], [res.data[0]]); + } + } }, 200); } }); From 1a7e47ac8a94e75f3469646db3e594e81ba41c4c Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 19 Jul 2023 21:07:14 +0800 Subject: [PATCH 0358/1069] feat: client add zoom in/out/resert operation --- chat2db-client/src/main/menu.js | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index ec97780ff..8cbbde012 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -1,7 +1,7 @@ const { shell, app, dialog, BrowserWindow, Menu } = require('electron'); const os = require('os'); const path = require('path'); -const registerAppMenu = () => { +const registerAppMenu = (mainWindow) => { const menuBar = [ { label: 'Chat2DB', @@ -19,9 +19,6 @@ const registerAppMenu = () => { }); }, }, - // { - // label: '检查更新', - // }, { type: 'separator' }, { label: '退出', @@ -61,6 +58,22 @@ const registerAppMenu = () => { }, }, { type: 'separator' }, + { + label: '放大', + accelerator: 'CmdOrCtrl+=', + role: 'zoomIn', + }, + { + label: '缩小', + accelerator: 'CmdOrCtrl+-', + role: 'zoomOut', + }, + { + label: '重置', + accelerator: 'CmdOrCtrl+0', + role: 'resetZoom', + }, + { type: 'separator' }, { label: '全屏', role: 'togglefullscreen' }, ], }, From ec9b01157a5158622b98185179a460d320fbd51c Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 19 Jul 2023 21:10:58 +0800 Subject: [PATCH 0359/1069] feat: Adjusted program to allow client direct closure of Java process upon exit. --- chat2db-client/src/main/index.js | 54 ++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index ab3828e39..c72799050 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -1,4 +1,5 @@ const { app, BrowserWindow, Menu, shell, net, ipcMain, dialog } = require('electron'); +const { exec } = require('child_process'); const path = require('path'); const os = require('os'); const fs = require('fs'); @@ -64,16 +65,51 @@ app.on('window-all-closed', () => { }); app.on('before-quit', (event) => { - try { - const request = net.request({ - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - url: 'http://127.0.0.1:10824/api/system/stop', + const isWindows = os.platform() === 'win32'; + let ports = [10821, 10822, 10824]; // 日常端口、测试包端口、线上包端口 + for (let port of ports) { + let command = ''; + if (isWindows) { + command = `netstat -ano | findstr:${port}`; + } else { + command = `lsof -i :${port} | awk '{print $2}'`; + } + + exec(command, (err, stdout) => { + if (err) { + console.error(`exec error: ${err}`); + return; + } + + let pidArr = []; + if (isWindows) { + const lines = stdout.trim().split('\n'); + pidArr = lines.map((line) => line.trim().split(/\s+/)[4]).filter((pid) => !isNaN(pid)); + } else { + pidArr = stdout.trim().split('\n'); + } + + if (pidArr.length) { + try { + (pidArr || []).forEach((pid) => { + !!pid && !isNaN(pid) && process.kill(pid); + }); + } catch (error) { + console.error(`Error killing process: ${error}`); + } + } }); - request.end(); - } catch (error) { } + } + // try { + // const request = net.request({ + // headers: { + // 'Content-Type': 'application/json', + // }, + // method: 'POST', + // url: 'http://127.0.0.1:10824/api/system/stop', + // }); + // request.end(); + // } catch (error) { } }); ipcMain.handle('get-product-name', (event) => { From 2e25eed1dbc07e50f4b6b1e4682ba02f96c2ce45 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 19 Jul 2023 21:31:34 +0800 Subject: [PATCH 0360/1069] feat: update iconfont --- .vscode/settings.json | 1 + chat2db-client/src/components/Iconfont/index.less | 9 ++++----- chat2db-client/src/components/Iconfont/index.tsx | 12 ++++++------ 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b18c708f8..b144a66ae 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cSpell.words": [ "ahooks", + "alicdn", "antd", "asar", "AZUREAI", diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 15f70156a..27450f7f7 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,9 +1,8 @@ /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.woff2?t=1689606758277') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.woff?t=1689606758277') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.ttf?t=1689606758277') format('truetype'); + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); } .iconfont { @@ -14,4 +13,4 @@ -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale; -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 16ae9a356..e52f07b04 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -9,13 +9,13 @@ if (__ENV__ === 'local') { /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/a/font_3633546_cvx0po2baqd.woff2?t=1688205862419') format('woff2'), - url('//at.alicdn.com/t/a/font_3633546_cvx0po2baqd.woff?t=1688205862419') format('woff'), - url('//at.alicdn.com/t/a/font_3633546_cvx0po2baqd.ttf?t=1688205862419') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.woff2?t=1689606758277') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.woff?t=1689606758277') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_fu54vhe509d.ttf?t=1689606758277') format('truetype'); } - ` - let style = document.createElement("style"); - style.type = "text/css"; + `; + let style = document.createElement('style'); + style.type = 'text/css'; document.head.appendChild(style); style.appendChild(document.createTextNode(container)); } From 116a19f1480e7e1483a8739f30cb4b11fda5df45 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Wed, 19 Jul 2023 21:40:37 +0800 Subject: [PATCH 0361/1069] add custom jdbc --- .../domain/api/param/DataSourceTestParam.java | 12 +++++++---- .../core/impl/DataSourceServiceImpl.java | 21 +++++++++---------- .../web/api/aspect/ConnectionInfoHandler.java | 6 +++++- .../ai/chat2db/spi/config/DriverConfig.java | 7 +++++++ 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceTestParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceTestParam.java index 38e132897..4f49978e0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceTestParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DataSourceTestParam.java @@ -1,19 +1,17 @@ package ai.chat2db.server.domain.api.param; +import java.util.List; +import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; import jakarta.validation.constraints.NotNull; - - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; -import java.util.List; - /** * 数据源测试参数 * @@ -89,4 +87,10 @@ public class DataSourceTestParam { * 扩展信息 */ private List extendInfo; + + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; } 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 56f0ac35d..fd91220a7 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 @@ -114,19 +114,18 @@ public ListResult queryByIds(List ids) { @Override public ActionResult preConnect(DataSourcePreConnectParam param) { DataSourceTestParam testParam - = dataSourceConverter.param2param(param); - -// todo -// DriverConfig driverConfig = new DriverConfig(); -// driverConfig.setJdbcDriver(param.getDriver()); -// driverConfig.setJdbcDriverClass(param.getDriver()); - DriverConfig driverConfig = Chat2DBContext.getDefaultDriverConfig(param.getType()); + = dataSourceConverter.param2param(param); + DriverConfig driverConfig = testParam.getDriverConfig(); + if (driverConfig == null || !driverConfig.notEmpty()) { + driverConfig = Chat2DBContext.getDefaultDriverConfig(param.getType()); + } DataSourceConnect dataSourceConnect = JdbcUtils.testConnect(testParam.getUrl(), testParam.getHost(), - testParam.getPort(), - testParam.getUsername(), testParam.getPassword(), testParam.getDbType(), - driverConfig, param.getSsh(), KeyValue.toMap(param.getExtendInfo())); + testParam.getPort(), + testParam.getUsername(), testParam.getPassword(), testParam.getDbType(), + driverConfig, param.getSsh(), KeyValue.toMap(param.getExtendInfo())); if (BooleanUtils.isNotTrue(dataSourceConnect.getSuccess())) { - return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription(), dataSourceConnect.getErrorDetail()); + return ActionResult.fail(dataSourceConnect.getMessage(), dataSourceConnect.getDescription(), + dataSourceConnect.getErrorDetail()); } return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index cef6ec81b..89e39bd7c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -7,6 +7,7 @@ import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; +import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import lombok.extern.slf4j.Slf4j; @@ -28,7 +29,6 @@ public class ConnectionInfoHandler { @Autowired private DataSourceService dataSourceService; - @Around("within(@ai.chat2db.server.web.api.aspect.ConnectionInfoAspect *)") public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { try { @@ -78,6 +78,10 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { connectInfo.setUrl(dataSource.getUrl()); connectInfo.setPort(dataSource.getPort() != null ? Integer.parseInt(dataSource.getPort()) : null); connectInfo.setHost(dataSource.getHost()); + DriverConfig driverConfig = dataSource.getDriverConfig(); + if (driverConfig != null && driverConfig.notEmpty()) { + connectInfo.setDriverConfig(driverConfig); + } return connectInfo; } 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 39be1918a..03e6bdda4 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 @@ -4,6 +4,7 @@ import java.util.List; import lombok.Data; +import org.apache.commons.lang3.StringUtils; /** * @author jipengfei @@ -38,4 +39,10 @@ public class DriverConfig { * 自定义 */ private boolean custom; + + + public boolean notEmpty() { + return StringUtils.isNotBlank(getJdbcDriver()) && StringUtils.isNotBlank( + getJdbcDriverClass()); + } } \ No newline at end of file From c255abda9eca568162919ebd30b80cbc41541c97 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 19 Jul 2023 22:15:35 +0800 Subject: [PATCH 0362/1069] feat: adjustment package.json --- chat2db-client/package.json | 8 +- .../src/components/MyNotification/index.tsx | 83 +- chat2db-client/src/i18n/en-us/common.ts | 5 +- chat2db-client/src/i18n/zh-cn/common.ts | 2 +- .../src/pages/main/connection/index.tsx | 2 +- chat2db-client/yarn.lock | 9849 ----------------- 6 files changed, 58 insertions(+), 9891 deletions(-) delete mode 100644 chat2db-client/yarn.lock diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 03c659f71..2468054fb 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -36,7 +36,6 @@ "monaco-editor": "^0.34.0", "monaco-editor-esm-webpack-plugin": "^2.1.0", "monaco-editor-webpack-plugin": "^7.0.1", - "react-monaco-editor": "^0.52.0", "react-sortablejs": "^6.1.4", "sql-formatter": "^12.2.1", "styled-components": "^6.0.1", @@ -63,6 +62,13 @@ "prettier-plugin-packagejson": "^2", "typescript": "^5.0.3" }, + "peerDependencies": { + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "engines": { + "node": ">=16" + }, "build": { "appId": "com.chat2db", "directories": { diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index d1cda3054..b268ae065 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -1,6 +1,6 @@ -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react'; import { Button, ConfigProvider, Modal, notification, Space } from 'antd'; -import styles from './index.less' +import styles from './index.less'; import i18n from '@/i18n'; import { IconType } from 'antd/es/notification/interface'; import Iconfont from '../Iconfont'; @@ -20,39 +20,47 @@ interface IProps { function MyNotification() { const [notificationApi, notificationDom] = notification.useNotification({ - maxCount: 2 + maxCount: 2, }); const [open, setOpen] = useState(false); - const [props, setProps] = useState() + const [props, setProps] = useState(); window._notificationApi = useCallback((props: IProps) => { const { errorCode, errorMessage, errorDetail, solutionLink } = props; setProps(props); const btn = ( - - ); const renderDescription = () => { - return
- {props.errorCode}{props.errorCode}{props.errorCode}{props.errorCode} -
- } + return ( +
+ {props.errorCode} {props.errorMessage} +
+ ); + }; const renderMessage = () => { - return
- - Error -
- } + return ( +
+ + Error +
+ ); + }; notificationApi.open({ className: styles.notification, @@ -61,25 +69,26 @@ function MyNotification() { placement: 'bottomRight', btn, duration: null, - }) - }, []) + }); + }, []); - - return <> - {notificationDom} - { - setOpen(false) - }} - > - {props?.errorDetail} - - + return ( + <> + {notificationDom} + { + setOpen(false); + }} + > + {props?.errorDetail} + + + ); } -export default MyNotification; \ No newline at end of file +export default MyNotification; diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 98120bd75..8fb57fd49 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -56,8 +56,9 @@ export default { 'common.text.tryToRestart': 'Try To Restart', 'common.text.contactUs': 'Contact Us', 'common.text.wechatPopularizeAi': 'Follow the wechat public account and send "AI" to get free experiences.', - 'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', + 'common.text.wechatPopularizeAi2': + 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', - 'common.notification.detial': 'More details', + 'common.notification.detail': 'More details', 'common.notification.solution': 'Solution', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index f37fc49ce..b8873f432 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -58,6 +58,6 @@ export default { 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', - 'common.notification.detial': '查看详情', + 'common.notification.detail': '查看详情', 'common.notification.solution': '解决办法', }; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 5449f342d..2566051af 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -134,7 +134,7 @@ function Connections(props: IProps) {
{i18n('connection.title.connections')}
{renderMenu()} { - curConnection && Object.keys(curConnection).length && + curConnection && Object.keys(curConnection).length > 0 &&
<> + {/*
+
+ +
+
*/}

{i18n('common.text.noData')}

}} - isLoading={isLoading} + // isStickyHead + // stickyTop={24} {...pipeline.getProps()} />
diff --git a/chat2db-client/src/constants/table.ts b/chat2db-client/src/constants/table.ts index ab8c4f5a0..a28c62257 100644 --- a/chat2db-client/src/constants/table.ts +++ b/chat2db-client/src/constants/table.ts @@ -14,9 +14,10 @@ export enum TableDataType { ROWID = 'ROWID', ANY = 'ANY', UNKNOWN = 'UNKNOWN', + CHAT2DB_ROW_NUMBER = 'CHAT2DB_ROW_NUMBER', } export enum StatusType { SUCCESS = 'success', FAIL = 'fail', -} \ No newline at end of file +} From 3adbd9418dc8cb7580fb88dd3f8273fcf3153db3 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 22 Jul 2023 22:54:01 +0800 Subject: [PATCH 0389/1069] feat: Add sql statement on result tab --- .../src/components/SearchResult/index.tsx | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 75948fce2..e020e1ef3 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -3,12 +3,9 @@ import classnames from 'classnames'; import Tabs, { IOption } from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; -import LoadingContent from '@/components/Loading/LoadingContent'; -import MonacoEditor from '@/components/Console/MonacoEditor'; -import { Button, DatePicker, Input, Table, Modal, message, Spin } from 'antd'; -import { StatusType, TableDataType } from '@/constants'; -import { formatDate } from '@/utils/date'; -import { IManageResultData, ITableHeaderItem } from '@/typings'; +import { Spin, Popover } from 'antd'; +import { StatusType } from '@/constants'; +import { IManageResultData } from '@/typings'; import i18n from '@/i18n'; import { v4 as uuidv4 } from 'uuid'; import TableBox from './TableBox'; @@ -29,14 +26,14 @@ const handleTabs = (result: IManageResultData[]) => { return (result || []).map((item, index) => { return { label: ( - <> + {`${i18n('common.text.executionResult')}-${index + 1}`} - + ), value: item.uuid!, }; From c3cccf11867a9bbd90aff47a7a0540ca167f1132 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 22 Jul 2023 22:59:16 +0800 Subject: [PATCH 0390/1069] Auto Upgrade --- .github/workflows/release_test.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index db19a9380..bc0f19cca 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -129,7 +129,6 @@ jobs: run: | cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/1.0.${{ github.run_id }}/static/ cp -r chat2db-server/chat2db-server-start/target/lib chat2db-client/versions/1.0.${{ github.run_id }}/static/lib - zip chat2db-client/versions/1.0.${{ github.run_id }}/static/chat2db-server-start.zip chat2db-client/versions/1.0.${{ github.run_id }}/static/chat2db-server-start.jar chat2db-client/versions/1.0.${{ github.run_id }}/static/lib/ - name: Prepare Build Electron run: | @@ -208,7 +207,7 @@ jobs: mkdir oss_temp_file/mac mkdir oss_temp_file/mac/amd cp chat2db-client/versions/1.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file/jar/${{ github.run_id }} - cp chat2db-client/versions/1.0.${{ github.run_id }}/static/chat2db-server-start.zip ./oss_temp_file/jar/${{ github.run_id }} + zip -r ./oss_temp_file/jar/${{ github.run_id }}/chat2db-server-start.zip chat2db-client/versions/1.0.${{ github.run_id }}/static/ cp -r chat2db-client/release/*.zip ./oss_temp_file/mac/amd cp -r chat2db-client/release/*.zip.blockmap ./oss_temp_file/mac/amd cp -r chat2db-client/release/*.dmg ./oss_temp_file/mac/amd From cc48d52c065baa7021fbb61d78c17e4c8ab9f844 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 22 Jul 2023 23:41:05 +0800 Subject: [PATCH 0391/1069] Auto Upgrade --- .github/workflows/release_test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index bc0f19cca..c4c21b69c 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -207,13 +207,14 @@ jobs: mkdir oss_temp_file/mac mkdir oss_temp_file/mac/amd cp chat2db-client/versions/1.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file/jar/${{ github.run_id }} - zip -r ./oss_temp_file/jar/${{ github.run_id }}/chat2db-server-start.zip chat2db-client/versions/1.0.${{ github.run_id }}/static/ cp -r chat2db-client/release/*.zip ./oss_temp_file/mac/amd cp -r chat2db-client/release/*.zip.blockmap ./oss_temp_file/mac/amd cp -r chat2db-client/release/*.dmg ./oss_temp_file/mac/amd cp -r chat2db-client/release/*.dmg.blockmap ./oss_temp_file/mac/amd cp -r chat2db-client/release/latest-mac.yml ./oss_temp_file/mac/amd ls chat2db-client/release/ + cd chat2db-client/versions/1.0.${{ github.run_id }}/static/ && zip -r chat2db-server-start.zip ./ + cp -r chat2db-server-start.zip ../../../../oss_temp_file/jar/${{ github.run_id }} # 准备要需要的数据 MacOS arm64 - name: Prepare upload for MacOS arm64 From 10a8ab1421262efdfac069db6f7e6ebd7401ca5a Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 23 Jul 2023 11:05:40 +0800 Subject: [PATCH 0392/1069] Auto Upgrade --- .github/workflows/release.yml | 5 +++++ .github/workflows/release_test.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7192e74d..1db460426 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -142,6 +142,8 @@ jobs: - name: Copy App run: | cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/ + cp -r chat2db-server/chat2db-server-start/target/lib chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/lib + - name: Prepare Build Electron run: | @@ -215,6 +217,9 @@ jobs: mkdir oss_temp_file cp chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/chat2db-server-start.jar ./oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file + cp -r chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/dist ./oss_temp_file/dist + cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/ && zip -r chat2db-server-start.zip ./ + cp -r chat2db-server-start.zip ../../../../oss_temp_file/jar/${{ steps.chat2db_version.outputs.substring }} # 准备要需要的数据 MacOS arm64 - name: Prepare upload for MacOS arm64 diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index c4c21b69c..cae2effe9 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -213,6 +213,7 @@ jobs: cp -r chat2db-client/release/*.dmg.blockmap ./oss_temp_file/mac/amd cp -r chat2db-client/release/latest-mac.yml ./oss_temp_file/mac/amd ls chat2db-client/release/ + cp -r chat2db-client/versions/1.0.${{ github.run_id }}/dist ./oss_temp_file/mac/dist cd chat2db-client/versions/1.0.${{ github.run_id }}/static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file/jar/${{ github.run_id }} From b13368e493429984f0fe2fa877045f8ef55ccc73 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 23 Jul 2023 11:21:39 +0800 Subject: [PATCH 0393/1069] Query modification returns the original SQL --- .../server/domain/core/impl/DlTemplateServiceImpl.java | 5 ++++- .../server/web/api/controller/rdb/vo/ExecuteResultVO.java | 5 +++++ .../src/main/java/ai/chat2db/spi/model/ExecuteResult.java | 6 +++++- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 99bbda454..b6bd7a6ad 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -66,7 +66,8 @@ public ListResult execute(DlExecuteParam param) { List result = new ArrayList<>(); ListResult listResult = ListResult.of(result); // 执行sql - for (String sql : sqlList) { + for (String originalSql : sqlList) { + String sql = originalSql; int pageNo = 0; int pageSize = 0; String sqlType = SqlTypeEnum.UNKNOWN.getCode(); @@ -79,6 +80,7 @@ public ListResult execute(DlExecuteParam param) { log.warn("解析sql失败:{}", sql, e); ExecuteResult executeResult = ExecuteResult.builder() .success(Boolean.FALSE) + .originalSql(originalSql) .sql(sql) .message(e.getMessage()) .build(); @@ -106,6 +108,7 @@ public ListResult execute(DlExecuteParam param) { ExecuteResult executeResult = execute(sql); executeResult.setSqlType(sqlType); + executeResult.setOriginalSql(originalSql); // 自动分页 if (autoLimit) { executeResult.setPageNo(pageNo); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java index 6bab54523..d7a3ccb21 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java @@ -18,6 +18,11 @@ public class ExecuteResultVO { */ private String sql; + /** + * Original SQL without pagination + */ + private String originalSql; + /** * 描述 */ diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java index b507e94a6..adc96ee9b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java @@ -2,7 +2,6 @@ import java.util.List; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -35,6 +34,11 @@ public class ExecuteResult { */ private String sql; + /** + * Original SQL without pagination + */ + private String originalSql; + /** * 描述 */ From fb7f8891a77d34f9cdea2c4b1d47cda677916862 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 23 Jul 2023 12:30:31 +0800 Subject: [PATCH 0394/1069] feat: Result table add pagination --- .../src/components/Console/index.tsx | 18 +-- .../src/components/SearchResult/TableBox.less | 45 ++++++++ .../src/components/SearchResult/TableBox.tsx | 42 +++++-- .../src/components/SearchResult/index.less | 20 +--- .../src/components/SearchResult/index.tsx | 61 +++++----- .../pages/main/dashboard/chart-item/index.tsx | 36 +++--- .../components/WorkspaceRightItem/index.less | 2 +- .../components/WorkspaceRightItem/index.tsx | 105 +++++++++++++++--- chat2db-client/src/service/sql.ts | 35 +++--- chat2db-client/src/typings/database.ts | 8 ++ 10 files changed, 250 insertions(+), 122 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index c5802175d..4ada2bda4 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -71,8 +71,7 @@ interface IProps { dispatch: Function; // remainingUse: IAIState['remainingUse']; // onSQLContentChange: (v: string) => void; - onExecuteSQLBefore?: () => void; - onExecuteSQL: (result: any, sql: string, createHistoryParams) => void; + onExecuteSQL: (sql: string) => void; onConsoleSave: () => void; tables: any[]; } @@ -315,25 +314,12 @@ function Console(props: IProps) { }; const executeSQL = (sql?: string) => { - props.onExecuteSQLBefore && props.onExecuteSQLBefore(); - const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); if (!sqlContent) { return; } - - let p: any = { - sql: sqlContent, - ...executeParams, - }; - sqlServer.executeSql(p).then((res) => { - let createHistoryParams: any = { - ...executeParams, - ddl: sqlContent, - }; - props.onExecuteSQL?.(res, sqlContent!, createHistoryParams); - }); + props.onExecuteSQL && props.onExecuteSQL(sqlContent); }; const saveConsole = (value?: string) => { diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox.less index db9170ea5..8565472a2 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox.less @@ -34,6 +34,29 @@ } .toolBar { + position: sticky; + top: 0; + z-index: 30; + display: flex; + justify-content: start; + align-items: center; + border-top: 1px solid var(--color-border-secondary); + background-color: var(--color-bg-elevated); + padding: 0 16px; +} +.toolBarItem { + margin: 4px 0; + padding-right: 8px; + display: flex; + justify-content: start; + align-items: center; + &:not(:last-child) { + border-right: 1px solid var(--color-border); + } +} + +.table { + flex: 1; } .statusBar { @@ -47,6 +70,10 @@ align-items: center; border-top: 1px solid var(--color-border-secondary); background-color: var(--color-bg-elevated); + + & > span { + margin-right: 16px; + } } .tableItem { @@ -94,6 +121,24 @@ } } +.pagination { + :global { + .ant-pagination-prev, + .ant-pagination-next, + .ant-pagination-jump-prev, + .ant-pagination-jump-next { + min-width: 20px; + } + .ant-pagination-simple-pager { + input { + padding: 0px !important; + font-size: 12px; + height: 70% !important; + } + } + } +} + .monacoEditor { height: 400px; margin: -15px; diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index 135bfc868..fceea2dd4 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useMemo, useState } from 'react'; import { TableDataType } from '@/constants/table'; -import { IManageResultData, ITableHeaderItem } from '@/typings/database'; +import { IManageResultData, IResultConfig, ITableHeaderItem } from '@/typings/database'; import { formatDate } from '@/utils/date'; -import { Button, message, Modal, Pagination, Table } from 'antd'; +import { Button, message, Modal, Pagination, Select, Table } from 'antd'; import antd from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem, BaseTableProps } from 'ali-react-table'; import Iconfont from '../Iconfont'; @@ -19,7 +19,8 @@ import { compareStrings } from '@/utils/sort'; interface ITableProps { className?: string; data: IManageResultData; - isLoading?: boolean; + config: IResultConfig; + onConfigChange: (config: IResultConfig) => void; } interface IViewTableCellData { @@ -43,7 +44,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` `; export default function TableBox(props: ITableProps) { - const { className, data, isLoading } = props; + const { className, data, config, onConfigChange } = props; const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); @@ -161,20 +162,43 @@ export default function TableBox(props: ITableProps) { }), ); + const onPageNoChange = (pageNo: number) => { + onConfigChange && onConfigChange({ ...config, pageNo }); + }; + const onPageSizeChange = (pageSize: number) => { + onConfigChange && onConfigChange({ ...config, pageSize, pageNo: 1 }); + }; return (
{columns.length ? ( <> - {/*
+
- + + Date: Sun, 23 Jul 2023 13:31:27 +0800 Subject: [PATCH 0396/1069] feat: Optimize styles --- chat2db-client/src/components/SearchResult/TableBox.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index 4b7b9e50c..8bebf3148 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -182,7 +182,8 @@ export default function TableBox(props: ITableProps) { {...config} /> + + + + +
+ ); +} diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox.tsx index 8bebf3148..466eaa2a9 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox.tsx @@ -15,12 +15,14 @@ import styles from './TableBox.less'; import { ThemeType } from '@/constants'; import i18n from '@/i18n'; import { compareStrings } from '@/utils/sort'; +import MyPagination from './Pagination'; interface ITableProps { className?: string; data: IManageResultData; config: IResultConfig; onConfigChange: (config: IResultConfig) => void; + onSearchTotal: () => Promise; } interface IViewTableCellData { @@ -44,7 +46,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` `; export default function TableBox(props: ITableProps) { - const { className, data, config, onConfigChange } = props; + const { className, data, config, onConfigChange, onSearchTotal } = props; const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); @@ -168,31 +170,23 @@ export default function TableBox(props: ITableProps) { const onPageSizeChange = (pageSize: number) => { onConfigChange && onConfigChange({ ...config, pageSize, pageNo: 1 }); }; + + const onClickTotalBtn = async () => { + if (props.onSearchTotal) { + return await props.onSearchTotal(); + } + }; return (
{columns.length ? ( <>
- - { - setChatGPTConfig({ ...chatGPTConfig, chat2dbApiKey: e.target.value }); + setAiConfig({ ...aiConfig, apiKey: e.target.value }); }} />
)} - {chatGPTConfig?.aiSqlSource === AiSqlSourceType.OPENAI && ( + {aiConfig?.aiSqlSource === AiSqlSourceType.OPENAI && (
Api Key
{ - setChatGPTConfig({ ...chatGPTConfig, apiKey: e.target.value }); + setAiConfig({ ...aiConfig, apiKey: e.target.value }); }} />
@@ -91,9 +90,9 @@ export default function SettingAI(props: IProps) {
{ - setChatGPTConfig({ ...chatGPTConfig, apiHost: e.target.value }); + setAiConfig({ ...aiConfig, apiHost: e.target.value }); }} />
@@ -101,10 +100,10 @@ export default function SettingAI(props: IProps) {
{ - setChatGPTConfig({ - ...chatGPTConfig, + setAiConfig({ + ...aiConfig, httpProxyHost: e.target.value, }); }} @@ -114,10 +113,10 @@ export default function SettingAI(props: IProps) {
{ - setChatGPTConfig({ - ...chatGPTConfig, + setAiConfig({ + ...aiConfig, httpProxyPort: e.target.value, }); }} @@ -125,15 +124,15 @@ export default function SettingAI(props: IProps) {
)} - {chatGPTConfig?.aiSqlSource === AiSqlSourceType.AZUREAI && ( + {aiConfig?.aiSqlSource === AiSqlSourceType.AZUREAI && (
Api Key
{ - setChatGPTConfig({ ...chatGPTConfig, azureApiKey: e.target.value }); + setAiConfig({ ...aiConfig, apiKey: e.target.value }); }} />
@@ -141,9 +140,9 @@ export default function SettingAI(props: IProps) {
{ - setChatGPTConfig({ ...chatGPTConfig, azureEndpoint: e.target.value }); + setAiConfig({ ...aiConfig, apiHost: e.target.value }); }} />
@@ -151,28 +150,28 @@ export default function SettingAI(props: IProps) {
{ - setChatGPTConfig({ - ...chatGPTConfig, - azureDeploymentId: e.target.value, + setAiConfig({ + ...aiConfig, + model: e.target.value, }); }} />
)} - {chatGPTConfig?.aiSqlSource === AiSqlSourceType.RESTAI && ( + {aiConfig?.aiSqlSource === AiSqlSourceType.RESTAI && (
{i18n('setting.label.customAiUrl')}
{ - setChatGPTConfig({ - ...chatGPTConfig, - restAiUrl: e.target.value, + setAiConfig({ + ...aiConfig, + apiHost: e.target.value, }); }} /> @@ -181,12 +180,12 @@ export default function SettingAI(props: IProps) {
{ - setChatGPTConfig({ - ...chatGPTConfig, - restAiStream: e.target.value, + setAiConfig({ + ...aiConfig, + stream: e.target.value, }); }} - value={chatGPTConfig.restAiStream} + value={aiConfig.stream} > {i18n('common.text.is')} {i18n('common.text.no')} @@ -195,11 +194,14 @@ export default function SettingAI(props: IProps) {
)}
- */} +
- {/* {chatGPTConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && ( + {/* {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && ( ) } */} diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 63a125d66..df8600437 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -10,61 +10,32 @@ import About from './About'; import { connect } from 'umi'; import { IAIState } from '@/models/ai'; import styles from './index.less'; -import configService, { IChatGPTConfig } from '@/service/config'; +import configService from '@/service/config'; import { AiSqlSourceType } from '@/typings/ai'; import TestVersion from '@/components/TestVersion'; +import { IAiConfig } from '@/typings'; interface IProps { + aiConfig: IAiConfig; className?: string; text?: string; dispatch: Function; } -const initChatGPTConfig = { - chat2dbApiKey: '', - chat2dbApiHost: '', - apiKey: '', - httpProxyHost: '', - httpProxyPort: '', - restAiUrl: '', - apiHost: '', - restAiStream: true, - aiSqlSource: '', - azureApiKey: '', - azureEndpoint: '', - azureDeploymentId: '', -}; + function Setting(props: IProps) { const { className, text, dispatch } = props; const [isModalVisible, setIsModalVisible] = useState(false); - const [chatGPTConfig, setChatGPTConfig] = useState(initChatGPTConfig); const [currentMenu, setCurrentMenu] = useState(0); useEffect(() => { - getChatGptSystemConfig(); + getAiSystemConfig(); }, []); - useEffect(() => { - if (!isModalVisible) { - return; - } - getChatGptSystemConfig(); - }, [isModalVisible]); - - const getChatGptSystemConfig = () => { - configService.getChatGptSystemConfig().then((res: IChatGPTConfig) => { - if (!res) { - return; - } - handleUpdateAiConfig({ - key: res.chat2dbApiHost, - aiType: res.aiSqlSource, - }); - setChatGPTConfig({ - ...res, - restAiStream: res.restAiStream || true, - aiSqlSource: res.aiSqlSource || AiSqlSourceType.CHAT2DBAI, - }); + const getAiSystemConfig = () => { + /** 获取ai相关配置 */ + dispatch({ + type: 'ai/getAiSystemConfig', }); }; @@ -84,18 +55,13 @@ function Setting(props: IProps) { setCurrentMenu(t); } - const handleUpdateAiConfig = (payload: IAIState['keyAndAiType']) => { + const handleApplyAiConfig = (aiConfig: IAiConfig) => { dispatch({ - type: 'ai/setKeyAndAiType', - payload, - }); - dispatch({ - type: 'ai/fetchRemainingUse', - payload: { - key: payload.key, - }, + type: 'ai/setAiSystemConfig', + payload: aiConfig, }); }; + const menusList = [ { label: i18n('setting.nav.basic'), @@ -105,7 +71,7 @@ function Setting(props: IProps) { { label: i18n('setting.nav.customAi'), icon: '\ue646', - body: , + body: , }, { label: i18n('setting.nav.proxy'), @@ -128,7 +94,7 @@ function Setting(props: IProps) { )}
- + ({ - remainingUse: ai.remainingUse, + aiConfig: ai.aiConfig, }))(Setting); diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 45336e15f..1364616d5 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -26,11 +26,25 @@ margin-right: 14px; } + .suffixBlock { display: flex; align-items: center; } +.enterIcon:after { + display: block; + content: '\21B5'; + // transform: translateY(-50%); + font-size: 14px; + color: var(--color-text-quaternary); + line-height: 100%; + text-align: center; + margin-top: 2px; + margin-right: 8px; + +} + .tableSelectBlock { margin-right: 8px; cursor: pointer; diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index e75a2beba..c2327a0ec 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -5,7 +5,7 @@ import { Checkbox, Dropdown, Input, Modal, Popover, Select } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; -import { IRemainingUse } from '@/typings/ai'; +import { AiSqlSourceType, IRemainingUse } from '@/typings/ai'; import { WECHAT_MP_URL } from '@/constants/social'; interface IProps { @@ -14,6 +14,7 @@ interface IProps { tables?: string[]; selectedTables?: string[]; remainingUse?: IRemainingUse; + aiType: AiSqlSourceType; onPressEnter: (value: string) => void; onSelectTables?: (tables: string[]) => void; onClickRemainBtn: Function; @@ -59,19 +60,22 @@ function ChatInput(props: IProps) { const remainCnt = props?.remainingUse?.remainingUses ?? '-'; return (
+
-
{ - props.onClickRemainBtn && props.onClickRemainBtn(); - }} - > - {i18n('chat.input.remain', remainCnt)} -
+ {props.aiType === AiSqlSourceType.CHAT2DBAI && ( +
{ + props.onClickRemainBtn && props.onClickRemainBtn(); + }} + > + {i18n('chat.input.remain', remainCnt)} +
+ )}
); }; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 5fd5eabc6..ea24eba3c 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -6,7 +6,6 @@ import { Button, Spin, message, Drawer, Modal } from 'antd'; import ChatInput from './ChatInput'; import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; import { format } from 'sql-formatter'; -import sqlServer from '@/service/sql'; import historyServer from '@/service/history'; import aiServer from '@/service/ai'; import { v4 as uuidv4 } from 'uuid'; @@ -16,7 +15,7 @@ import { ITreeNode } from '@/typings'; import { IAIState } from '@/models/ai'; import Popularize from '@/components/Popularize'; import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; -import { chatErrorCodeArr, chatErrorToInvite, chatErrorToLogin } from '@/constants/chat'; +import { chatErrorToLogin } from '@/constants/chat'; import { AiSqlSourceType } from '@/typings/ai'; import i18n from '@/i18n'; import styles from './index.less'; @@ -83,7 +82,6 @@ function Console(props: IProps) { appendValue, isActive, hasSaveBtn = true, - value, aiModel, dispatch, source, @@ -104,6 +102,14 @@ function Console(props: IProps) { const timerRef = useRef(); const aiFetchIntervalRef = useRef(); + /** + * 当前选择的AI类型是Chat2DBAi + */ + const isChat2DBAi = useMemo( + () => aiModel.aiConfig.aiSqlSource === AiSqlSourceType.CHAT2DBAI, + [aiModel.aiConfig.aiSqlSource], + ); + useEffect(() => { if (appendValue) { editorRef?.current?.setValue(appendValue.text, appendValue.range); @@ -170,7 +176,7 @@ function Console(props: IProps) { setIsLoading(true); const { wechatQrCodeUrl, token, tip } = await aiServer.getLoginQrCode({}); setIsLoading(false); - // console.log('weiChatConfig', wechatQrCodeUrl, token); + setPopularizeModal(true); setModalProps({ imageUrl: wechatQrCodeUrl, @@ -180,18 +186,19 @@ function Console(props: IProps) { if (shouldPoll) { let pollCnt = 0; aiFetchIntervalRef.current = setInterval(async () => { - const { apiKey } = await aiServer.getLoginStatus({ token }); + const { apiKey } = (await aiServer.getLoginStatus({ token })) || {}; pollCnt++; if (apiKey || pollCnt >= 60) { clearInterval(aiFetchIntervalRef.current); } if (apiKey) { setPopularizeModal(false); + await dispatch({ - type: 'ai/setKeyAndAiType', + type: 'ai/aiConfig', payload: { - key: apiKey, - aiType: AiSqlSourceType.CHAT2DBAI, + ...aiModel.aiConfig, + apiKey, }, }); await dispatch({ @@ -206,8 +213,8 @@ function Console(props: IProps) { }; const handleAiChat = async (content: string, promptType: IPromptType) => { - const { key } = aiModel?.keyAndAiType; - if (!key) { + const { apiKey } = aiModel?.aiConfig; + if (!apiKey && isChat2DBAi) { handleApiKeyEmptyOrGetQrCode(true); return; } @@ -231,18 +238,20 @@ function Console(props: IProps) { const handleMessage = (message: string) => { setIsLoading(false); - + console.log('message', message); try { const isEOF = message === '[DONE]'; if (isEOF) { closeEventSource(); setIsLoading(false); - dispatch({ - type: 'ai/fetchRemainingUse', - payload: { - key, - }, - }); + if (isChat2DBAi) { + dispatch({ + type: 'ai/fetchRemainingUse', + payload: { + key: apiKey, + }, + }); + } if (isNL2SQL) { editorRef?.current?.setValue('\n\n\n'); } else { @@ -254,33 +263,27 @@ function Console(props: IProps) { return; } - // let hasError = false; - // chatErrorCodeArr.forEach((err) => { - // if (message.includes(err)) { - // hasError = true; - // } - // }); let hasErrorToLogin = false; chatErrorToLogin.forEach((err) => { if (message.includes(err)) { hasErrorToLogin = true; } }); - let hasErrorToInvite = false; - chatErrorToInvite.forEach((err) => { - if (message.includes(err)) { - hasErrorToInvite = true; - } - }); - if (hasErrorToLogin || hasErrorToInvite) { + // let hasErrorToInvite = false; + // chatErrorToInvite.forEach((err) => { + // if (message.includes(err)) { + // hasErrorToInvite = true; + // } + // }); + if (hasErrorToLogin) { closeEventSource(); setIsLoading(false); hasErrorToLogin && handleApiKeyEmptyOrGetQrCode(true); - hasErrorToInvite && handleClickRemainBtn(); + // hasErrorToInvite && handleClickRemainBtn(); dispatch({ type: 'ai/fetchRemainingUse', payload: { - key, + key: apiKey, }, }); return; @@ -309,10 +312,6 @@ function Console(props: IProps) { }); }; - const onPressChatInput = (value: string) => { - handleAiChat(value, IPromptType.NL_2_SQL); - }; - const executeSQL = (sql?: string) => { const sqlContent = sql || editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent(); @@ -360,7 +359,7 @@ function Console(props: IProps) { const handleClickRemainBtn = async () => { if ( - !aiModel.keyAndAiType.key || + !aiModel.aiConfig.apiKey || aiModel.remainingUse?.remainingUses === null || aiModel.remainingUse?.remainingUses === undefined ) { @@ -368,12 +367,12 @@ function Console(props: IProps) { return; } - setIsLoading(true); - const { tip, wechatQrCodeUrl } = (await aiServer.getInviteQrCode({})) || {}; - setIsLoading(false); + // setIsLoading(true); + // const { tip, wechatQrCodeUrl } = (await aiServer.getInviteQrCode({})) || {}; + // setIsLoading(false); setModalProps({ - imageUrl: wechatQrCodeUrl, - tip, + // imageUrl: wechatQrCodeUrl, + tip: '测试测试==前往公众号', }); setPopularizeModal(true); }; @@ -383,9 +382,12 @@ function Console(props: IProps) { {hasAiChat && ( { + handleAiChat(value, IPromptType.NL_2_SQL); + }} selectedTables={selectedTables} onSelectTables={(tables: string[]) => { if (tables.length > 8) { @@ -412,7 +414,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index f601028cf..2e4047772 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -1,12 +1,13 @@ import aiService from '@/service/ai'; +import configService from '@/service/config'; +import { IAiConfig } from '@/typings/setting'; import { AiSqlSourceType, IRemainingUse } from '@/typings/ai'; -import { Effect, Reducer } from 'umi'; +import { Effect, EffectsCommandMap, Reducer } from 'umi'; +import { message } from 'antd'; +import i18n from '@/i18n'; export interface IAIState { - keyAndAiType: { - key: string; - aiType: AiSqlSourceType; - }; + aiConfig: IAiConfig; remainingUse?: IRemainingUse; } @@ -15,9 +16,11 @@ export interface IAIModelType { state: IAIState; reducers: { setRemainUse: Reducer; - setKeyAndAiType: Reducer; + setAiConfig: Reducer; }; effects: { + getAiSystemConfig: Effect; + setAiSystemConfig: Effect; fetchRemainingUse: Effect; }; } @@ -26,16 +29,15 @@ const AIModel: IAIModelType = { namespace: 'ai', state: { remainingUse: undefined, - keyAndAiType: { - key: '', - aiType: AiSqlSourceType.CHAT2DB, + aiConfig: { + aiSqlSource: AiSqlSourceType.CHAT2DBAI, }, }, reducers: { - setKeyAndAiType(state, { payload }) { + setAiConfig(state, { payload }) { return { ...state, - keyAndAiType: payload, + aiConfig: payload, }; }, setRemainUse(state, { payload }) { @@ -46,6 +48,42 @@ const AIModel: IAIModelType = { }, }, effects: { + *getAiSystemConfig({}, { put }) { + const res = (yield configService.getAiSystemConfig({})) as IAiConfig; + yield put({ + type: 'setAiConfig', + payload: res, + }); + if (res.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { + const res = (yield aiService.getRemainingUse({})) as IRemainingUse; + yield put({ + type: 'setRemainUse', + payload: res, + }); + } + }, + // *setAiSystemConfig({ payload }: { type: string; payload: { aiConfig: IAiConfig } }, { put }: EffectsCommandMap) { + *setAiSystemConfig({ payload }: { type: any; payload?: IAiConfig }, { put }: EffectsCommandMap) { + const aiConfig = payload; + const { aiSqlSource, apiKey } = aiConfig || {}; + try { + (yield configService.setAiSystemConfig(aiConfig!)) as void; + message.success(i18n('common.text.submittedSuccessfully')); + yield put({ + type: 'setAiConfig', + payload: aiConfig, + }); + + // 如果设置的是Chat2DBAI,需要查询下剩余次数 + if (apiKey && aiSqlSource === AiSqlSourceType.CHAT2DBAI) { + const res = (yield aiService.getRemainingUse({})) as IRemainingUse; + yield put({ + type: 'setRemainUse', + payload: res, + }); + } + } catch (error) {} + }, *fetchRemainingUse({ payload }, { put }) { try { const { key } = payload; diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index c1a6764f7..b628ef5bb 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -1,4 +1,4 @@ -import { AiSqlSourceType } from '@/typings/ai'; +import { IAiConfig } from '@/typings'; import createRequest from './base'; const getSystemConfig = createRequest<{ code: string }, { code: string; content: string }>( '/api/config/system_config/:code', @@ -9,25 +9,11 @@ const setSystemConfig = createRequest<{ code: string; content: string }, void>(' method: 'post', }); -export interface IChatGPTConfig { - chat2dbApiKey: string; - chat2dbApiHost: string; - apiKey: string; - httpProxyHost: string; - httpProxyPort: string; - restAiUrl: string; - apiHost: string; - aiSqlSource: AiSqlSourceType; - restAiStream: boolean; - azureEndpoint: string; - azureApiKey: string; - azureDeploymentId: string; -} -const getChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { +const getAiSystemConfig = createRequest<{ aiSqlSource?: string }, IAiConfig>('/api/config/system_config/ai', { errorLevel: false, }); -const setChatGptSystemConfig = createRequest('/api/config/system_config/chatgpt', { +const setAiSystemConfig = createRequest('/api/config/system_config/ai', { errorLevel: 'toast', method: 'post', }); @@ -35,6 +21,6 @@ const setChatGptSystemConfig = createRequest('/api/config/ export default { getSystemConfig, setSystemConfig, - getChatGptSystemConfig, - setChatGptSystemConfig, + getAiSystemConfig, + setAiSystemConfig, }; diff --git a/chat2db-client/src/typings/ai.ts b/chat2db-client/src/typings/ai.ts index fdb374cb8..9946c6789 100644 --- a/chat2db-client/src/typings/ai.ts +++ b/chat2db-client/src/typings/ai.ts @@ -1,5 +1,4 @@ export enum AiSqlSourceType { - CHAT2DB = 'CHAT2DB', CHAT2DBAI = 'CHAT2DBAI', OPENAI = 'OPENAI', AZUREAI = 'AZUREAI', diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index 124717aea..d2aad71e6 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -4,4 +4,5 @@ export * from './dashboard'; export * from './database'; export * from './main'; export * from './theme'; -export * from './tree'; \ No newline at end of file +export * from './tree'; +export * from './setting' \ No newline at end of file diff --git a/chat2db-client/src/typings/setting.ts b/chat2db-client/src/typings/setting.ts new file mode 100644 index 000000000..5b2da05d4 --- /dev/null +++ b/chat2db-client/src/typings/setting.ts @@ -0,0 +1,11 @@ +import { AiSqlSourceType } from './ai'; + +export interface IAiConfig { + aiSqlSource: AiSqlSourceType; + apiKey?: string; + apiHost?: string; + httpProxyHost?: string; + httpProxyPort?: string; + stream?: boolean; + model?: string; +} From 6a953c0e845f9888693672ad5958d953c253a8a4 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Sat, 29 Jul 2023 16:45:55 +0800 Subject: [PATCH 0477/1069] feat: Optimize AI Execution Pipeline --- .../components/Console/ChatInput/index.tsx | 24 ++++--- .../src/components/Console/index.tsx | 65 ++++++++++++------- chat2db-client/src/constants/chat.ts | 2 +- chat2db-client/src/models/ai.ts | 23 +++---- 4 files changed, 66 insertions(+), 48 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index c2327a0ec..2a2fed003 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; -import { Checkbox, Dropdown, Input, Modal, Popover, Select } from 'antd'; +import { Checkbox, Dropdown, Input, Modal, Popover, Select, Spin } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; @@ -15,6 +15,8 @@ interface IProps { selectedTables?: string[]; remainingUse?: IRemainingUse; aiType: AiSqlSourceType; + remainingBtnLoading: boolean; + disabled?: boolean; onPressEnter: (value: string) => void; onSelectTables?: (tables: string[]) => void; onClickRemainBtn: Function; @@ -67,14 +69,16 @@ function ChatInput(props: IProps) {
{props.aiType === AiSqlSourceType.CHAT2DBAI && ( -
{ - props.onClickRemainBtn && props.onClickRemainBtn(); - }} - > - {i18n('chat.input.remain', remainCnt)} -
+ +
{ + props.onClickRemainBtn && props.onClickRemainBtn(); + }} + > + {i18n('chat.input.remain', remainCnt)} +
+
)}
); @@ -84,10 +88,10 @@ function ChatInput(props: IProps) {
diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index ea24eba3c..3dfba07cb 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -15,7 +15,7 @@ import { ITreeNode } from '@/typings'; import { IAIState } from '@/models/ai'; import Popularize from '@/components/Popularize'; import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; -import { chatErrorToLogin } from '@/constants/chat'; +import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; import { AiSqlSourceType } from '@/typings/ai'; import i18n from '@/i18n'; import styles from './index.less'; @@ -68,6 +68,7 @@ interface IProps { editorOptions?: IEditorOptions; aiModel: IAIState; dispatch: Function; + remainingBtnLoading: boolean; // remainingUse: IAIState['remainingUse']; // onSQLContentChange: (v: string) => void; onExecuteSQL: (sql: string) => void; @@ -195,7 +196,7 @@ function Console(props: IProps) { setPopularizeModal(false); await dispatch({ - type: 'ai/aiConfig', + type: 'ai/setAiConfig', payload: { ...aiModel.aiConfig, apiKey, @@ -204,7 +205,7 @@ function Console(props: IProps) { await dispatch({ type: 'ai/fetchRemainingUse', payload: { - key: apiKey, + apiKey, }, }); } @@ -248,7 +249,7 @@ function Console(props: IProps) { dispatch({ type: 'ai/fetchRemainingUse', payload: { - key: apiKey, + apiKey, }, }); } @@ -269,12 +270,20 @@ function Console(props: IProps) { hasErrorToLogin = true; } }); - // let hasErrorToInvite = false; - // chatErrorToInvite.forEach((err) => { - // if (message.includes(err)) { - // hasErrorToInvite = true; - // } - // }); + let hasKeyLimitedOrExpired = false; + chatErrorForKey.forEach((err) => { + if (message.includes(err)) { + hasKeyLimitedOrExpired = true; + } + }); + + if (hasKeyLimitedOrExpired) { + closeEventSource(); + setIsLoading(false); + handlePopUp(); + return; + } + if (hasErrorToLogin) { closeEventSource(); setIsLoading(false); @@ -283,7 +292,7 @@ function Console(props: IProps) { dispatch({ type: 'ai/fetchRemainingUse', payload: { - key: apiKey, + apiKey, }, }); return; @@ -358,21 +367,28 @@ function Console(props: IProps) { ); const handleClickRemainBtn = async () => { - if ( - !aiModel.aiConfig.apiKey || - aiModel.remainingUse?.remainingUses === null || - aiModel.remainingUse?.remainingUses === undefined - ) { + if (!isChat2DBAi) return; + + // chat2dbAi模型下,没有key,就需要登录 + if (!aiModel.aiConfig.apiKey) { handleApiKeyEmptyOrGetQrCode(true); return; } + handlePopUp(); + }; - // setIsLoading(true); - // const { tip, wechatQrCodeUrl } = (await aiServer.getInviteQrCode({})) || {}; - // setIsLoading(false); + /** + * 弹框 关注公众号 + */ + const handlePopUp = () => { setModalProps({ - // imageUrl: wechatQrCodeUrl, - tip: '测试测试==前往公众号', + imageUrl: 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp', + tip: ( + <> + {aiModel.remainingUse?.remainingUses === 0 &&

Key次数用完或者过期

} +

微信扫描二维码并关注公众号“每天”可以获得 25 次 AI 使用机会。

+ + ), }); setPopularizeModal(true); }; @@ -382,9 +398,11 @@ function Console(props: IProps) { {hasAiChat && ( { handleAiChat(value, IPromptType.NL_2_SQL); }} @@ -464,7 +482,8 @@ function Console(props: IProps) { ); } -const dvaModel = connect(({ ai }: { ai: IAIState }) => ({ +const dvaModel = connect(({ ai, loading }: { ai: IAIState; loading: any }) => ({ aiModel: ai, + remainingBtnLoading: loading.effects['ai/fetchRemainingUse'], })); export default dvaModel(Console); diff --git a/chat2db-client/src/constants/chat.ts b/chat2db-client/src/constants/chat.ts index 637b847aa..3df0a890b 100644 --- a/chat2db-client/src/constants/chat.ts +++ b/chat2db-client/src/constants/chat.ts @@ -14,4 +14,4 @@ export const chatError = { export const chatErrorCodeArr = Object.keys(chatError); export const chatErrorToLogin = ['CHAT2DB_KEY_INVALID', 'CHAT2DB_AUTH_HEADER_MISSING', 'CHAT2DB_AUTH_TOKEN_MISSING']; -export const chatErrorToInvite = ['CHAT2DB_KEY_LIMIT', 'CHAT2DB_KEY_EXPIRED']; +export const chatErrorForKey = ['CHAT2DB_KEY_LIMIT', 'CHAT2DB_KEY_EXPIRED']; diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index 2e4047772..bae49976d 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -55,10 +55,9 @@ const AIModel: IAIModelType = { payload: res, }); if (res.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { - const res = (yield aiService.getRemainingUse({})) as IRemainingUse; yield put({ - type: 'setRemainUse', - payload: res, + type: 'fetchRemainingUse', + payload: { apiKey: res.apiKey }, }); } }, @@ -74,20 +73,16 @@ const AIModel: IAIModelType = { payload: aiConfig, }); - // 如果设置的是Chat2DBAI,需要查询下剩余次数 - if (apiKey && aiSqlSource === AiSqlSourceType.CHAT2DBAI) { - const res = (yield aiService.getRemainingUse({})) as IRemainingUse; - yield put({ - type: 'setRemainUse', - payload: res, - }); - } + yield put({ + type: 'fetchRemainingUse', + payload: { apiKey: aiConfig?.apiKey }, + }); } catch (error) {} }, - *fetchRemainingUse({ payload }, { put }) { + *fetchRemainingUse({ payload }: { type: any; payload?: { apiKey?: string } }, { put }) { + const { apiKey } = payload || {}; try { - const { key } = payload; - if (!key) { + if (!apiKey) { yield put({ type: 'setRemainUse', payload: undefined, From 44ffce084dfb8250e0c9af0e46ac71214d1d1a06 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 29 Jul 2023 16:46:14 +0800 Subject: [PATCH 0478/1069] =?UTF-8?q?fix:=20=E5=88=87=E6=8D=A2=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=BA=93bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/CreateConnection/index.tsx | 5 +- .../src/components/SearchResult/index.tsx | 1 - chat2db-client/src/hooks/useUpdateEffect.ts | 2 +- .../src/pages/main/connection/index.less | 2 + .../src/pages/main/connection/index.tsx | 50 +++-- chat2db-client/src/pages/main/index.tsx | 9 +- .../components/WorkspaceHeader/index.tsx | 197 ++++++++++-------- 7 files changed, 151 insertions(+), 115 deletions(-) diff --git a/chat2db-client/src/components/CreateConnection/index.tsx b/chat2db-client/src/components/CreateConnection/index.tsx index 4f91260e7..6c84c834b 100644 --- a/chat2db-client/src/components/CreateConnection/index.tsx +++ b/chat2db-client/src/components/CreateConnection/index.tsx @@ -176,7 +176,6 @@ export default function CreateConnection(props: IProps) { : i18n('connection.message.testConnectResult', i18n('common.text.successful')), ); } else { - submitCallback?.(); message.success( type === submitType.UPDATE ? i18n('common.message.modifySuccessfully') @@ -189,6 +188,7 @@ export default function CreateConnection(props: IProps) { id: res, }); } + submitCallback?.(); } }) .finally(() => { @@ -284,8 +284,7 @@ function RenderForm(props: IRenderFormProps) { const [dataSourceFormConfig, setDataSourceFormConfig] = useState(dataSourceFormConfigProps); useEffect(() => { - setDataSourceFormConfig(dataSourceFormConfigProps) - console.log(dataSourceFormConfigProps) + setDataSourceFormConfig(dataSourceFormConfigProps); }, [dataSourceFormConfigProps]) const initialValuesMemo = useMemo(() => { diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index e60fb0067..a2bdd626b 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -59,7 +59,6 @@ export default memo(function SearchResult(props) { return; } - // debugger; if (!currentTab || !manageResultDataList.find((d) => d.uuid === currentTab)) { setCurrentTab(manageResultDataList[0].uuid); } diff --git a/chat2db-client/src/hooks/useUpdateEffect.ts b/chat2db-client/src/hooks/useUpdateEffect.ts index a21e2c4c0..85af8aa4e 100644 --- a/chat2db-client/src/hooks/useUpdateEffect.ts +++ b/chat2db-client/src/hooks/useUpdateEffect.ts @@ -1,4 +1,4 @@ -import {useRef, useEffect} from 'react'; +import { useRef, useEffect } from 'react'; export function useUpdateEffect(fn: Function, arr: any[]) { const first = useRef(true); diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index ef9902d19..50ff97497 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -95,10 +95,12 @@ .layoutRight { flex: 1; + overflow: auto; display: flex; justify-content: center; align-items: center; position: relative; + } .dataBaseList { diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 5e668194e..a6ab0244b 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -29,20 +29,10 @@ interface IProps { function Connections(props: IProps) { const { connectionModel, workspaceModel, dispatch } = props; const { connectionList } = connectionModel; + const { curWorkspaceParams } = workspaceModel; const volatileRef = useRef(); - // const [connectionList, setConnectionList] = useState(); const [curConnection, setCurConnection] = useState>({}); - useEffect(() => { - getConnectionList(); - }, []); - - const getConnectionList = () => { - dispatch({ - type: 'connection/fetchConnectionList', - }); - }; - function handleCreateConnections(database: IDatabase) { setCurConnection({ type: database.code, @@ -100,25 +90,42 @@ function Connections(props: IProps) { { key: 'EnterWorkSpace', label: i18n('connection.button.connect'), - onClick: () => { + onClick: ({ domEvent }) => { + domEvent.stopPropagation(); handleMenuItemDoubleClick(t); }, }, { key: 'Delete', label: i18n('common.button.delete'), - onClick: async ({ domEvent }) => { + onClick: ({ domEvent }) => { // 禁止冒泡到menuItem domEvent.stopPropagation(); - await connectionService.remove({ id: key }); - setCurConnection({}); - getConnectionList(); + 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() }}>
@@ -161,7 +168,14 @@ function Connections(props: IProps) { closeCreateConnection={() => { setCurConnection({}); }} - submitCallback={getConnectionList} + submitCallback={() => { + dispatch({ + type: 'connection/fetchConnectionList', + callback: (res: any) => { + setCurConnection(res.data[res.data?.length - 1]); + } + }); + }} /> }
diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 15624f173..56e187320 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -60,9 +60,14 @@ interface IProps { function MainPage(props: IProps) { const { mainModel, workspaceModel, connectionModel, dispatch } = props; const { curPage } = mainModel; - const { curConnection } = connectionModel; const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); + useEffect(() => { + dispatch({ + type: 'connection/fetchConnectionList', + }); + }, []); + useEffect(() => { // activeNav 发生变化,同步到全局状态管理 activeNav.isLoad = true; @@ -99,7 +104,7 @@ function MainPage(props: IProps) { return (
- {}} className={styles.brandLogo} /> + { }} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( 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 a97dea115..b0b4722e3 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -1,16 +1,16 @@ import React, { memo, useEffect, useMemo, useState } from 'react'; +import classnames from 'classnames'; import { connect } from 'umi'; +import lodash from 'lodash'; +import Iconfont from '@/components/Iconfont'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; -import styles from './index.less'; -import classnames from 'classnames'; import { Cascader, Spin, Modal, Button } from 'antd'; -import Iconfont from '@/components/Iconfont'; import { databaseMap, TreeNodeType } from '@/constants'; import { treeConfig } from '../Tree/treeConfig'; -import lodash from 'lodash'; - +import { useUpdateEffect } from '@/hooks/useUpdateEffect' +import styles from './index.less'; interface IProps { className?: string; @@ -32,17 +32,29 @@ const WorkspaceHeader = memo((props) => { const { curPage } = mainPageModel; const [cascaderLoading, setCascaderLoading] = useState(false); const [noConnectionModal, setNoConnectionModal] = useState(false); + const [connectionOptions, setConnectionOptions] = useState([]); const [curDBOptions, setCurDBOptions] = useState([]); const [curSchemaOptions, setCurSchemaOptions] = useState([]); - const [connectionOptions, setConnectionOptions] = useState([]); - // 第一次进入请求连接列表 useEffect(() => { - getConnectionList(); - }, []); + if (curPage !== 'workspace') { + return + } + // 如果没有curConnection默认选第一个 + if (!curConnection?.id && connectionList.length) { + connectionChange([connectionList[0].id], [connectionList[0]]); + return + } + // 如果都有的话 + if (curConnection?.id && connectionList.length) { + // 如果curConnection不再connectionList里,也是默认选第一个 + const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id) + if (flag === -1) { + connectionChange([connectionList[0].id], [connectionList[0]]); + return + } - useEffect(() => { - if (curConnection?.id) { + // 如果切换了curConnection 导致curWorkspaceParams与curConnection不同 if (curWorkspaceParams.dataSourceId !== curConnection?.id) { setCurWorkspaceParams({ dataSourceId: curConnection.id, @@ -52,11 +64,34 @@ const WorkspaceHeader = memo((props) => { setCurDBOptions([]) setCurSchemaOptions([]) } + + // 获取database列表 getDatabaseList(false); } - }, [curConnection]); + }, [connectionList, curConnection, curPage]) + + useUpdateEffect(() => { + // connectionList转换成可用的ConnectionOptions + setConnectionOptions(connectionList?.map(t => { + return { + value: t.id, + label: t.alias + } + })); + if (!connectionList.length) { + dispatch({ + type: 'workspace/setCurWorkspaceParams', + payload: {} + }) + dispatch({ + type: 'connection/setCurConnection', + payload: {} + }) + } + }, [connectionList]) function getDatabaseList(refresh = false) { + setCascaderLoading(true); if (!curConnection?.id) { return } @@ -76,7 +111,8 @@ const WorkspaceHeader = memo((props) => { } }) || [] setCurDBOptions(dbList); - const databaseName = curWorkspaceParams.dataSourceId !== curConnection?.id ? dbList[0]?.label : curWorkspaceParams.databaseName + // 如果是切换那么就默认取列表的第一个database, 如果不是切换那么就取缓存的,如果缓存没有还是取列表第一个(这里是兜底,如果原先他并没有database,后来他加了database,如果还是取缓存的空就不对了) + const databaseName = curWorkspaceParams.dataSourceId !== curConnection?.id ? dbList[0]?.label : curWorkspaceParams.databaseName || dbList[0]?.label getSchemaList(databaseName, refresh); }).catch(error => { setCascaderLoading(false); @@ -105,7 +141,7 @@ const WorkspaceHeader = memo((props) => { } }) || [] setCurSchemaOptions(schemaList); - const schemaName = curWorkspaceParams.dataSourceId !== curConnection?.id ? schemaList[0]?.label : curWorkspaceParams.schemaName + const schemaName = curWorkspaceParams.dataSourceId !== curConnection?.id ? schemaList[0]?.label : curWorkspaceParams.schemaName || schemaList[0]?.label const data: any = { dataSourceId: curConnection.id, dataSourceName: curConnection.alias, @@ -128,19 +164,6 @@ const WorkspaceHeader = memo((props) => { }) } - useEffect(() => { - if (!curConnection && connectionList.length) { - connectionChange([connectionList[0].id], [connectionList[0]]); - } - const list = connectionList?.map(t => { - return { - value: t.id, - label: t.alias - } - }) - setConnectionOptions(list); - }, [connectionList]) - function setCurWorkspaceParams(payload: IWorkspaceModelType['state']['curWorkspaceParams']) { if (lodash.isEqual(curWorkspaceParams, payload)) { return @@ -152,29 +175,15 @@ const WorkspaceHeader = memo((props) => { }); } - const getConnectionList = (refresh = false) => { + const getConnectionList = () => { setCascaderLoading(true); dispatch({ type: 'connection/fetchConnectionList', payload: { - refresh + refresh: true }, callback: (res: any) => { - if (refresh) { - getDatabaseList(true); - return - } - if (curPage === 'workspace' && !res.data?.length) { - setNoConnectionModal(true); - return - } - setNoConnectionModal(false); - if (curConnection?.id && res.data.length) { - const flag = res.data.findIndex((t: any) => t.id === curConnection?.id) - if (flag === -1) { - connectionChange([res.data[0].id], [res.data[0]]); - } - } + getDatabaseList(true); } }); }; @@ -182,7 +191,7 @@ const WorkspaceHeader = memo((props) => { // 连接切换 function connectionChange(id: any, data: any) { connectionList.map(t => { - if (t.id === id[0]) { + if (t.id === id[0] && curWorkspaceParams.dataSourceId !== id[0]) { dispatch({ type: 'connection/setCurConnection', payload: t @@ -193,68 +202,75 @@ const WorkspaceHeader = memo((props) => { // 数据库切换 function databaseChange(valueArr: any, selectedOptions: any) { - getSchemaList(selectedOptions[0].label); + if (selectedOptions[0].label !== curWorkspaceParams.databaseName) { + getSchemaList(selectedOptions[0].label); + } }; // schema切换 function schemaChange(valueArr: any, selectedOptions: any) { - setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value }) + if (selectedOptions[0].label !== curWorkspaceParams.schemaName) { + setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value }) + } } function handelRefresh() { - getConnectionList(true); + getConnectionList(); } return <> - {
    - -
    -
    {curWorkspaceParams.dataSourceName}
    - -
    -
    - - { - !!curDBOptions?.length && + { + !!connectionList.length && +
    -
    {curWorkspaceParams.databaseName}
    - { - !!curSchemaOptions.length && - } +
    {curWorkspaceParams.dataSourceName}
    +
    - } - { - !!curSchemaOptions.length && - -
    -
    {curWorkspaceParams.schemaName}
    -
    -
    - } -
    - {cascaderLoading ? : } -
    -
    } + + { + !!curDBOptions?.length && + +
    +
    {curWorkspaceParams.databaseName}
    + { + !!curSchemaOptions.length && + } +
    +
    + } + { + !!curSchemaOptions.length && + +
    +
    {curWorkspaceParams.schemaName}
    +
    +
    + } +
    + {cascaderLoading ? : } +
    +
    + } } @@ -268,6 +284,7 @@ const WorkspaceHeader = memo((props) => { 您当前还没有创建任何连接
- {/* {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && ( - - ) - } */} + + {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && } ); } diff --git a/chat2db-client/src/components/Popularize/index.tsx b/chat2db-client/src/components/Popularize/index.tsx index 412250fd3..9d631a524 100644 --- a/chat2db-client/src/components/Popularize/index.tsx +++ b/chat2db-client/src/components/Popularize/index.tsx @@ -9,7 +9,8 @@ interface IProps { imageUrl?: string; tip?: string; } -const url = 'https://oss-chat2db.alibaba.com/static/wechat.webp'; +const url = + 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp'; export default memo(function Popularize(props) { const { className } = props; @@ -19,7 +20,7 @@ export default memo(function Popularize(props) { } let dom; if (props.source === 'setting') { - dom =

{i18n('common.text.wechatPopularizeAi2')}

; + dom =

{'关注公众号获取AI Key'}

; } else { dom =

{i18n('common.text.wechatPopularizeAi')}

; } diff --git a/chat2db-client/src/components/SearchResult/Pagination.less b/chat2db-client/src/components/SearchResult/Pagination/index.less similarity index 100% rename from chat2db-client/src/components/SearchResult/Pagination.less rename to chat2db-client/src/components/SearchResult/Pagination/index.less diff --git a/chat2db-client/src/components/SearchResult/Pagination.tsx b/chat2db-client/src/components/SearchResult/Pagination/index.tsx similarity index 99% rename from chat2db-client/src/components/SearchResult/Pagination.tsx rename to chat2db-client/src/components/SearchResult/Pagination/index.tsx index a50f99cf5..e77a8e338 100644 --- a/chat2db-client/src/components/SearchResult/Pagination.tsx +++ b/chat2db-client/src/components/SearchResult/Pagination/index.tsx @@ -5,7 +5,7 @@ import { Button, InputNumber, Popover, Select } from 'antd'; import { IResultConfig } from '@/typings'; import i18n from '@/i18n'; import _ from 'lodash'; -import styles from './Pagination.less'; +import styles from './index.less'; interface IProps { onPageSizeChange?: (pageSize: number) => void; diff --git a/chat2db-client/src/components/SearchResult/TableBox.less b/chat2db-client/src/components/SearchResult/TableBox/index.less similarity index 98% rename from chat2db-client/src/components/SearchResult/TableBox.less rename to chat2db-client/src/components/SearchResult/TableBox/index.less index 5a26aa15c..beb42c25c 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -1,4 +1,4 @@ -@import '../../styles/var.less'; +@import '../../../styles/var.less'; .tableBox { position: absolute; diff --git a/chat2db-client/src/components/SearchResult/TableBox.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx similarity index 91% rename from chat2db-client/src/components/SearchResult/TableBox.tsx rename to chat2db-client/src/components/SearchResult/TableBox/index.tsx index f068076cb..18fb3d76f 100644 --- a/chat2db-client/src/components/SearchResult/TableBox.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -1,21 +1,20 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { TableDataType } from '@/constants/table'; -import { IManageResultData, IResultConfig, ITableHeaderItem } from '@/typings/database'; +import { IManageResultData, IResultConfig } from '@/typings/database'; import { formatDate } from '@/utils/date'; -import { Button, message, Modal, Pagination, Select, Table } from 'antd'; -import antd from 'antd'; -import { BaseTable, ArtColumn, useTablePipeline, features, SortItem, BaseTableProps } from 'ali-react-table'; -import Iconfont from '../Iconfont'; +import { Button, message, Modal } from 'antd'; +import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; +import Iconfont from '../../Iconfont'; import classnames from 'classnames'; -import StateIndicator from '../StateIndicator'; -import MonacoEditor from '../Console/MonacoEditor'; +import StateIndicator from '../../StateIndicator'; +import MonacoEditor from '../../Console/MonacoEditor'; import { useTheme } from '@/hooks/useTheme'; import styled from 'styled-components'; -import styles from './TableBox.less'; import { ThemeType } from '@/constants'; import i18n from '@/i18n'; import { compareStrings } from '@/utils/sort'; -import MyPagination from './Pagination'; +import MyPagination from '../Pagination'; +import styles from './index.less'; interface ITableProps { className?: string; @@ -189,6 +188,16 @@ export default function TableBox(props: ITableProps) { onClickTotalBtn={onClickTotalBtn} />
+ {/*
+ +
*/}
('/api/pin/tabl /** 获取当前执行SQL 所有行 */ const getDMLCount = createRequest('/api/rdb/dml/count', { method: 'post' }); +export interface IExportParams extends IExecuteSqlParams { + originalSql: string; + exportType: ExportTypeEnum; + exportSize: ExportSizeEnum; +} +/** + * 导出-表格 + */ +const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); + export default { getList, executeSql, @@ -131,4 +142,5 @@ export default { addTablePin, deleteTablePin, getDMLCount, + exportResultTable }; diff --git a/chat2db-client/src/typings/resultTable.ts b/chat2db-client/src/typings/resultTable.ts new file mode 100644 index 000000000..5d5c5af19 --- /dev/null +++ b/chat2db-client/src/typings/resultTable.ts @@ -0,0 +1,10 @@ +import { IExecuteSqlParams } from '@/service/sql'; + +export enum ExportTypeEnum { + CSV = 'CSV', + INSERT = 'INSERT', +} +export enum ExportSizeEnum { + CURRENT_PAGE = 'CURRENT_PAGE', + ALL = 'ALL', +} From f01e47a56da6a901125f9c044da4b1ce177fe68e Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 29 Jul 2023 19:27:12 +0800 Subject: [PATCH 0485/1069] ai config update --- .../server/web/api/controller/ai/ChatController.java | 7 ++++++- .../ai/listener/AzureOpenAIEventSourceListener.java | 5 ----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 6a2c65273..012f561fa 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -367,7 +367,12 @@ private Map> buildTableColumn(TableQueryParam tableQue if (CollectionUtils.isEmpty(tableNames)) { return Maps.newHashMap(); } - List tableColumns = tableService.queryColumns(tableQueryParam); + List tableColumns = Lists.newArrayList(); + try { + tableColumns = tableService.queryColumns(tableQueryParam); + } catch (Exception exception) { + log.error("query table error, do nothing"); + } if (CollectionUtils.isEmpty(tableColumns)) { return Maps.newHashMap(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java index ceccaf957..81d9e329a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java @@ -110,11 +110,6 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); - if ("No route to host".equals(message)) { - message = "网络连接超时,请检查网络连通性,参考文章"; - } else { - message = "Azure AI无法正常访问,请参考文章进行配置"; - } Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() From 81a7cc9697fcb8a79363cd02bf482afe0fdcf786 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 29 Jul 2023 19:30:31 +0800 Subject: [PATCH 0486/1069] Completing CSV export --- .../chat2db/server/start/log/EasyLogSink.java | 8 +- .../start/test/druid/SqlUtilsTest2.java | 59 ++++++++++ .../server/tools/common/util/LogUtils.java | 2 +- .../rdb/RdbDmlExportController.java | 109 +++++++++++++----- .../java/ai/chat2db/spi/util/SqlUtils.java | 31 +++++ chat2db-server/pom.xml | 2 +- 6 files changed, 178 insertions(+), 33 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java index dff6aa8a7..268edb65b 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; +import org.thymeleaf.util.ContentTypeUtils; import org.zalando.logbook.Correlation; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; @@ -54,7 +55,12 @@ public void printLog(final Correlation correlation, final HttpRequest request, f webLog.setEndTime(LocalDateTime.ofInstant(correlation.getEnd(), ZoneId.systemDefault())); try { webLog.setRequest(LogUtils.cutLog(new String(request.getBody(), StandardCharsets.UTF_8))); - webLog.setResponse(LogUtils.cutLog(new String(response.getBody(), StandardCharsets.UTF_8))); + if (ContentTypeUtils.isContentTypeJSON(response.getContentType()) || ContentTypeUtils.isContentTypeHTML( + response.getContentType())) { + webLog.setResponse(LogUtils.cutLog(new String(response.getBody(), StandardCharsets.UTF_8))); + } else { + webLog.setResponse(response.getContentType() + ":[" + response.getBody().length + "]"); + } } catch (IOException e) { log.warn("获取日志的请求&返回异常,大概率是用户关闭了流。", e); } diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest2.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest2.java index 6efbd3a8b..7ef4e6c95 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest2.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/druid/SqlUtilsTest2.java @@ -3,7 +3,12 @@ import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; +import ai.chat2db.server.tools.base.excption.BusinessException; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @@ -17,4 +22,58 @@ public void coment() { DbType.h2); log.info("解析sql:{}", sqlStatement); } + + @Test + public void SELECT() { + SQLStatement sqlStatement = SQLUtils.parseSingleStatement( + "SELECT * FROM score a left join user b on a.id=b.id LIMIT 10", + DbType.mysql); + + if (!(sqlStatement instanceof SQLSelectStatement)) { + throw new BusinessException("dataSource.sqlAnalysisError"); + } + SQLSelectStatement sqlSelectStatement = (SQLSelectStatement)sqlStatement; + log.info("解析sql1:{}", sqlSelectStatement); + + log.info("解析sql2:{}", sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom().toString()); + } + + @Test + public void select2() { + log.info("tablename:{}",getTable("SELECT * FROM score LIMIT 10")); + log.info("tablename:{}",getTable("SELECT * FROM score a LIMIT 10")); + log.info("tablename:{}",getTable("SELECT * FROM score a left join user b on a.id=b.id LIMIT 10")); + } + + + @Test + public void insert() { + SQLStatement sqlStatement = SQLUtils.parseSingleStatement("INSERT INTO chat2db.`order` (id, user_id, total_price, created_at, updated_at) VALUES (8, 345, 5601.16, '2022-09-18 11:21:12', '2023-04-30 11:21:12');", + DbType.mysql); + log.info("解析sql1:{}", sqlStatement); + + } + + private String getTable(String sql) { + SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, + DbType.mysql); + + if (!(sqlStatement instanceof SQLSelectStatement)) { + throw new BusinessException("dataSource.sqlAnalysisError"); + } + SQLSelectStatement sqlSelectStatement = (SQLSelectStatement)sqlStatement; + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource)getSQLExprTableSource( + sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); + return sqlExprTableSource.getTableName(); + } + + private SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSource) { + if (sqlTableSource instanceof SQLExprTableSource sqlExprTableSource) { + return sqlExprTableSource; + } else if (sqlTableSource instanceof SQLJoinTableSource sqlJoinTableSource) { + return getSQLExprTableSource(sqlJoinTableSource.getLeft()); + } + return null; + } + } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java index 0665abda8..14cd284a1 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java @@ -68,7 +68,7 @@ public static String cutLog(Object log) { * @return */ public static String generateTraceId() { - String traceId = UUID.fastUUID().toString(); + String traceId = UUID.fastUUID().toString().replaceAll("-", ""); TRACE_ID_THREAD_LOCAL.set(traceId); return traceId; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java index e3345ae74..6df86c19b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java @@ -1,28 +1,41 @@ package ai.chat2db.server.web.api.controller.rdb; import java.io.IOException; +import java.io.PrintWriter; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; import java.util.List; +import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; +import com.alibaba.druid.sql.SQLUtils.FormatOption; +import com.alibaba.druid.sql.ast.SQLStatement; +import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; +import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement; +import com.alibaba.druid.sql.ast.statement.SQLInsertStatement.ValuesClause; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.visitor.VisitorFeature; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.support.ExcelTypeEnum; -import com.alibaba.excel.util.BeanMapUtils; import com.alibaba.excel.write.builder.ExcelWriterBuilder; import com.alibaba.excel.write.metadata.WriteSheet; import ai.chat2db.server.domain.api.enums.ExportSizeEnum; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; -import ai.chat2db.server.domain.api.service.DlTemplateService; +import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.JdbcUtils; +import ai.chat2db.spi.util.SqlUtils; +import cn.hutool.core.date.DatePattern; import com.google.common.collect.Lists; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; @@ -32,8 +45,6 @@ import lombok.experimental.SuperBuilder; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cglib.beans.BeanMap; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -50,11 +61,14 @@ @Slf4j public class RdbDmlExportController { - @Autowired - private RdbWebConverter rdbWebConverter; + /** + * Format insert statement + */ + private static final FormatOption INSERT_FORMAT_OPTION = new FormatOption(true, false); - @Autowired - private DlTemplateService dlTemplateService; + static { + INSERT_FORMAT_OPTION.config(VisitorFeature.OutputNameQuote, true); + } /** * export data @@ -66,20 +80,6 @@ public class RdbDmlExportController { public void export(@Valid @RequestBody DataExportRequest request, HttpServletResponse response) throws IOException { ExportSizeEnum exportSize = EasyEnumUtils.getEnum(ExportSizeEnum.class, request.getExportSize()); ExportTypeEnum exportType = EasyEnumUtils.getEnum(ExportTypeEnum.class, request.getExportType()); - BeanMap beanMap = BeanMap.create(request); - log.info("x:{}", beanMap.get("sql")); - - com.alibaba.excel.support.cglib.beans.BeanMap beanMap2 = BeanMapUtils.create(request); - log.info("te:{}", beanMap2.get("sql")); - - if (exportType == ExportTypeEnum.CSV) { - doExportCsv(exportSize, request, response); - } - - } - - private void doExportCsv(ExportSizeEnum exportSize, DataExportRequest request, HttpServletResponse response) - throws IOException { String sql; if (exportSize == ExportSizeEnum.CURRENT_PAGE) { sql = request.getSql(); @@ -89,15 +89,29 @@ private void doExportCsv(ExportSizeEnum exportSize, DataExportRequest request, H if (StringUtils.isBlank(sql)) { throw new ParamBusinessException("exportSize"); } - // - //ublic void download(HttpServletResponse response) throws IOException { - // // 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postman - response.setContentType("text/csv"); + DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); + SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + if (!(sqlStatement instanceof SQLSelectStatement)) { + throw new BusinessException("dataSource.sqlAnalysisError"); + } + String tableName = SqlUtils.getTableName(sql, dbType); response.setCharacterEncoding("utf-8"); - // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系 - String fileName = URLEncoder.encode("测试", StandardCharsets.UTF_8).replaceAll("\\+", "%20"); + String fileName = URLEncoder.encode( + tableName + "-" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"); + + if (exportType == ExportTypeEnum.CSV) { + doExportCsv(sql, response, fileName); + } else { + doExportInsert(sql, response, fileName, dbType, tableName); + } + } + + private void doExportCsv(String sql, HttpServletResponse response, String fileName) + throws IOException { + response.setContentType("text/csv"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".csv"); - // EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data()); ExcelWrapper excelWrapper = new ExcelWrapper(); try { @@ -121,6 +135,41 @@ private void doExportCsv(ExportSizeEnum exportSize, DataExportRequest request, H } } + private void doExportInsert(String sql, HttpServletResponse response, String fileName, DbType dbType, + String tableName) + throws IOException { + response.setContentType("text/sql"); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".sql"); + + try (PrintWriter printWriter = response.getWriter()) { + InsertWrapper insertWrapper = new InsertWrapper(); + SQLExecutor.getInstance().executeSql(Chat2DBContext.getConnection(), sql, + headerList -> insertWrapper.setHeaderList( + EasyCollectionUtils.toList(headerList, header -> new SQLIdentifierExpr(header.getName()))) + , dataList -> { + SQLInsertStatement sqlInsertStatement = new SQLInsertStatement(); + sqlInsertStatement.setDbType(dbType); + sqlInsertStatement.setTableSource(new SQLExprTableSource(tableName)); + sqlInsertStatement.getColumns().addAll(insertWrapper.getHeaderList()); + ValuesClause valuesClause = new ValuesClause(); + for (String s : dataList) { + valuesClause.addValue(s); + } + sqlInsertStatement.setValues(valuesClause); + + printWriter.println(SQLUtils.toSQLString(sqlInsertStatement, dbType, INSERT_FORMAT_OPTION) + ";"); + }); + } + } + + @Data + @SuperBuilder + @NoArgsConstructor + @AllArgsConstructor + public static class InsertWrapper { + private List headerList; + } + @Data @SuperBuilder @NoArgsConstructor diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index 2d39bbfb0..cea57cafe 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -9,7 +9,9 @@ import java.util.stream.Collectors; import com.alibaba.druid.DbType; +import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; +import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.expr.SQLCharExpr; import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddColumn; @@ -23,9 +25,12 @@ import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement; import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; +import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint; import com.alibaba.druid.sql.ast.statement.SQLNullConstraint; import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; +import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; +import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey; import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique; import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr; @@ -36,6 +41,7 @@ import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex; +import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.util.EasyBooleanUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; @@ -56,6 +62,8 @@ */ public class SqlUtils { + public static final String DEFAULT_TABLE_NAME = "table1"; + public static List buildSql(Table oldTable, Table newTable) { List sqlList = new ArrayList<>(); // 创建表 @@ -430,4 +438,27 @@ private static void modifyIndex(List sqlList, Table oldTable, Table newTabl public static String formatSQLString(Object para) { return para != null ? " '" + para + "' " : null; } + + public static String getTableName(String sql, DbType dbType) { + SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + if (!(sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { + throw new BusinessException("dataSource.sqlAnalysisError"); + } + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource)getSQLExprTableSource( + sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); + if (sqlExprTableSource == null) { + return DEFAULT_TABLE_NAME; + } + return sqlExprTableSource.getTableName(); + } + + private static SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSource) { + if (sqlTableSource instanceof SQLExprTableSource sqlExprTableSource) { + return sqlExprTableSource; + } else if (sqlTableSource instanceof SQLJoinTableSource sqlJoinTableSource) { + return getSQLExprTableSource(sqlJoinTableSource.getLeft()); + } + return null; + } + } \ No newline at end of file diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 5dbd91993..cde1ae980 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -219,7 +219,7 @@ org.zalando logbook-spring-boot-starter - 3.1.0 + 3.3.0 From 5426dee4eec8a61702e9014c0fbeaa5b58bc2148 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 29 Jul 2023 19:52:20 +0800 Subject: [PATCH 0487/1069] Modify abnormal output --- .../ai/chat2db/spi/util/ExceptionUtils.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java index 085fd9a5b..dc3c20a7a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ExceptionUtils.java @@ -3,9 +3,12 @@ import java.io.PrintWriter; import java.io.StringWriter; +import lombok.extern.slf4j.Slf4j; + /** * exception utils */ +@Slf4j public class ExceptionUtils { /** @@ -15,17 +18,12 @@ public class ExceptionUtils { * @return */ public static String getErrorInfoFromException(Throwable throwable) { - String errorDetail = ""; - try { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - throwable.printStackTrace(pw); - errorDetail = " \r\n " + sw.toString() + " \r\n "; - sw.close(); - pw.close(); - } catch (Exception e2) { + try (StringWriter stringWriter = new StringWriter(); PrintWriter printWriter = new PrintWriter(stringWriter)) { + throwable.printStackTrace(printWriter); + return stringWriter.toString(); + } catch (Exception e) { + log.error("ErrorInfoFromException", e); return "ErrorInfoFromException"; } - return errorDetail; } } From 9c1e79f4cc6292d1086edfa3a1a19d559481fde6 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 29 Jul 2023 19:56:33 +0800 Subject: [PATCH 0488/1069] Modify abnormal output --- .../server/web/api/controller/rdb/RdbDmlExportController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java index 6df86c19b..f09d69402 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java @@ -97,7 +97,7 @@ public void export(@Valid @RequestBody DataExportRequest request, HttpServletRes String tableName = SqlUtils.getTableName(sql, dbType); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode( - tableName + "-" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), + tableName + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), StandardCharsets.UTF_8) .replaceAll("\\+", "%20"); From 5f3abbd4d5501d4ee4c4a279a45a4a801d2501a3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 29 Jul 2023 20:39:56 +0800 Subject: [PATCH 0489/1069] =?UTF-8?q?fix:error=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Console/index.tsx | 14 +++---- .../src/components/MyNotification/index.less | 39 ++++++++++++++++++ .../src/components/MyNotification/index.tsx | 40 ++++++++++++++++--- chat2db-client/src/i18n/en-us/common.ts | 2 + chat2db-client/src/i18n/zh-cn/common.ts | 2 + chat2db-client/src/models/ai.ts | 8 ++-- .../main/workspace/components/Tree/index.less | 12 +++++- .../main/workspace/components/Tree/index.tsx | 5 +++ chat2db-client/src/service/base.ts | 2 + chat2db-client/src/utils/index.ts | 20 ++++++++++ 10 files changed, 126 insertions(+), 18 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 6510814ed..5d2bf248f 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -107,8 +107,8 @@ function Console(props: IProps) { * 当前选择的AI类型是Chat2DBAi */ const isChat2DBAi = useMemo( - () => aiModel.aiConfig.aiSqlSource === AiSqlSourceType.CHAT2DBAI, - [aiModel.aiConfig.aiSqlSource], + () => aiModel.aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI, + [aiModel.aiConfig?.aiSqlSource], ); useEffect(() => { @@ -198,7 +198,7 @@ function Console(props: IProps) { await dispatch({ type: 'ai/setAiConfig', payload: { - ...aiModel.aiConfig, + ...(aiModel.aiConfig || {}), apiKey, }, }); @@ -214,7 +214,7 @@ function Console(props: IProps) { }; const handleAiChat = async (content: string, promptType: IPromptType) => { - const { apiKey } = aiModel?.aiConfig; + const { apiKey } = aiModel?.aiConfig || {}; if (!apiKey && isChat2DBAi) { handleApiKeyEmptyOrGetQrCode(true); return; @@ -370,7 +370,7 @@ function Console(props: IProps) { if (!isChat2DBAi) return; // chat2dbAi模型下,没有key,就需要登录 - if (!aiModel.aiConfig.apiKey) { + if (!aiModel.aiConfig?.apiKey) { handleApiKeyEmptyOrGetQrCode(true); return; } @@ -399,7 +399,7 @@ function Console(props: IProps) { {hasAiChat && ( {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/components/MyNotification/index.less b/chat2db-client/src/components/MyNotification/index.less index b889ff100..d8f406d27 100644 --- a/chat2db-client/src/components/MyNotification/index.less +++ b/chat2db-client/src/components/MyNotification/index.less @@ -11,7 +11,19 @@ } .modal{ + position: relative; + h3{ + background-color: #fff; + position: sticky; + top: 0; + } :global{ + .ant-modal-header{ + padding: 8px 10px; + } + .ant-modal-body{ + padding: 0px 10px 10px; + } .ant-modal-content{ max-height: 80vh; display: flex; @@ -28,6 +40,29 @@ } } +.modalTitle{ + display: flex; + align-items: center; + + i{ + font-size: 16px ; + font-weight: 400; + color: var(--color-error-text); + margin-right: 10px; + } +} + +.modalFooter{ + text-align: start; + cursor: pointer; + &:hover{ + color: var(--color-primary) + } + .copyErrorTips{ + color: var(--color-error-text); + } +} + .description { .f-lines(2); word-break: break-all; @@ -46,3 +81,7 @@ } } } + +.errorDetail{ + white-space: pre; +} diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index b268ae065..69670f5e2 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -1,9 +1,10 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Button, ConfigProvider, Modal, notification, Space } from 'antd'; -import styles from './index.less'; +import { Button, ConfigProvider, message, Modal, notification, Space, Tooltip } from 'antd'; import i18n from '@/i18n'; import { IconType } from 'antd/es/notification/interface'; import Iconfont from '../Iconfont'; +import { copy, getApplicationMessage } from '@/utils' +import styles from './index.less'; interface IProps { type?: IconType; @@ -16,6 +17,10 @@ interface IProps { errorDetail: string; /** 问题wiki路径 */ solutionLink: string; + /** 请求的接口 */ + requestUrl: string; + /** 请求的参数 */ + requestParams: string; } function MyNotification() { @@ -68,24 +73,47 @@ function MyNotification() { description: renderDescription(), placement: 'bottomRight', btn, - duration: null, }); }, []); + function renderModalTitle() { + return
+ {`${props?.errorCode}:${props?.errorMessage} `} +
+ } + + function copyError() { + const errorMessage = { + getApplicationMessage: getApplicationMessage(), + ...props, + } + copy(JSON.stringify(errorMessage)) + message.success(i18n('common.button.copySuccessfully')) + } + + function renderModalFooter() { + return
+ {i18n('common.button.copyError')} + {i18n('common.button.copyErrorTips')} +
+ } + return ( <> {notificationDom} { setOpen(false); }} > - {props?.errorDetail} +
+ {props?.errorDetail} +
); diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index d32237c37..d3bf5ef0e 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -63,4 +63,6 @@ export default { 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', 'common.notification.detail': 'More details', 'common.notification.solution': 'Solution', + 'common.button.copyError': 'Copy error report', + 'common.button.copyErrorTips': '(The interface information and detailed parameters will be copied here. If there are sensitive parameters, please parse JSON first and then send them)', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index fddd07b02..021c3fdb8 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -62,4 +62,6 @@ export default { 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', 'common.notification.detail': '查看详情', 'common.notification.solution': '解决办法', + 'common.button.copyError': '复制错误报告', + 'common.button.copyErrorTips': '(这里会复制接口信息以及详细参数,如有敏感参数,请先解析JSON处理后在发送)', }; diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index 55cdb569c..9796cde6a 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -48,13 +48,13 @@ const AIModel: IAIModelType = { }, }, effects: { - *getAiSystemConfig({}, { put }) { + *getAiSystemConfig({ }, { put }) { const res = (yield configService.getAiSystemConfig({})) as IAiConfig; yield put({ type: 'setAiConfig', payload: res, }); - if (res.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { + if (res?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { yield put({ type: 'fetchRemainingUse', payload: { apiKey: res.apiKey }, @@ -77,7 +77,7 @@ const AIModel: IAIModelType = { type: 'fetchRemainingUse', payload: { apiKey: aiConfig?.apiKey }, }); - } catch (error) {} + } catch (error) { } }, *fetchRemainingUse({ payload }: { type: any; payload?: { apiKey?: string } }, { put, select }) { const currentState = (yield select((state: any) => state.ai)) as IAIState; @@ -96,7 +96,7 @@ const AIModel: IAIModelType = { payload: res, }); return res; - } catch {} + } catch { } }, }, }; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/index.less index 286ad0d14..47e1a8f77 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.less @@ -98,15 +98,17 @@ } .contentText { + width: 0; flex: 1; display: flex; justify-content: space-between; align-items: center; } + .name { - flex: 1; .f-lines(1); } + .type { font-size: 12px; flex-shrink: 0; @@ -114,6 +116,14 @@ color: var(--color-primary); } +.describe{ + flex: 1; + font-size: 10px; + margin-left: 20px; + color: var(--color-text-tertiary); + .f-single-line(); +} + .indent { width: 20px; height: 100%; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index 48c573122..be728293f 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -209,6 +209,11 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => {
{data.treeNodeType === TreeNodeType.COLUMN &&
{data.columnType}
} + {/* {data.treeNodeType === TreeNodeType.TABLE && + +
我是描述呀我是描述呀我是描述呀我是描述呀我是描述呀我是描述呀
+
+ } */}
diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index f4b02126c..a644a72a0 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -174,6 +174,8 @@ export default function createRequest

(url: string, options?: I if (!success && errorLevel === 'toast' && !noNeedToastErrorCode.includes(errorCode)) { delayTimeFn(() => { window._notificationApi({ + requestUrl: eventualUrl, + requestParams: JSON.stringify(params), errorCode, errorMessage, errorDetail, diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 8bf2d801d..287005ef6 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -195,3 +195,23 @@ export function isVersionHigher(version: string, currentVersion: string): boolea // 如果两个版本号完全相等,则返回false return false; } + +// Copy +export function copy(message: string) { + navigator.clipboard.writeText(message); +} + +// 获取应用的一些基本信息 +export function getApplicationMessage() { + const env = __ENV__; + const versions = __APP_VERSION__; + const buildTime = __BUILD_TIME__; + const userAgent = navigator.userAgent; + return { + env, + versions, + buildTime, + userAgent + } +} + From 8de533944b5ef100ffcec7fde4e3ff30fb971484 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 29 Jul 2023 21:27:07 +0800 Subject: [PATCH 0490/1069] Starting Flyway No More Repeatedly --- .../ai/chat2db/server/start/Application.java | 17 ++++ .../config/AsyncContextRefreshedListener.java | 31 +++++++ .../start/config/config/AutomaticUpgrade.java | 45 ++------- .../server/tools/common/model/ConfigJson.java | 23 +++++ .../server/tools/common/util/ConfigUtils.java | 91 +++++++++++++++++++ .../rdb/RdbDmlExportController.java | 2 + 6 files changed, 170 insertions(+), 39 deletions(-) create mode 100644 chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 3925f5932..0b9c93874 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -1,11 +1,17 @@ package ai.chat2db.server.start; +import ai.chat2db.server.tools.common.model.ConfigJson; +import ai.chat2db.server.tools.common.util.ConfigUtils; import com.dtflys.forest.springboot.annotation.ForestScan; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.stereotype.Indexed; @@ -21,9 +27,20 @@ @Indexed @EnableCaching @EnableScheduling +@EnableAsync +@Slf4j public class Application { public static void main(String[] args) { + String currentVersion = ConfigUtils.getLocalVersion(); + ConfigJson configJson = ConfigUtils.getConfig(); + // Represents that the current version has been successfully launched + if (StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { + // Flyway doesn't need to start every time to increase startup speed + args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); + log.info("The current version {} has been successfully launched once and will no longer load Flyway.", + currentVersion); + } SpringApplication.run(Application.class, args); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java new file mode 100644 index 000000000..02a146a13 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.start.config.config; + +import ai.chat2db.server.tools.common.model.ConfigJson; +import ai.chat2db.server.tools.common.util.ConfigUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * Execute tasks after startup is completed + * + * @author Jiaju Zhuang + */ +@Component +@Slf4j +public class AsyncContextRefreshedListener implements ApplicationListener { + @Override + @Async + public void onApplicationEvent(ContextRefreshedEvent event) { + // Successfully set up startup + String currentVersion = ConfigUtils.getLocalVersion(); + ConfigJson configJson = ConfigUtils.getConfig(); + if (!StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { + configJson.setLatestStartupSuccessVersion(currentVersion); + ConfigUtils.setConfig(configJson); + } + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java index 99f300edf..473efd324 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java @@ -9,6 +9,7 @@ import java.util.List; import java.util.Map; +import ai.chat2db.server.tools.common.util.ConfigUtils; import cn.hutool.core.io.FileUtil; import com.dtflys.forest.Forest; import com.dtflys.forest.utils.TypeReference; @@ -31,7 +32,7 @@ public class AutomaticUpgrade { @Scheduled(fixedRate = 3600000) // 每小时运行一次 public void checkVersionUpdates() { - String appPath = getAppPath(); + String appPath = ConfigUtils.APP_PATH; log.info("appPath: {}", appPath); if (StringUtils.isBlank(appPath) || !appPath.contains("app")) { @@ -40,7 +41,7 @@ public void checkVersionUpdates() { try { Upgrade dataResult = getUpgrade(); - String oldVersion = getLocalVersion(appPath); + String oldVersion = ConfigUtils.getLocalVersion(); if (!needUpdate(dataResult, oldVersion)) { return; @@ -63,12 +64,11 @@ public void checkVersionUpdates() { FileUtil.copy(oldLib, newLib, true); } - for (Map downloadFile : dataResult.getDownloadFiles()) { downloadUpgrade(downloadFile, versionFile); } - updateVersion(appPath, dataResult.getVersion()); + ConfigUtils.updateVersion(dataResult.getVersion()); } catch (Exception e) { log.error("checkVersionUpdates error", e); } @@ -119,25 +119,6 @@ private boolean needUpdate(Upgrade upgrade, String localVersion) { return false; } - private void updateVersion(String appFile, String version) { - String versionPath = appFile + File.separator + "versions" + File.separator + "version"; - File versionFile = new File(versionPath); - if (!versionFile.exists()) { - versionFile.mkdir(); - } - FileUtil.writeUtf8String(version, versionFile); - } - - private String getLocalVersion(String appFile) { - String versionPath = appFile + File.separator + "versions" + File.separator + "version"; - File file = new File(versionPath); - if (file.exists()) { - return FileUtil.readUtf8String(file); - } else { - return null; - } - } - private void download(String url, String outputPath) throws IOException { File file = new File(outputPath); if (file.exists()) { @@ -158,29 +139,15 @@ private void download(String url, String outputPath) throws IOException { } fos.flush(); } - // System.out.println("File downloaded: " + outputPath); + // System.out.println("File downloaded: " + outputPath); } } - private String getAppPath() { - try { - String jarPath = System.getProperty("project.path"); - log.info("user home: {}", System.getProperty("user.home")); - log.info("project.path: {}", System.getProperty("project.path")); - log.info("jarPath: {}", jarPath); - return FileUtil.getParent(jarPath, 4); - } catch (Exception e) { - log.error("getAppPath error", e); - return null; - } - } - - @Data public static class Upgrade implements Serializable { private String version; - private List> downloadFiles; + private List> downloadFiles; } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java new file mode 100644 index 000000000..3869c8446 --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.tools.common.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Configuration information for chat2db + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class ConfigJson { + + /** + * Last successfully launched version + */ + private String latestStartupSuccessVersion; +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java new file mode 100644 index 000000000..c34539ebe --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java @@ -0,0 +1,91 @@ +package ai.chat2db.server.tools.common.util; + +import java.io.File; + +import com.alibaba.fastjson2.JSON; + +import ai.chat2db.server.tools.common.model.ConfigJson; +import cn.hutool.core.io.FileUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * Configure information on the user side + * + * @author Jiaju Zhuang + */ +@Slf4j +public class ConfigUtils { + public static String CONFIG_BASE_PATH = System.getProperty("user.home") + File.separator + ".chat2db"; + + public static final String APP_PATH = getAppPath(); + + private static String version = null; + public static File versionFile = null; + public static File configFile; + private static ConfigJson config = null; + + static { + String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); + if (APP_PATH != null) { + versionFile = new File( + getAppPath() + File.separator + "versions" + File.separator + "version"); + if (!versionFile.exists()) { + versionFile = null; + } + } + configFile = new File( + CONFIG_BASE_PATH + File.separator + "config" + File.separator + "config_" + environment + ".json"); + if (!configFile.exists()) { + FileUtil.writeUtf8String(JSON.toJSONString(new ConfigJson()), configFile); + } + } + + public static void updateVersion(String version) { + if (versionFile == null) { + log.warn("VERSION_FILE is null"); + return; + } + FileUtil.writeUtf8String(version, versionFile); + ConfigUtils.version = version; + } + + public static String getLocalVersion() { + if (versionFile == null) { + log.warn("VERSION_FILE is null"); + return null; + } + if (version != null) { + return version; + } + version = StringUtils.trim(FileUtil.readUtf8String(versionFile)); + return version; + } + + public static ConfigJson getConfig() { + if (config == null) { + config = JSON.parseObject(StringUtils.trim(FileUtil.readUtf8String(configFile)), ConfigJson.class); + } + return config; + } + + public static void setConfig(ConfigJson config) { + String stringConfigJson = JSON.toJSONString(config); + FileUtil.writeUtf8String(stringConfigJson, configFile); + ConfigUtils.config = config; + log.info("set config:{}", stringConfigJson); + } + + private static String getAppPath() { + try { + String jarPath = System.getProperty("project.path"); + log.info("user home: {}", System.getProperty("user.home")); + log.info("project.path: {}", System.getProperty("project.path")); + log.info("jarPath: {}", jarPath); + return FileUtil.getParent(jarPath, 4); + } catch (Exception e) { + log.error("getAppPath error", e); + return null; + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java index f09d69402..b9f3f5001 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java @@ -27,6 +27,7 @@ import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; @@ -106,6 +107,7 @@ public void export(@Valid @RequestBody DataExportRequest request, HttpServletRes } else { doExportInsert(sql, response, fileName, dbType, tableName); } + String SS= ConfigUtils.APP_PATH; } private void doExportCsv(String sql, HttpServletResponse response, String fileName) From a280800c2908e9a88072c76328db728369f31628 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 29 Jul 2023 21:30:30 +0800 Subject: [PATCH 0491/1069] Starting Flyway No More Repeatedly --- .../src/main/java/ai/chat2db/server/start/Application.java | 2 +- .../start/config/config/AsyncContextRefreshedListener.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 0b9c93874..847acf271 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -35,7 +35,7 @@ public static void main(String[] args) { String currentVersion = ConfigUtils.getLocalVersion(); ConfigJson configJson = ConfigUtils.getConfig(); // Represents that the current version has been successfully launched - if (StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { + if (StringUtils.isNotBlank(currentVersion) && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { // Flyway doesn't need to start every time to increase startup speed args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); log.info("The current version {} has been successfully launched once and will no longer load Flyway.", diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java index 02a146a13..65eacf3fe 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AsyncContextRefreshedListener.java @@ -23,7 +23,8 @@ public void onApplicationEvent(ContextRefreshedEvent event) { // Successfully set up startup String currentVersion = ConfigUtils.getLocalVersion(); ConfigJson configJson = ConfigUtils.getConfig(); - if (!StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { + if (StringUtils.isNotBlank(currentVersion) && !StringUtils.equals(currentVersion, + configJson.getLatestStartupSuccessVersion())) { configJson.setLatestStartupSuccessVersion(currentVersion); ConfigUtils.setConfig(configJson); } From 330e48f378868327a1a4dffce0f2f89bdaeb8b4f Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 29 Jul 2023 21:49:42 +0800 Subject: [PATCH 0492/1069] Change the version number to long --- .../ai/chat2db/server/start/config/config/AutomaticUpgrade.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java index 473efd324..ef70f39b4 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java @@ -112,7 +112,7 @@ private boolean needUpdate(Upgrade upgrade, String localVersion) { String[] versionArray = upgrade.getVersion().split("\\."); String[] localVersionArray = localVersion.split("\\."); for (int i = 0; i < versionArray.length; i++) { - if (Integer.parseInt(versionArray[i]) > Integer.parseInt(localVersionArray[i])) { + if (Long.parseLong(versionArray[i]) > Long.parseLong(localVersionArray[i])) { return true; } } From f6a528200aa0ce7cb91d83c54ed7a919eda5d848 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 29 Jul 2023 22:41:29 +0800 Subject: [PATCH 0493/1069] feat: Add Export CSV --- .../src/components/SearchResult/TableBox/index.tsx | 9 +++++---- chat2db-client/src/components/SearchResult/index.tsx | 5 +++++ .../workspace/components/WorkspaceRightItem/index.tsx | 10 +++++++++- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 18fb3d76f..67b25a268 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -22,6 +22,7 @@ interface ITableProps { config: IResultConfig; onConfigChange: (config: IResultConfig) => void; onSearchTotal: () => Promise; + onExport: () => void; } interface IViewTableCellData { @@ -188,16 +189,16 @@ export default function TableBox(props: ITableProps) { onClickTotalBtn={onClickTotalBtn} />

- {/*
+
-
*/} +
void; + onExport: (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise; onTabEdit: (type: 'add' | 'remove', value?: number | string) => void; onSearchTotal: (index: number) => Promise; isLoading?: boolean; @@ -106,6 +108,9 @@ export default memo(function SearchResult(props) { return await props.onSearchTotal(index); } }} + onExport={() => { + props.onExport && props.onExport(item.originalSql, ExportTypeEnum.CSV, ExportSizeEnum.ALL); + }} /> ); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 9df4d1f10..2b3462823 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -10,10 +10,11 @@ import { IManageResultData, IResultConfig } from '@/typings'; import { IWorkspaceModelState } from '@/models/workspace'; import historyServer, { IGetSavedListParams, ISaveBasicInfo } from '@/service/history'; import { IAIState } from '@/models/ai'; -import sqlServer, { IExecuteSqlParams } from '@/service/sql'; +import sqlServer, { IExecuteSqlParams, IExportParams } from '@/service/sql'; import { v4 as uuidV4 } from 'uuid'; import sql from '@/service/sql'; import { isNumber } from 'lodash'; +import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; interface IProps { className?: string; isActive: boolean; @@ -138,6 +139,12 @@ const WorkspaceRightItem = memo(function (props) { return total; }; + const handleExportSQLResult = async (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + const params: IExportParams = { ...data, originalSql, exportType, exportSize }; + + await sqlServer.exportResultTable(params); + }; + const handleResultTabEdit = (type: 'add' | 'remove', uuid?: string | number) => { if (type === 'remove') { const tabIndex = resultData.findIndex((d) => d.uuid === uuid); @@ -187,6 +194,7 @@ const WorkspaceRightItem = memo(function (props) { onTabEdit={handleResultTabEdit} onExecute={handleExecuteSQLbyConfigChanged} onSearchTotal={handleSearchTotal} + onExport={handleExportSQLResult} manageResultDataList={resultData} resultConfig={resultConfig} isLoading={tableLoading} From d5dee02c65dbd624f7f6a77485d428b18fb0e490 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 30 Jul 2023 09:35:13 +0800 Subject: [PATCH 0494/1069] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 50d24a276..d9856c23c 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ $ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # To launch the cha Please star and fork on GitHub before joining the group. Follow our WeChat public account. - + ## ❤️ Acknowledgements From 5e608c88a2c80260b0ea2ad411c1022e18de052c Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 30 Jul 2023 09:35:57 +0800 Subject: [PATCH 0495/1069] Update README_CN.md --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index cb85a9669..4450f6a9b 100644 --- a/README_CN.md +++ b/README_CN.md @@ -202,7 +202,7 @@ $ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 启动应用 chat 加群前请先 Star 和 Fork,谢谢~关注微信公众号可加入微信、钉钉、QQ 群一起讨论,并可以获取 Chat2DB 最新动态和更新。 - + ## ❤️ 致谢 From 41b14d3ce9b369f4c83e65d31fd8db60039a7c9e Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 10:40:56 +0800 Subject: [PATCH 0496/1069] upgrade fastjson --- chat2db-server/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index cde1ae980..2e6885f90 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -108,7 +108,7 @@ com.alibaba.fastjson2 fastjson2 - 2.0.34 + 2.0.37 From 59717e513be7aea9187913cd20d31156d2658b2a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 11:02:18 +0800 Subject: [PATCH 0497/1069] Optimize startup speed --- .../config/listener/ManageApplicationListener.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java index adc9a7343..8fdb1cc15 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/ManageApplicationListener.java @@ -1,13 +1,11 @@ package ai.chat2db.server.start.config.listener; -import java.time.Duration; - import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.TypeReference; import ai.chat2db.server.tools.base.enums.SystemEnvironmentEnum; import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import com.dtflys.forest.Forest; -import com.dtflys.forest.utils.TypeReference; +import cn.hutool.http.HttpUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -35,10 +33,8 @@ public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { // 尝试访问确认应用是否已经启动 DataResult dataResult; try { - dataResult = Forest.get("http://127.0.0.1:" + serverPort + "/api/system/get-version-a") - .connectTimeout(Duration.ofMillis(50)) - .readTimeout(Duration.ofMillis(100)) - .execute(new TypeReference<>() {}); + String body = HttpUtil.get("http://127.0.0.1:" + serverPort + "/api/system/get-version-a", 10); + dataResult = JSON.parseObject(body, new TypeReference<>() {}); } catch (Exception e) { // 抛出异常 代表没有旧的启动 或者旧的不靠谱 log.info("尝试访问旧的应用失败。本异常不重要,正常启动启动都会输出,请忽略。" + e.getMessage()); From 7a9ee0444d1052fef33cc4b3d0860c410247facf Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 11:20:55 +0800 Subject: [PATCH 0498/1069] Optimize startup speed --- .../java/ai/chat2db/server/tools/common/util/I18nUtils.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java index 7b99f1c7c..91dc2dde8 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/I18nUtils.java @@ -7,6 +7,7 @@ import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.NoSuchMessageException; +import org.springframework.context.annotation.Lazy; import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; @@ -18,8 +19,9 @@ */ @Slf4j @Component +@Lazy(value = false) public class I18nUtils implements InitializingBean { - public static final String DEFAULT_message_Code="common.systemError"; + public static final String DEFAULT_MESSAGE_CODE="common.systemError"; @Resource private MessageSource messageSource; private static MessageSource messageSourceStatic; @@ -34,7 +36,7 @@ public static String getMessage(String messageCode, @Nullable Object[] args) { } catch (NoSuchMessageException e) { log.error("no message.", e); } - return messageSourceStatic.getMessage(DEFAULT_message_Code, args, LocaleContextHolder.getLocale()); + return messageSourceStatic.getMessage(DEFAULT_MESSAGE_CODE, args, LocaleContextHolder.getLocale()); } /** From 7f24d378649aa4c71a7a0f437c1efeaac54e8f6f Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 11:21:38 +0800 Subject: [PATCH 0499/1069] feat: Add Export Type --- .../SearchResult/TableBox/index.less | 2 +- .../SearchResult/TableBox/index.tsx | 61 +++++++++++++++---- .../src/components/SearchResult/index.tsx | 4 +- chat2db-client/src/i18n/en-us/common.ts | 4 +- chat2db-client/src/i18n/zh-cn/common.ts | 1 + 5 files changed, 57 insertions(+), 15 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index beb42c25c..fda0d7bf7 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -38,7 +38,7 @@ top: 0; z-index: 30; display: flex; - justify-content: start; + justify-content: space-between; align-items: center; border-top: 1px solid var(--color-border-secondary); background-color: var(--color-bg-elevated); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 67b25a268..b40a3c333 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react'; import { TableDataType } from '@/constants/table'; import { IManageResultData, IResultConfig } from '@/typings/database'; import { formatDate } from '@/utils/date'; -import { Button, message, Modal } from 'antd'; +import { Button, Dropdown, MenuProps, message, Modal, Space } from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; import Iconfont from '../../Iconfont'; import classnames from 'classnames'; @@ -14,6 +14,8 @@ import { ThemeType } from '@/constants'; import i18n from '@/i18n'; import { compareStrings } from '@/utils/sort'; import MyPagination from '../Pagination'; +import { DownOutlined, UserOutlined } from '@ant-design/icons'; +import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import styles from './index.less'; interface ITableProps { @@ -22,7 +24,7 @@ interface ITableProps { config: IResultConfig; onConfigChange: (config: IResultConfig) => void; onSearchTotal: () => Promise; - onExport: () => void; + onExport: (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void; } interface IViewTableCellData { @@ -52,6 +54,44 @@ export default function TableBox(props: ITableProps) { const [appTheme] = useTheme(); const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); + const items: MenuProps['items'] = useMemo( + () => [ + { + label: '导出全部数据为csv', + key: '1', + icon: , + onClick: () => { + props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); + }, + }, + { + label: '导出全部数据为插入语句', + key: '2', + icon: , + onClick: () => { + props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); + }, + }, + { + label: '导出当前页数据为csv', + key: '3', + icon: , + onClick: () => { + props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); + }, + }, + { + label: '导出当前页数据为插入语句', + key: '4', + icon: , + onClick: () => { + props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); + }, + }, + ], + [], + ); + const defaultSorts: SortItem[] = useMemo( () => (headerList || []).map((item) => ({ @@ -189,16 +229,15 @@ export default function TableBox(props: ITableProps) { onClickTotalBtn={onClickTotalBtn} />
-
- -
+
(function SearchResult(props) { return await props.onSearchTotal(index); } }} - onExport={() => { - props.onExport && props.onExport(item.originalSql, ExportTypeEnum.CSV, ExportSizeEnum.ALL); + onExport={(exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + props.onExport && props.onExport(item.originalSql, exportType, exportSize); }} /> diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index d3bf5ef0e..b33dbeba9 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -61,8 +61,10 @@ export default { 'common.text.wechatPopularizeAi2': 'Follow the wechat public account and send "AI" to get the ApiKey for free, and give away the number of experiences.', 'common.text.wechatPopularize': 'You can also send "promotion" to get more experiences for free.', + 'common.text.export': 'Export', 'common.notification.detail': 'More details', 'common.notification.solution': 'Solution', 'common.button.copyError': 'Copy error report', - 'common.button.copyErrorTips': '(The interface information and detailed parameters will be copied here. If there are sensitive parameters, please parse JSON first and then send them)', + 'common.button.copyErrorTips': + '(The interface information and detailed parameters will be copied here. If there are sensitive parameters, please parse JSON first and then send them)', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 021c3fdb8..6955f8822 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -60,6 +60,7 @@ export default { 'common.text.wechatPopularizeAi': '关注微信公众号,发送 “AI” 免费获取体验次数。', 'common.text.wechatPopularizeAi2': '关注微信公众号,发送 “AI” 可以免费获得ApiKey,并赠送体验次数。', 'common.text.wechatPopularize': '发送 “推广” 还可以免费获取更多体验次数。', + 'common.text.export': '导出', 'common.notification.detail': '查看详情', 'common.notification.solution': '解决办法', 'common.button.copyError': '复制错误报告', From e5df5828c907f870c5e9b7c436fde026b0418b46 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 11:24:41 +0800 Subject: [PATCH 0500/1069] Optimize startup speed --- .../src/main/java/ai/chat2db/server/start/Application.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 847acf271..25567ad03 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -23,7 +23,7 @@ @SpringBootApplication @ComponentScan(value = {"ai.chat2db.server"}) @MapperScan("ai.chat2db.server.domain.repository.mapper") -@ForestScan(basePackages = "ai.chat2db.server") +@ForestScan(basePackages = "ai.chat2db.server.web.api.http") @Indexed @EnableCaching @EnableScheduling From 298a8c162ee865169106000d7b2da26d5d362d5b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 12:04:53 +0800 Subject: [PATCH 0501/1069] fix: fix originalSQL --- .../SearchResult/TableBox/index.tsx | 16 ++-- .../src/components/SearchResult/index.tsx | 84 ++++++++++--------- .../components/WorkspaceRightItem/index.tsx | 9 +- 3 files changed, 60 insertions(+), 49 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index b40a3c333..03212b466 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -24,7 +24,7 @@ interface ITableProps { config: IResultConfig; onConfigChange: (config: IResultConfig) => void; onSearchTotal: () => Promise; - onExport: (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void; + onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void; } interface IViewTableCellData { @@ -54,6 +54,10 @@ export default function TableBox(props: ITableProps) { const [appTheme] = useTheme(); const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); + const handleExport = (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + props.onExport && props.onExport(data.sql, data.originalSql, exportType, exportSize); + }; + const items: MenuProps['items'] = useMemo( () => [ { @@ -61,7 +65,7 @@ export default function TableBox(props: ITableProps) { key: '1', icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); + handleExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); }, }, { @@ -69,7 +73,7 @@ export default function TableBox(props: ITableProps) { key: '2', icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); + handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); }, }, { @@ -77,7 +81,7 @@ export default function TableBox(props: ITableProps) { key: '3', icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); + handleExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); }, }, { @@ -85,11 +89,11 @@ export default function TableBox(props: ITableProps) { key: '4', icon: , onClick: () => { - props.onExport && props.onExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); + handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); }, }, ], - [], + [data], ); const defaultSorts: SortItem[] = useMemo( diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 99e205e72..07bf9ebc2 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useState, useMemo, Fragment } from 'react'; +import React, { memo, useEffect, useState, useMemo, Fragment, useCallback } from 'react'; import classnames from 'classnames'; import Tabs, { IOption } from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; @@ -16,7 +16,7 @@ interface IProps { manageResultDataList?: IManageResultData[]; resultConfig: IResultConfig[]; onExecute: (sql: string, config: IResultConfig, index: number) => void; - onExport: (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise; + onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise; onTabEdit: (type: 'add' | 'remove', value?: number | string) => void; onSearchTotal: (index: number) => Promise; isLoading?: boolean; @@ -45,7 +45,7 @@ const handleTabs = (result: IManageResultData[]) => { }; export default memo(function SearchResult(props) { - const { className, manageResultDataList = [], isLoading, onExecute, onSearchTotal } = props; + const { className, manageResultDataList = [], isLoading, onExecute, onExport } = props; const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState([]); const [resultConfig, setResultConfig] = useState([]); @@ -64,7 +64,7 @@ export default memo(function SearchResult(props) { setCurrentTab(manageResultDataList[0].uuid); } - setResultDataList(manageResultDataList); + setResultDataList([...manageResultDataList]); setTabs(handleTabs(manageResultDataList)); }, [manageResultDataList]); @@ -85,48 +85,50 @@ export default memo(function SearchResult(props) { ); }; - const renderTable = useMemo(() => { + const renderTable = () => { if (!tabs || !tabs.length) { return renderEmpty(); } if (!resultDataList || !resultDataList.length) { return renderEmpty(); } - return (resultDataList || []).map((item, index: number) => { - if (item.success) { - return ( - - { - if (props.onSearchTotal) { - return await props.onSearchTotal(index); - } - }} - onExport={(exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { - props.onExport && props.onExport(item.originalSql, exportType, exportSize); - }} - /> - - ); - } else { - return ( - - - - ); - } - }); - }, [currentTab, resultDataList, resultConfig]); + + return renderTableContent; + }; + + const renderTableContent = (resultDataList || []).map((item, index: number) => { + console.log(item.sql, item.originalSql); + if (item.success) { + return ( + { + onExecute && onExecute(item.originalSql, config, index); + }} + onSearchTotal={async () => { + if (props.onSearchTotal) { + return await props.onSearchTotal(index); + } + }} + onExport={(sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + onExport && onExport(sql, originalSql, exportType, exportSize); + }} + /> + ); + } else { + return ( + + ); + } + }); return (
@@ -144,7 +146,7 @@ export default memo(function SearchResult(props) {
) : null} - {renderTable} + {renderTable()} ); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 2b3462823..a7d6af347 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -139,8 +139,13 @@ const WorkspaceRightItem = memo(function (props) { return total; }; - const handleExportSQLResult = async (originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { - const params: IExportParams = { ...data, originalSql, exportType, exportSize }; + const handleExportSQLResult = async ( + sql: string, + originalSql: string, + exportType: ExportTypeEnum, + exportSize: ExportSizeEnum, + ) => { + const params: IExportParams = { ...data, sql, originalSql, exportType, exportSize }; await sqlServer.exportResultTable(params); }; From 7e63a61e9675dbb16574deb8b04688003dfb9827 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 14:17:00 +0800 Subject: [PATCH 0502/1069] Support team functionality --- ...0\346\210\267\346\235\203\351\231\220.sql" | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" new file mode 100644 index 000000000..a25d426dd --- /dev/null +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -0,0 +1,94 @@ +CREATE TABLE IF NOT EXISTS `environment` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', + `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', + `name` varchar(128) DEFAULT NOT NULL COMMENT '环境名称', + `short_name` varchar(128) DEFAULT NULL COMMENT '环境缩写', + `style` varchar(32) DEFAULT NULL COMMENT '样式类型', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='数据库连接环境' +; + +INSERT INTO `environment` +(`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `style`) +VALUES (1, 1, 1, '线上环境', '线上', 'RELEASE'); +INSERT INTO `environment` +(`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `style`) +VALUES (2, 1, 1, '测试环境', '测试', 'TEST'); + +ALTER TABLE `data_source` + ADD COLUMN `environment_id` bigint(20) unsigned NOT NULL DEFAULT 2 COMMENT '环境id'; + +CREATE TABLE IF NOT EXISTS `team` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', + `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', + `code` varchar(128) DEFAULT NOT NULL COMMENT '团队编码', + `name` varchar(512) DEFAULT NULL COMMENT '团队名称', + `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '团队状态', + `description` text DEFAULT NULL COMMENT '团队描述', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='团队' +; + +create UNIQUE INDEX uk_team_code on team (code); + + +CREATE TABLE IF NOT EXISTS `team_dbhub_user` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', + `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', + `team_id` bigint(20) unsigned NOT NULL COMMENT '团队id', + `dbhub_user_id` bigint(20) unsigned NOT NULL COMMENT '用户id', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='用户团队表' +; + +create INDEX idx_team_dbhub_user_team_id on team_dbhub_user (`team_id`); +create INDEX idx_team_dbhub_user_dbhub_user_id on team_dbhub_user (`dbhub_user_id`); + +CREATE TABLE IF NOT EXISTS `team_role` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', + `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', + `team_id` bigint(20) unsigned NOT NULL COMMENT '团队id', + `role_code` varchar(32) NOT NULL COMMENT '角色编码', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='团队角色表' +; + +create INDEX team_role_team_id on team_role (`team_id`); + +CREATE TABLE IF NOT EXISTS `data_source_access` +( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', + `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源id', + `access_object_type` varchar(32) NOT NULL COMMENT '授权类型', + `access_object_id` bigint(20) unsigned NOT NULL COMMENT '授权id,根据类型区分是用户还是团队', + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 COMMENT ='数据源授权' +; + +create INDEX data_source_access_data_source_id on data_source_access (`data_source_id`); +create INDEX data_source_access_access_object_id on data_source_access (`access_object_type`,`access_object_id`); From bd0321679044874e222f6089f9649fc42d2b2f43 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 14:17:28 +0800 Subject: [PATCH 0503/1069] Enable Flyway --- .../src/main/java/ai/chat2db/server/start/Application.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 25567ad03..2203cefc9 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -4,7 +4,6 @@ import ai.chat2db.server.tools.common.util.ConfigUtils; import com.dtflys.forest.springboot.annotation.ForestScan; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; @@ -37,7 +36,7 @@ public static void main(String[] args) { // Represents that the current version has been successfully launched if (StringUtils.isNotBlank(currentVersion) && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { // Flyway doesn't need to start every time to increase startup speed - args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); + //args = ArrayUtils.add(args, "--spring.flyway.enabled=false"); log.info("The current version {} has been successfully launched once and will no longer load Flyway.", currentVersion); } From b972e34e179be820ed4a17de4279b870e074e99d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 14:17:53 +0800 Subject: [PATCH 0504/1069] feat: Complete the result export --- .../components/WorkspaceRightItem/index.tsx | 4 +- chat2db-client/src/service/sql.ts | 4 +- chat2db-client/src/utils/common.ts | 42 +++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index a7d6af347..0b91cd55b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -15,6 +15,7 @@ import { v4 as uuidV4 } from 'uuid'; import sql from '@/service/sql'; import { isNumber } from 'lodash'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; +import { downloadFile } from '@/utils/common'; interface IProps { className?: string; isActive: boolean; @@ -146,8 +147,7 @@ const WorkspaceRightItem = memo(function (props) { exportSize: ExportSizeEnum, ) => { const params: IExportParams = { ...data, sql, originalSql, exportType, exportSize }; - - await sqlServer.exportResultTable(params); + downloadFile(window._BaseURL + '/api/rdb/dml/export', params); }; const handleResultTabEdit = (type: 'add' | 'remove', uuid?: string | number) => { diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 91d23eecf..89144cba7 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -123,7 +123,7 @@ export interface IExportParams extends IExecuteSqlParams { /** * 导出-表格 */ -const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); +// const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); export default { getList, @@ -142,5 +142,5 @@ export default { addTablePin, deleteTablePin, getDMLCount, - exportResultTable + // exportResultTable }; diff --git a/chat2db-client/src/utils/common.ts b/chat2db-client/src/utils/common.ts index 007da3d13..ee2933124 100644 --- a/chat2db-client/src/utils/common.ts +++ b/chat2db-client/src/utils/common.ts @@ -14,3 +14,45 @@ export function formatParams(obj: { [key: string]: any }) { }); return params.toString(); } + +export function downloadFile(url: string, params: any) { + // 创建POST请求 + fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', // 或者根据服务端的要求设置其他的内容类型 + }, + body: JSON.stringify(params), // 将参数转换为JSON字符串 + }) + .then((response) => { + // 从content-disposition头中获取文件名 + const contentDisposition = response.headers.get('content-disposition'); + const filename = contentDisposition ? decodeURIComponent(contentDisposition.split("''")[1]) : 'file.txt'; + + // 获取返回的Blob数据 + return response.blob().then((blob) => ({ blob, filename })); + }) + .then(({ blob, filename }) => { + // 创建一个代表Blob对象的URL + const blobUrl = URL.createObjectURL(blob); + + // 创建一个隐藏的 标签,并设置其 href 属性 + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = blobUrl; + + // 使用从响应头解析的文件名 + a.download = filename; + + // 将 标签附加到 DOM,并触发点击事件 + document.body.appendChild(a); + a.click(); + + // 清理:从 DOM 中移除 标签,并释放Blob URL + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + }) + .catch((error) => { + console.error('下载文件失败:', error); + }); +} From b79337bf4f9431eeaae8f8f29bb6a9e87877f90d Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 30 Jul 2023 14:21:56 +0800 Subject: [PATCH 0505/1069] update changelog --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01868895d..0326f5ed2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.0.7 +## ⭐ New Features + +- Export query result as file is supported + +## 🐞 Bug Fixes + +- Fixed ai config issues [Issue #346](https://github.com/chat2db/Chat2DB/issues/346) + + # 2.0.6 ## 🐞 Bug Fixes From 6e668a6773d17889b7219baa5b8294169504c017 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 30 Jul 2023 15:00:59 +0800 Subject: [PATCH 0506/1069] style:table --- .../SearchResult/TableBox/index.less | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index fda0d7bf7..0e328d3c0 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -51,6 +51,7 @@ display: flex; justify-content: start; align-items: center; + white-space: pre; &:not(:last-child) { border-right: 1px solid var(--color-border); } @@ -60,6 +61,10 @@ flex: 1; } +.monacoEditor{ + height: 60vh; +} + .statusBar { position: sticky; bottom: 0; @@ -78,12 +83,12 @@ } .tableItem { + position: relative; width: 100%; height: 100%; - // cursor: pointer; - // display: flex; - // align-items: center; - // justify-content: space-between; + display: flex; + align-items: center; + justify-content: space-between; // height: 100%; max-height: 120px; @@ -94,31 +99,30 @@ // } &:hover .tableHoverBox { - // display: flex !important; - display: block; + display: flex; } } .tableHoverBox { + align-items: center; position: absolute; top: 0px; right: 0px; width: 40px; display: none; align-items: center; - // position: absolute; - background-color: var(--color-bg-elevated); - // top: 0; - // right: 0; - // bottom: 0; + top: 0; + right: 0; + bottom: 0; i { font-size: 15px; margin: 0px 2px; + cursor: pointer; } i:hover { - color: var(--custom-primary-color); + color: var(--color-primary); } } From 768bd5596a069a6baed92c1712f8888706993c3a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 15:25:53 +0800 Subject: [PATCH 0507/1069] feat: Optimize AI processes --- chat2db-client/src/blocks/Setting/AiSetting/index.tsx | 5 +---- chat2db-client/src/blocks/Setting/index.tsx | 11 ++++++++--- .../src/components/SearchResult/TableBox/index.tsx | 9 +++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 7512115d6..56c27a4c0 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -195,15 +195,12 @@ export default function SettingAI(props: IProps) { )}
- {/* */}
- {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && } + {/* {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && } */} ); } diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index df8600437..a2471bbde 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -9,12 +9,11 @@ import ProxySetting from './ProxySetting'; import About from './About'; import { connect } from 'umi'; import { IAIState } from '@/models/ai'; -import styles from './index.less'; -import configService from '@/service/config'; -import { AiSqlSourceType } from '@/typings/ai'; import TestVersion from '@/components/TestVersion'; import { IAiConfig } from '@/typings'; +import styles from './index.less'; + interface IProps { aiConfig: IAiConfig; className?: string; @@ -28,6 +27,12 @@ function Setting(props: IProps) { const [currentMenu, setCurrentMenu] = useState(0); + useEffect(() => { + if (isModalVisible) { + getAiSystemConfig(); + } + }, [isModalVisible]); + useEffect(() => { getAiSystemConfig(); }, []); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 03212b466..4ed7c415c 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -63,7 +63,7 @@ export default function TableBox(props: ITableProps) { { label: '导出全部数据为csv', key: '1', - icon: , + // icon: , onClick: () => { handleExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); }, @@ -71,7 +71,7 @@ export default function TableBox(props: ITableProps) { { label: '导出全部数据为插入语句', key: '2', - icon: , + // icon: , onClick: () => { handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); }, @@ -79,7 +79,7 @@ export default function TableBox(props: ITableProps) { { label: '导出当前页数据为csv', key: '3', - icon: , + // icon: , onClick: () => { handleExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); }, @@ -87,7 +87,7 @@ export default function TableBox(props: ITableProps) { { label: '导出当前页数据为插入语句', key: '4', - icon: , + // icon: , onClick: () => { handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); }, @@ -264,6 +264,7 @@ export default function TableBox(props: ITableProps) { open={!!viewTableCellData?.name} onCancel={handleCancel} width="60vw" + height="70vh" maskClosable={false} footer={ <> From 1174d30e405f1da73ffb88b28dd985de973efb60 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 15:29:11 +0800 Subject: [PATCH 0508/1069] feat: Optimize Code --- .../SearchResult/TableBox/index.less | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 0e328d3c0..b26a3cd5e 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -51,7 +51,6 @@ display: flex; justify-content: start; align-items: center; - white-space: pre; &:not(:last-child) { border-right: 1px solid var(--color-border); } @@ -61,10 +60,6 @@ flex: 1; } -.monacoEditor{ - height: 60vh; -} - .statusBar { position: sticky; bottom: 0; @@ -83,12 +78,12 @@ } .tableItem { - position: relative; width: 100%; height: 100%; - display: flex; - align-items: center; - justify-content: space-between; + // cursor: pointer; + // display: flex; + // align-items: center; + // justify-content: space-between; // height: 100%; max-height: 120px; @@ -99,30 +94,32 @@ // } &:hover .tableHoverBox { - display: flex; + // display: flex !important; + display: block; } } .tableHoverBox { - align-items: center; position: absolute; top: 0px; right: 0px; width: 40px; display: none; align-items: center; - top: 0; - right: 0; - bottom: 0; + // position: absolute; + background-color: var(--color-bg-elevated); + // top: 0; + // right: 0; + // bottom: 0; + cursor: pointer; i { font-size: 15px; margin: 0px 2px; - cursor: pointer; } i:hover { - color: var(--color-primary); + color: var(--custom-primary-color); } } @@ -143,3 +140,7 @@ } } } + +.monacoEditor { + height: 60vh; +} From 51c0da81f6897a9b9da05b4344d0c06995787f63 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 15:59:15 +0800 Subject: [PATCH 0509/1069] Add Team Interface --- chat2db-server/.apifox-helper.properties | 15 +++ .../test/mybatis/MybatisGeneratorTest.java | 5 +- .../chat2db-server-admin-api/pom.xml | 49 ++++++++ .../DataSourceAccessController.java | 101 ++++++++++++++++ .../datasource/DataSourceController.java | 100 ++++++++++++++++ .../converter/DataSourceAdminConverter.java | 60 ++++++++++ .../request/DataSourceCloneRequest.java | 19 +++ .../request/DataSourceCreateRequest.java | 108 ++++++++++++++++++ .../request/DataSourcePageQueryRequest.java | 19 +++ .../request/DataSourceUpdateRequest.java | 105 +++++++++++++++++ .../vo/DataSourceAccessPageQueryVO.java | 28 +++++ .../datasource/vo/DataSourcePageQueryVO.java | 28 +++++ chat2db-server/chat2db-server-web/pom.xml | 1 + 13 files changed, 635 insertions(+), 3 deletions(-) create mode 100644 chat2db-server/.apifox-helper.properties create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java diff --git a/chat2db-server/.apifox-helper.properties b/chat2db-server/.apifox-helper.properties new file mode 100644 index 000000000..0da78a8d7 --- /dev/null +++ b/chat2db-server/.apifox-helper.properties @@ -0,0 +1,15 @@ +# easy-yapi插件生成的配置 +# 日期转long +json.rule.convert[java.util.Date]=java.lang.Long +json.rule.convert[java.sql.Timestamp]=java.lang.Long +json.rule.convert[java.time.LocalDateTime]=java.lang.Long +json.rule.convert[java.time.LocalDate]=java.lang.Long + +# 使用version来 修改标签 +api.tag=#version + +# ignore serialVersionUID +constant.field.ignore=groovy:it.name()=="serialVersionUID" + +# sprnSupport for Jackson annotations +json.rule.field.ignore=@com.fasterxml.jackson.annotation.JsonIgnore#value \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index 2a50566ee..7ddebb441 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -4,17 +4,16 @@ import java.util.List; import java.util.Map; -import jakarta.annotation.Resource; import javax.sql.DataSource; import ai.chat2db.server.start.test.common.BaseTest; - import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import com.google.common.collect.Lists; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @@ -33,7 +32,7 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("data_source")); //doGenerator(Lists.newArrayList("operation_log")); //doGenerator(Lists.newArrayList("operation_saved")); - doGenerator(Lists.newArrayList("dbhub_user")); + doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","team_role","data_source_access")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml new file mode 100644 index 000000000..7c1130c03 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml @@ -0,0 +1,49 @@ + + + + ai.chat2db + chat2db-server-web + ${revision} + ../pom.xml + + 4.0.0 + chat2db-server-admin-api + jar + chat2db-server-admin-api + + + + ai.chat2db + chat2db-server-tools-common + + + ai.chat2db + chat2db-server-domain-api + + + ai.chat2db + chat2db-server-domain-core + + + org.springframework.boot + spring-boot-starter-web + + + commons-io + commons-io + + + + + com.dtflys.forest + forest-spring-boot3-starter + + + com.alibaba + easyexcel + + + + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java new file mode 100644 index 000000000..ef3c04edf --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java @@ -0,0 +1,101 @@ + +package ai.chat2db.server.admin.api.controller.datasource; + +import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCloneRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourcePageQueryRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; +import ai.chat2db.server.domain.api.param.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Data Source Access Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/data/source/access") +@RestController +public class DataSourceAccessController { + + @Resource + private DataSourceService dataSourceService; + @Resource + private DataSourceAdminConverter dataSourceAdminConverter; + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid DataSourcePageQueryRequest request) { + return dataSourceService.queryPage(dataSourceAdminConverter.request2param(request), null) + .mapToWeb(dataSourceAdminConverter::dto2vo); + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/create") + public DataResult create(@RequestBody DataSourceCreateRequest request) { + DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); + return dataSourceService.create(param); + } + + /** + * update + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/update") + public ActionResult update(@RequestBody DataSourceUpdateRequest request) { + DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); + return dataSourceService.update(param); + } + + /** + * clone + * + * @param request + * @return + */ + @PostMapping("/clone") + public DataResult clone(@RequestBody DataSourceCloneRequest request) { + return dataSourceService.copyById(request.getId()); + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return dataSourceService.delete(id); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java new file mode 100644 index 000000000..af6968f01 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java @@ -0,0 +1,100 @@ + +package ai.chat2db.server.admin.api.controller.datasource; + +import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCloneRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourcePageQueryRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; +import ai.chat2db.server.domain.api.param.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Data Source Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/data/source") +@RestController +public class DataSourceController { + + @Resource + private DataSourceService dataSourceService; + @Resource + private DataSourceAdminConverter dataSourceAdminConverter; + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid DataSourcePageQueryRequest request) { + return dataSourceService.queryPage(dataSourceAdminConverter.request2param(request), null) + .mapToWeb(dataSourceAdminConverter::dto2vo); + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/create") + public DataResult create(@RequestBody DataSourceCreateRequest request) { + DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); + return dataSourceService.create(param); + } + + /** + * update + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/update") + public ActionResult update(@RequestBody DataSourceUpdateRequest request) { + DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); + return dataSourceService.update(param); + } + + /** + * clone + * + * @param request + * @return + */ + @PostMapping("/clone") + public DataResult clone(@RequestBody DataSourceCloneRequest request) { + return dataSourceService.copyById(request.getId()); + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return dataSourceService.delete(id); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java new file mode 100644 index 000000000..6c0f03dff --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java @@ -0,0 +1,60 @@ +package ai.chat2db.server.admin.api.controller.datasource.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourcePageQueryRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; +import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.param.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.DataSourcePageQueryParam; +import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class DataSourceAdminConverter { + + /** + * conversion + * + * @param request + * @return + */ + public abstract DataSourcePageQueryParam request2param(DataSourcePageQueryRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract DataSourcePageQueryVO dto2vo(DataSource dto); + + /** + * 参数转换 + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "user", target = "userName") + }) + public abstract DataSourceCreateParam createReq2param(DataSourceCreateRequest request); + + /** + * 参数转换 + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "user", target = "userName") + }) + public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java new file mode 100644 index 000000000..54386e61b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + + +import lombok.Data; + +/** + * @author moji + * @version ConnectionCloneRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceCloneRequest { + + /** + * 主键id + */ + private Long id; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java new file mode 100644 index 000000000..07cbdbc8b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java @@ -0,0 +1,108 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.util.List; + +import ai.chat2db.server.tools.base.enums.EnvTypeEnum; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceCreateRequest { + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + @NotNull + private String url; + + /** + * 连接用户名 + */ + private String user; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 认证类型 + */ + private String authenticationType; + + /** + * 连接类型 + */ + @NotNull + private String type; + + /** + * 环境类型 + * @see EnvTypeEnum + */ + private String envType; + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + /** + * 扩展信息 + */ + private List extendInfo; + + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; + + + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java new file mode 100644 index 000000000..ff2ad3018 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java @@ -0,0 +1,19 @@ + +package ai.chat2db.server.admin.api.controller.datasource.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourcePageQueryRequest extends PageQueryRequest { + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java new file mode 100644 index 000000000..860050f12 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java @@ -0,0 +1,105 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.util.List; + +import ai.chat2db.server.tools.base.enums.EnvTypeEnum; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceUpdateRequest { + + /** + * 主键id + */ + @NotNull + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 连接用户 + */ + private String user; + + /** + * 密码 + */ + private String password; + + /** + * 连接类型 + */ + private String type; + + /** + * 环境类型 + * @see EnvTypeEnum + */ + private String envType; + + + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java new file mode 100644 index 000000000..6e4e046f7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java @@ -0,0 +1,28 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryVO { + + /** + * 主键id + */ + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java new file mode 100644 index 000000000..5aff886e6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java @@ -0,0 +1,28 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourcePageQueryVO { + + /** + * 主键id + */ + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; +} diff --git a/chat2db-server/chat2db-server-web/pom.xml b/chat2db-server/chat2db-server-web/pom.xml index 15b8bbe5c..2db39633e 100644 --- a/chat2db-server/chat2db-server-web/pom.xml +++ b/chat2db-server/chat2db-server-web/pom.xml @@ -14,6 +14,7 @@ chat2db-server-web-api + chat2db-server-admin-api \ No newline at end of file From 64f1037b23a19a76786e810ab6c97a074d7eb14f Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 16:03:48 +0800 Subject: [PATCH 0510/1069] Modify the mybatis directory --- .../start/test/mybatis/MybatisGeneratorTest.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index 2a50566ee..7afe4ef2a 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -4,17 +4,16 @@ import java.util.List; import java.util.Map; -import jakarta.annotation.Resource; import javax.sql.DataSource; import ai.chat2db.server.start.test.common.BaseTest; - import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import com.google.common.collect.Lists; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; @@ -38,13 +37,13 @@ public void coreGenerator() { private void doGenerator(List tableList) { - // 当前项目地址 拿到的是ali-dbhub-server-start地址 + // 当前项目地址 拿到的是chat2db-server-start地址 String outputDir = System.getProperty("user.dir") - + "/../ali-dbhub-server-domain/ali-dbhub-server-domain-repository/src/main" + + "/../chat2db-server-domain/chat2db-server-domain-repository/src/main" + "/java"; String xmlDir = System.getProperty("user.dir") - + "/../ali-dbhub-server-domain/ali-dbhub-server-domain-repository/src/main" - + "/resources/com/alibaba/dbhub/server/domain/repository"; + + "/../chat2db-server-domain/chat2db-server-domain-repository/src/main" + + "/resources/ai/chat2db/server/domain/repository"; // 不要生成service controller Map pathInfo = new HashMap<>(); @@ -59,7 +58,7 @@ private void doGenerator(List tableList) { //全局配置 .globalConfig(builder -> { // 设置作者 - builder.author("ali-dbhub") + builder.author("chat2db") //执行完毕不打开文件夹 .disableOpenDir() // 指定输出目录 From 271df595789f5e42f60a418984a5db48d32eb26c Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 16:09:22 +0800 Subject: [PATCH 0511/1069] Add Team Interface --- .../repository/entity/DataSourceAccessDO.java | 66 +++++++++++++++++ .../repository/entity/DataSourceDO.java | 30 ++++---- .../repository/entity/EnvironmentDO.java | 66 +++++++++++++++++ .../domain/repository/entity/TeamDO.java | 71 +++++++++++++++++++ .../repository/entity/TeamDbhubUserDO.java | 61 ++++++++++++++++ .../domain/repository/entity/TeamRoleDO.java | 61 ++++++++++++++++ .../mapper/DataSourceAccessMapper.java | 16 +++++ .../repository/mapper/DataSourceMapper.java | 4 +- .../repository/mapper/EnvironmentMapper.java | 16 +++++ .../mapper/TeamDbhubUserMapper.java | 16 +++++ .../domain/repository/mapper/TeamMapper.java | 16 +++++ .../repository/mapper/TeamRoleMapper.java | 16 +++++ .../repository/DataSourceAccessMapper.xml | 5 ++ .../domain/repository/EnvironmentMapper.xml | 5 ++ .../domain/repository/TeamDbhubUserMapper.xml | 5 ++ .../server/domain/repository/TeamMapper.xml | 5 ++ .../domain/repository/TeamRoleMapper.xml | 5 ++ 17 files changed, 449 insertions(+), 15 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/DataSourceAccessMapper.xml create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/EnvironmentMapper.xml create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamMapper.xml create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java new file mode 100644 index 000000000..55ab3d3ab --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java @@ -0,0 +1,66 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 数据源授权 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +@Getter +@Setter +@TableName("DATA_SOURCE_ACCESS") +public class DataSourceAccessDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 数据源id + */ + private Long dataSourceId; + + /** + * 授权类型 + */ + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long accessObjectId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java index e8d3c0d1c..5d3040840 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java @@ -1,11 +1,10 @@ package ai.chat2db.server.domain.repository.entity; -import java.io.Serializable; -import java.time.LocalDateTime; - import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; @@ -14,8 +13,8 @@ * 数据源连接表 *

* - * @author ali-dbhub - * @since 2022-12-28 + * @author chat2db + * @since 2023-07-30 */ @Getter @Setter @@ -41,7 +40,7 @@ public class DataSourceDO implements Serializable { private LocalDateTime gmtModified; /** - * 数据源名称 + * 别名 */ private String alias; @@ -76,22 +75,22 @@ public class DataSourceDO implements Serializable { private Long userId; /** - * host + * host地址 */ private String host; /** - * port + * 端口 */ private String port; /** - * ssh + * ssh配置信息json */ private String ssh; /** - * ssh + * ssl配置信息json */ private String ssl; @@ -101,7 +100,7 @@ public class DataSourceDO implements Serializable { private String sid; /** - * driver + * 驱动信息 */ private String driver; @@ -111,12 +110,17 @@ public class DataSourceDO implements Serializable { private String jdbc; /** - * 扩展信息 + * 自定义扩展字段json */ private String extendInfo; /** - * 驱动配置 + * driver_config配置 */ private String driverConfig; + + /** + * 环境id + */ + private Long environmentId; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java new file mode 100644 index 000000000..b32f8a07e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java @@ -0,0 +1,66 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 数据库连接环境 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +@Getter +@Setter +@TableName("ENVIRONMENT") +public class EnvironmentDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 环境名称 + */ + private String name; + + /** + * 环境缩写 + */ + private String shortName; + + /** + * 样式类型 + */ + private String style; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java new file mode 100644 index 000000000..07f2a9ef4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java @@ -0,0 +1,71 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 团队 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +@Getter +@Setter +@TableName("TEAM") +public class TeamDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + /** + * 团队状态 + */ + private String status; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java new file mode 100644 index 000000000..3f9c614c2 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java @@ -0,0 +1,61 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 用户团队表 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +@Getter +@Setter +@TableName("TEAM_DBHUB_USER") +public class TeamDbhubUserDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 团队id + */ + private Long teamId; + + /** + * 用户id + */ + private Long dbhubUserId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java new file mode 100644 index 000000000..7baee194f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java @@ -0,0 +1,61 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.time.LocalDateTime; +import lombok.Getter; +import lombok.Setter; + +/** + *

+ * 团队角色表 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +@Getter +@Setter +@TableName("TEAM_ROLE") +public class TeamRoleDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 团队id + */ + private Long teamId; + + /** + * 角色编码 + */ + private String roleCode; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java new file mode 100644 index 000000000..f5664b87f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 数据源授权 Mapper 接口 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +public interface DataSourceAccessMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java index 5b8367779..43a192591 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java @@ -8,8 +8,8 @@ * 数据源连接表 Mapper 接口 *

* - * @author ali-dbhub - * @since 2022-12-28 + * @author chat2db + * @since 2023-07-30 */ public interface DataSourceMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java new file mode 100644 index 000000000..8762bcd6d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.EnvironmentDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 数据库连接环境 Mapper 接口 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +public interface EnvironmentMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java new file mode 100644 index 000000000..46182c229 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.TeamDbhubUserDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 用户团队表 Mapper 接口 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +public interface TeamDbhubUserMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java new file mode 100644 index 000000000..932d03b60 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.TeamDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 团队 Mapper 接口 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +public interface TeamMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java new file mode 100644 index 000000000..d853e566d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.TeamRoleDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * 团队角色表 Mapper 接口 + *

+ * + * @author chat2db + * @since 2023-07-30 + */ +public interface TeamRoleMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/DataSourceAccessMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/DataSourceAccessMapper.xml new file mode 100644 index 000000000..ee6ce37e1 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/DataSourceAccessMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/EnvironmentMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/EnvironmentMapper.xml new file mode 100644 index 000000000..ae4165812 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/EnvironmentMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml new file mode 100644 index 000000000..8308dde26 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamMapper.xml new file mode 100644 index 000000000..29780b01d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml new file mode 100644 index 000000000..b0d1bbe42 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml @@ -0,0 +1,5 @@ + + + + + From c9d3acde1e78650a860d393e4d4c263a002b52b6 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 30 Jul 2023 16:41:25 +0800 Subject: [PATCH 0512/1069] login bug fix --- .../server/web/api/controller/ai/AiConfigController.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java index b68da7471..c5eccfc6b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java @@ -2,6 +2,7 @@ import java.util.Objects; +import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; @@ -9,6 +10,7 @@ import ai.chat2db.server.tools.common.config.Chat2dbProperties; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; +import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.response.ApiKeyResponse; import ai.chat2db.server.web.api.http.response.InviteQrCodeResponse; @@ -64,6 +66,9 @@ public DataResult getLoginStatus(@RequestParam(required = false) QrCodeResponse qrCodeResponse = dataResult.getData(); // Representative successfully logged in if (StringUtils.isNotBlank(qrCodeResponse.getApiKey())) { + SystemConfigParam sqlSourceParam = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE) + .content(AiSqlSourceEnum.CHAT2DBAI.getCode()).build(); + configService.createOrUpdate(sqlSourceParam); SystemConfigParam param = SystemConfigParam.builder() .code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content(qrCodeResponse.getApiKey()) .build(); From 3f8aad9eb323e95664e8e7399966bd470c9cf1a7 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 17:05:35 +0800 Subject: [PATCH 0513/1069] feat: Optimize i18n --- .../src/components/SearchResult/TableBox/index.tsx | 8 ++++---- chat2db-client/src/i18n/en-us/workspace.ts | 4 ++++ chat2db-client/src/i18n/zh-cn/workspace.ts | 4 ++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 4ed7c415c..5d41a6789 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -61,7 +61,7 @@ export default function TableBox(props: ITableProps) { const items: MenuProps['items'] = useMemo( () => [ { - label: '导出全部数据为csv', + label: i18n('workspace.table.export.all.csv'), key: '1', // icon: , onClick: () => { @@ -69,7 +69,7 @@ export default function TableBox(props: ITableProps) { }, }, { - label: '导出全部数据为插入语句', + label: i18n('workspace.table.export.all.insert'), key: '2', // icon: , onClick: () => { @@ -77,7 +77,7 @@ export default function TableBox(props: ITableProps) { }, }, { - label: '导出当前页数据为csv', + label: i18n('workspace.table.export.cur.csv'), key: '3', // icon: , onClick: () => { @@ -85,7 +85,7 @@ export default function TableBox(props: ITableProps) { }, }, { - label: '导出当前页数据为插入语句', + label: i18n('workspace.table.export.cur.insert'), key: '4', // icon: , onClick: () => { diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 0c1e86d9a..4fea04001 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -11,4 +11,8 @@ export default { 'The table name you entered is not the same as the table name you want to delete, please confirm again', 'workspace.table.total': 'Total', 'workspace.table.total.tip': 'Load total number of rows', + 'workspace.table.export.all.csv': 'Export result set csv', + 'workspace.table.export.cur.csv': 'Export result of current page set csv', + 'workspace.table.export.all.insert': 'Export result set insert sql', + 'workspace.table.export.cur.insert': 'Export result of current page set insert sql', }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 33182677e..4025b522f 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -10,4 +10,8 @@ export default { 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', 'workspace.table.total': '总数', 'workspace.table.total.tip': '加载总行数', + 'workspace.table.export.all.csv': '导出结果集 csv', + 'workspace.table.export.cur.csv': '导出当前页结果集 csv', + 'workspace.table.export.all.insert': '导出结果集 insert sql', + 'workspace.table.export.cur.insert': '导出当前页结果集 insert sql', }; From cafb82a9bec2f8549c101ccd77a10ffb34830a0b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 17:10:59 +0800 Subject: [PATCH 0514/1069] feat: Optimize code --- chat2db-client/src/components/Console/index.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 5d2bf248f..e47a84e3b 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -382,11 +382,12 @@ function Console(props: IProps) { */ const handlePopUp = () => { setModalProps({ - imageUrl: 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp', + imageUrl: + 'http://oss.sqlgpt.cn/static/chat2db-wechat.jpg?x-oss-process=image/auto-orient,1/resize,m_lfit,w_256/quality,Q_80/format,webp', tip: ( <> {aiModel.remainingUse?.remainingUses === 0 &&

Key次数用完或者过期

} -

微信扫描二维码并关注公众号“每天”可以获得 25 次 AI 使用机会。

+

微信扫描二维码并关注公众号获得 AI 使用机会。

), }); @@ -432,7 +433,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> From 0871d53b2607d32551bb47c2471f239d393a1646 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 17:44:22 +0800 Subject: [PATCH 0515/1069] Add Team Interface --- .../chat2db-server-domain-api/pom.xml | 4 - .../api/enums/AccessObjectTypeEnum.java | 36 ++++++ .../domain/api/enums/EnvironmentEnum.java | 32 ++++++ .../api/enums/EnvironmentStyleEnum.java | 34 ++++++ .../server/domain/api/enums/RoleCodeEnum.java | 39 +++++++ .../domain/api/enums/ValidStatusEnum.java | 34 ++++++ .../server/domain/api/model/DataSource.java | 16 ++- .../domain/api/model/DataSourceAccess.java | 82 ++++++++++++++ .../api/model/DataSourceAccessObject.java | 49 ++++++++ .../server/domain/api/model/Environment.java | 48 ++++++++ .../chat2db/server/domain/api/model/User.java | 23 +++- .../domain/repository/entity/DbhubUserDO.java | 14 ++- .../domain/repository/entity/TeamDO.java | 5 + .../domain/repository/entity/TeamRoleDO.java | 61 ---------- .../repository/mapper/DbhubUserMapper.java | 4 +- .../repository/mapper/TeamRoleMapper.java | 16 --- .../domain/repository/TeamRoleMapper.xml | 5 - ...0\346\210\267\346\235\203\351\231\220.sql" | 29 ++--- .../test/mybatis/MybatisGeneratorTest.java | 2 +- .../chat2db-server-admin-api/pom.xml | 4 + .../controller/common/CommonController.java | 41 +++++++ .../request/CommonPageQueryRequest.java} | 6 +- .../request/TeamUserPageQueryRequest.java | 27 +++++ .../controller/common/vo/TeamUserListVO.java | 50 +++++++++ .../DataSourceAccessController.java | 51 ++------- .../datasource/DataSourceController.java | 4 +- .../converter/DataSourceAdminConverter.java | 12 +- .../DataSourceAccessBatchCreateRequest.java | 35 ++++++ .../DataSourceAccessObjectRequest.java | 41 +++++++ .../DataSourceAccessPageQueryRequest.java | 26 +++++ .../vo/DataSourceAccessObjectVO.java | 50 +++++++++ .../vo/DataSourceAccessPageQueryVO.java | 22 +++- .../datasource/vo/DataSourcePageQueryVO.java | 11 ++ .../user/DataSourceAccessController.java | 70 ++++++++++++ .../api/controller/user/UserController.java | 99 +++++++++++++++++ .../converter/DataSourceAdminConverter.java | 66 +++++++++++ .../DataSourceAccessBatchCreateRequest.java | 35 ++++++ .../DataSourceAccessObjectRequest.java | 41 +++++++ .../DataSourceAccessPageQueryRequest.java | 26 +++++ .../user/request/DataSourceCloneRequest.java | 19 ++++ .../user/request/DataSourceUpdateRequest.java | 105 ++++++++++++++++++ .../user/request/UserCreateRequest.java | 41 +++++++ .../user/vo/DataSourceAccessObjectVO.java | 50 +++++++++ .../user/vo/DataSourceAccessPageQueryVO.java | 40 +++++++ .../controller/user/vo/UserPageQueryVO.java | 39 +++++++ .../chat2db-server-common-api/pom.xml | 27 +++++ .../api/controller/vo/EnvironmentVO.java | 48 ++++++++ .../chat2db-server-web-api/pom.xml | 4 + chat2db-server/chat2db-server-web/pom.xml | 1 + chat2db-server/pom.xml | 10 ++ 50 files changed, 1468 insertions(+), 166 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AccessObjectTypeEnum.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentEnum.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/RoleCodeEnum.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ValidStatusEnum.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccessObject.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonController.java rename chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/{datasource/request/DataSourcePageQueryRequest.java => common/request/CommonPageQueryRequest.java} (56%) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/pom.xml create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/EnvironmentVO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/pom.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/pom.xml index b7a7d9bc7..e1cab189d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/pom.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/pom.xml @@ -17,10 +17,6 @@ ai.chat2db chat2db-server-tools-base - - - - org.projectlombok lombok diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AccessObjectTypeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AccessObjectTypeEnum.java new file mode 100644 index 000000000..222c05f0a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AccessObjectTypeEnum.java @@ -0,0 +1,36 @@ +package ai.chat2db.server.domain.api.enums; + +import ai.chat2db.server.tools.base.enums.BaseEnum; +import lombok.Getter; + +/** + * Access Object Type + * + * @author Jiaju Zhuang + */ +@Getter +public enum AccessObjectTypeEnum implements BaseEnum { + /** + * TEAM + */ + TEAM("TEAM"), + + /** + * USER + */ + USER("USER"), + + ; + + final String description; + + AccessObjectTypeEnum(String description) { + this.description = description; + } + + @Override + public String getCode() { + return this.name(); + } + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentEnum.java new file mode 100644 index 000000000..7f1586996 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentEnum.java @@ -0,0 +1,32 @@ +package ai.chat2db.server.domain.api.enums; + +import ai.chat2db.server.tools.base.enums.BaseEnum; +import lombok.Getter; + +/** + * Environment + * + * @author Jiaju Zhuang + */ +@Getter +public enum EnvironmentEnum implements BaseEnum { + /** + * RELEASE + */ + RELEASE(1L, "RELEASE"), + + /** + * TEST + */ + TEST(2L, "TEST"), + + ; + final Long code; + final String description; + + EnvironmentEnum(Long code, String description) { + this.code = code; + this.description = description; + } + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java new file mode 100644 index 000000000..1c5fa6a40 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.domain.api.enums; + +import ai.chat2db.server.tools.base.enums.BaseEnum; +import lombok.Getter; + +/** + * Environment + * + * @author Jiaju Zhuang + */ +@Getter +public enum EnvironmentStyleEnum implements BaseEnum { + /** + * RELEASE + */ + RELEASE("RELEASE"), + + /** + * TEST + */ + TEST("TEST"), + + ; + final String description; + + EnvironmentStyleEnum(String description) { + this.description = description; + } + + @Override + public String getCode() { + return this.name(); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/RoleCodeEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/RoleCodeEnum.java new file mode 100644 index 000000000..0c53a66c3 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/RoleCodeEnum.java @@ -0,0 +1,39 @@ +package ai.chat2db.server.domain.api.enums; + +import ai.chat2db.server.tools.base.enums.BaseEnum; +import lombok.Getter; + +/** + * role code + * + * @author Jiaju Zhuang + */ +@Getter +public enum RoleCodeEnum implements BaseEnum { + /** + * DESKTOP + */ + DESKTOP("DESKTOP"), + + /** + * USER + */ + USER("USER"), + + /** + * ADMIN + */ + ADMIN("ADMIN"), + + ; + final String description; + + RoleCodeEnum(String description) { + this.description = description; + } + + @Override + public String getCode() { + return this.name(); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ValidStatusEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ValidStatusEnum.java new file mode 100644 index 000000000..bd960fbea --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ValidStatusEnum.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.domain.api.enums; + +import ai.chat2db.server.tools.base.enums.BaseEnum; +import lombok.Getter; + +/** + * Is it a valid enumeration + * + * @author Jiaju Zhuang + */ +@Getter +public enum ValidStatusEnum implements BaseEnum { + /** + * VALID + */ + VALID("VALID"), + + /** + * INVALID + */ + INVALID("INVALID"), + + ; + final String description; + + ValidStatusEnum(String description) { + this.description = description; + } + + @Override + public String getCode() { + return this.name(); + } +} 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 b3b64a092..a34413085 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 @@ -99,7 +99,6 @@ public class DataSource { */ private String jdbc; - /** * 扩展信息 */ @@ -110,14 +109,23 @@ public class DataSource { */ private DriverConfig driverConfig; + /** + * 环境id + */ + private Long environmentId; + + /** + * 环境 + */ + private Environment environment; - public LinkedHashMap getExtendMap() { + public LinkedHashMap getExtendMap() { if (ObjectUtils.isEmpty(extendInfo)) { return new LinkedHashMap<>(); } - LinkedHashMap map = new LinkedHashMap<>(); + LinkedHashMap map = new LinkedHashMap<>(); for (KeyValue keyValue : extendInfo) { - map.put(keyValue.getKey(),keyValue.getValue()); + map.put(keyValue.getKey(), keyValue.getValue()); } return map; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java new file mode 100644 index 000000000..c0964d80b --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java @@ -0,0 +1,82 @@ +package ai.chat2db.server.domain.api.model; + +import java.io.Serial; +import java.io.Serializable; +import java.time.LocalDateTime; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccess implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 创建时间 + */ + @NotNull + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + @NotNull + private LocalDateTime gmtModified; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; + + /** + * 授权对象 + */ + @NotNull + private DataSourceAccessObject accessObject; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccessObject.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccessObject.java new file mode 100644 index 000000000..cf5648936 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccessObject.java @@ -0,0 +1,49 @@ +package ai.chat2db.server.domain.api.model; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObject implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * The name of the code that belongs to the authorization type, such as user account, team code + */ + private String code; + + /** + * Code that belongs to the authorization type, such as user name, team name + */ + private String name; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java new file mode 100644 index 000000000..ffb5971c5 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java @@ -0,0 +1,48 @@ +package ai.chat2db.server.domain.api.model; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.EnvironmentStyleEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Environment + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Environment implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + private Long id; + + /** + * 环境名称 + */ + private String name; + + /** + * 环境缩写 + */ + private String shortName; + + /** + * 样式类型 + * + * @see EnvironmentStyleEnum + */ + private String style; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java index 903e266fd..162dd5aa5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java @@ -1,5 +1,8 @@ package ai.chat2db.server.domain.api.model; +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -18,26 +21,44 @@ public class User { /** * 主键 */ + @NotNull private Long id; /** * 用户名 */ + @NotNull private String userName; /** * 密码 */ + @NotNull private String password; /** * 昵称 */ + @NotNull private String nickName; - /** * 邮箱 */ + @NotNull private String email; + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + private String roleCode; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + private String status; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java index 38555a486..a420dc8bf 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java @@ -13,8 +13,8 @@ * 数据源连接表 *

* - * @author ali-dbhub - * @since 2023-03-05 + * @author chat2db + * @since 2023-07-30 */ @Getter @Setter @@ -58,4 +58,14 @@ public class DbhubUserDO implements Serializable { * 邮箱 */ private String email; + + /** + * 角色编码 + */ + private String roleCode; + + /** + * 用户状态 + */ + private String status; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java index 07f2a9ef4..eb009d5ea 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java @@ -64,6 +64,11 @@ public class TeamDO implements Serializable { */ private String status; + /** + * 角色编码 + */ + private String roleCode; + /** * 团队描述 */ diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java deleted file mode 100644 index 7baee194f..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamRoleDO.java +++ /dev/null @@ -1,61 +0,0 @@ -package ai.chat2db.server.domain.repository.entity; - -import com.baomidou.mybatisplus.annotation.IdType; -import com.baomidou.mybatisplus.annotation.TableId; -import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.time.LocalDateTime; -import lombok.Getter; -import lombok.Setter; - -/** - *

- * 团队角色表 - *

- * - * @author chat2db - * @since 2023-07-30 - */ -@Getter -@Setter -@TableName("TEAM_ROLE") -public class TeamRoleDO implements Serializable { - - private static final long serialVersionUID = 1L; - - /** - * 主键 - */ - @TableId(value = "ID", type = IdType.AUTO) - private Long id; - - /** - * 创建时间 - */ - private LocalDateTime gmtCreate; - - /** - * 修改时间 - */ - private LocalDateTime gmtModified; - - /** - * 创建人用户id - */ - private Long createUserId; - - /** - * 修改人用户id - */ - private Long modifiedUserId; - - /** - * 团队id - */ - private Long teamId; - - /** - * 角色编码 - */ - private String roleCode; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java index 5ef7a09d5..c6be265bc 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java @@ -8,8 +8,8 @@ * 数据源连接表 Mapper 接口 *

* - * @author ali-dbhub - * @since 2023-03-05 + * @author chat2db + * @since 2023-07-30 */ public interface DbhubUserMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java deleted file mode 100644 index d853e566d..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamRoleMapper.java +++ /dev/null @@ -1,16 +0,0 @@ -package ai.chat2db.server.domain.repository.mapper; - -import ai.chat2db.server.domain.repository.entity.TeamRoleDO; -import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -/** - *

- * 团队角色表 Mapper 接口 - *

- * - * @author chat2db - * @since 2023-07-30 - */ -public interface TeamRoleMapper extends BaseMapper { - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml deleted file mode 100644 index b0d1bbe42..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamRoleMapper.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" index a25d426dd..3fd674b12 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -23,6 +23,16 @@ VALUES (2, 1, 1, '测试环境', '测试', 'TEST'); ALTER TABLE `data_source` ADD COLUMN `environment_id` bigint(20) unsigned NOT NULL DEFAULT 2 COMMENT '环境id'; +ALTER TABLE `dbhub_user` + ADD COLUMN `role_code` varchar(32) DEFAULT NULL COMMENT '角色编码'; + +ALTER TABLE `dbhub_user` + ADD `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '用户状态'; + +update dbhub_user +set role_code= 'DESKTOP' +where id = 1; + CREATE TABLE IF NOT EXISTS `team` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', @@ -33,6 +43,7 @@ CREATE TABLE IF NOT EXISTS `team` `code` varchar(128) DEFAULT NOT NULL COMMENT '团队编码', `name` varchar(512) DEFAULT NULL COMMENT '团队名称', `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '团队状态', + `role_code` varchar(32) DEFAULT NULL COMMENT '角色编码', `description` text DEFAULT NULL COMMENT '团队描述', PRIMARY KEY (`id`) ) ENGINE = InnoDB @@ -59,22 +70,6 @@ CREATE TABLE IF NOT EXISTS `team_dbhub_user` create INDEX idx_team_dbhub_user_team_id on team_dbhub_user (`team_id`); create INDEX idx_team_dbhub_user_dbhub_user_id on team_dbhub_user (`dbhub_user_id`); -CREATE TABLE IF NOT EXISTS `team_role` -( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', - `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', - `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', - `team_id` bigint(20) unsigned NOT NULL COMMENT '团队id', - `role_code` varchar(32) NOT NULL COMMENT '角色编码', - PRIMARY KEY (`id`) -) ENGINE = InnoDB - DEFAULT CHARSET = utf8mb4 COMMENT ='团队角色表' -; - -create INDEX team_role_team_id on team_role (`team_id`); - CREATE TABLE IF NOT EXISTS `data_source_access` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', @@ -91,4 +86,4 @@ CREATE TABLE IF NOT EXISTS `data_source_access` ; create INDEX data_source_access_data_source_id on data_source_access (`data_source_id`); -create INDEX data_source_access_access_object_id on data_source_access (`access_object_type`,`access_object_id`); +create INDEX data_source_access_access_object_id on data_source_access (`access_object_type`, `access_object_id`); diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index 7ec7b4eee..d8b8be607 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -32,7 +32,7 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("data_source")); //doGenerator(Lists.newArrayList("operation_log")); //doGenerator(Lists.newArrayList("operation_saved")); - doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","team_role","data_source_access")); + doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access","dbhub_user")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml index 7c1130c03..8497da780 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/pom.xml @@ -22,6 +22,10 @@ ai.chat2db chat2db-server-domain-api
+ + ai.chat2db + chat2db-server-common-api + ai.chat2db chat2db-server-domain-core diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonController.java new file mode 100644 index 000000000..5d1792a49 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonController.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.admin.api.controller.common; + +import ai.chat2db.server.admin.api.controller.common.request.TeamUserPageQueryRequest; +import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; +import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Some general data queries + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/common") +@RestController +public class CommonController { + + @Resource + private DataSourceService dataSourceService; + @Resource + private DataSourceAdminConverter dataSourceAdminConverter; + + /** + * Fuzzy query of users or teams + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/team-user/list") + public WebPageResult teamUserList(@Valid TeamUserPageQueryRequest request) { + return null; + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/CommonPageQueryRequest.java similarity index 56% rename from chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java rename to chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/CommonPageQueryRequest.java index ff2ad3018..f63813dce 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourcePageQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/CommonPageQueryRequest.java @@ -1,16 +1,16 @@ -package ai.chat2db.server.admin.api.controller.datasource.request; +package ai.chat2db.server.admin.api.controller.common.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; import lombok.Data; /** - * Pagination query + * Common pagination query * * @author Jiaju Zhuang */ @Data -public class DataSourcePageQueryRequest extends PageQueryRequest { +public class CommonPageQueryRequest extends PageQueryRequest { /** * searchKey diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java new file mode 100644 index 000000000..53a5c01af --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java @@ -0,0 +1,27 @@ + +package ai.chat2db.server.admin.api.controller.common.request; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import lombok.Data; + +/** + * Common pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserPageQueryRequest extends PageQueryRequest { + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java new file mode 100644 index 000000000..7b2b55f25 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java @@ -0,0 +1,50 @@ + +package ai.chat2db.server.admin.api.controller.common.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserListVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * The name of the code that belongs to the authorization type, such as user account, team code + */ + private String code; + + /** + * Code that belongs to the authorization type, such as user name, team name + */ + private String name; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java index ef3c04edf..2b6a6e87b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java @@ -2,16 +2,11 @@ package ai.chat2db.server.admin.api.controller.datasource; import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCloneRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourcePageQueryRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; -import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; -import ai.chat2db.server.domain.api.param.DataSourceCreateParam; -import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessPageQueryRequest; +import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourceAccessPageQueryVO; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -45,46 +40,20 @@ public class DataSourceAccessController { * @version 2.1.0 */ @GetMapping("/page") - public WebPageResult page(@Valid DataSourcePageQueryRequest request) { - return dataSourceService.queryPage(dataSourceAdminConverter.request2param(request), null) - .mapToWeb(dataSourceAdminConverter::dto2vo); + public WebPageResult page(@Valid DataSourceAccessPageQueryRequest request) { + return null; } /** - * create + * batch * * @param request * @return * @version 2.1.0 */ - @PostMapping("/create") - public DataResult create(@RequestBody DataSourceCreateRequest request) { - DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); - return dataSourceService.create(param); - } - - /** - * update - * - * @param request - * @return - * @version 2.1.0 - */ - @PostMapping("/update") - public ActionResult update(@RequestBody DataSourceUpdateRequest request) { - DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); - return dataSourceService.update(param); - } - - /** - * clone - * - * @param request - * @return - */ - @PostMapping("/clone") - public DataResult clone(@RequestBody DataSourceCloneRequest request) { - return dataSourceService.copyById(request.getId()); + @PostMapping("/batch-create") + public ActionResult batchCreate(@RequestBody DataSourceAccessBatchCreateRequest request) { + return null; } /** @@ -95,7 +64,7 @@ public DataResult clone(@RequestBody DataSourceCloneRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return dataSourceService.delete(id); + return null; } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java index af6968f01..8025fd03a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java @@ -1,10 +1,10 @@ package ai.chat2db.server.admin.api.controller.datasource; +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCloneRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourcePageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; import ai.chat2db.server.domain.api.param.DataSourceCreateParam; @@ -45,7 +45,7 @@ public class DataSourceController { * @version 2.1.0 */ @GetMapping("/page") - public WebPageResult page(@Valid DataSourcePageQueryRequest request) { + public WebPageResult page(@Valid CommonPageQueryRequest request) { return dataSourceService.queryPage(dataSourceAdminConverter.request2param(request), null) .mapToWeb(dataSourceAdminConverter::dto2vo); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java index 6c0f03dff..f537598f9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java @@ -1,7 +1,7 @@ package ai.chat2db.server.admin.api.controller.datasource.converter; +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourcePageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; import ai.chat2db.server.domain.api.model.DataSource; @@ -26,7 +26,15 @@ public abstract class DataSourceAdminConverter { * @param request * @return */ - public abstract DataSourcePageQueryParam request2param(DataSourcePageQueryRequest request); + public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * conversion + * + * @param request + * @return + */ + public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); /** * conversion diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java new file mode 100644 index 000000000..ed1f88440 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.util.List; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessBatchCreateRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DataSource Access Object + */ + @NotNull + @NotEmpty + private List accessObjectList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java new file mode 100644 index 000000000..e0bdf202e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectRequest implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java new file mode 100644 index 000000000..f833609b6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java @@ -0,0 +1,26 @@ + +package ai.chat2db.server.admin.api.controller.datasource.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Common pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryRequest extends PageQueryRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java new file mode 100644 index 000000000..4d1ccb914 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java @@ -0,0 +1,50 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * The name of the code that belongs to the authorization type, such as user account, team code + */ + private String code; + + /** + * Code that belongs to the authorization type, such as user name, team name + */ + private String name; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java index 6e4e046f7..6d2220dc6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java @@ -1,6 +1,8 @@ package ai.chat2db.server.admin.api.controller.datasource.vo; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -12,17 +14,27 @@ public class DataSourceAccessPageQueryVO { /** - * 主键id + * 主键 */ + @NotNull private Long id; + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; /** - * 连接别名 + * 授权id,根据类型区分是用户还是团队 */ - private String alias; + @NotNull + private Long accessObjectId; /** - * 连接地址 + * 授权对象 */ - private String url; + @NotNull + private DataSourceAccessObjectVO accessObject; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java index 5aff886e6..9b046cf72 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java @@ -1,6 +1,7 @@ package ai.chat2db.server.admin.api.controller.datasource.vo; +import ai.chat2db.server.common.api.controller.vo.EnvironmentVO; import lombok.Data; /** @@ -25,4 +26,14 @@ public class DataSourcePageQueryVO { * 连接地址 */ private String url; + + /** + * 环境id + */ + private Long environmentId; + + /** + * 环境 + */ + private EnvironmentVO environment; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java new file mode 100644 index 000000000..65233dab0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java @@ -0,0 +1,70 @@ + +package ai.chat2db.server.admin.api.controller.user; + +import ai.chat2db.server.admin.api.controller.user.converter.DataSourceAdminConverter; +import ai.chat2db.server.admin.api.controller.user.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.DataSourceAccessPageQueryRequest; +import ai.chat2db.server.admin.api.controller.user.vo.DataSourceAccessPageQueryVO; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Data Source Access Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/data/source/access") +@RestController +public class DataSourceAccessController { + + @Resource + private DataSourceService dataSourceService; + @Resource + private DataSourceAdminConverter dataSourceAdminConverter; + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid DataSourceAccessPageQueryRequest request) { + return null; + } + + /** + * batch + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/batch-create") + public ActionResult batchCreate(@RequestBody DataSourceAccessBatchCreateRequest request) { + return null; + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return null; + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java new file mode 100644 index 000000000..f02477467 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java @@ -0,0 +1,99 @@ + +package ai.chat2db.server.admin.api.controller.user; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.user.converter.DataSourceAdminConverter; +import ai.chat2db.server.admin.api.controller.user.request.DataSourceCloneRequest; +import ai.chat2db.server.admin.api.controller.user.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; +import ai.chat2db.server.domain.api.param.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * User Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/user") +@RestController +public class UserController { + + @Resource + private DataSourceService dataSourceService; + @Resource + private DataSourceAdminConverter dataSourceAdminConverter; + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid CommonPageQueryRequest request) { + return null; + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/create") + public DataResult create(@RequestBody UserCreateRequest request) { + DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); + return dataSourceService.create(param); + } + + /** + * update + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/update") + public ActionResult update(@RequestBody DataSourceUpdateRequest request) { + DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); + return dataSourceService.update(param); + } + + /** + * clone + * + * @param request + * @return + */ + @PostMapping("/clone") + public DataResult clone(@RequestBody DataSourceCloneRequest request) { + return dataSourceService.copyById(request.getId()); + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return dataSourceService.delete(id); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java new file mode 100644 index 000000000..9889d7b1e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java @@ -0,0 +1,66 @@ +package ai.chat2db.server.admin.api.controller.user.converter; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.user.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; +import ai.chat2db.server.domain.api.param.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.DataSourcePageQueryParam; +import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class DataSourceAdminConverter { + + /** + * conversion + * + * @param request + * @return + */ + public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * conversion + * + * @param request + * @return + */ + public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); + + ///** + // * conversion + // * + // * @param dto + // * @return + // */ + //public abstract DataSourcePageQueryVO dto2vo(DataSource dto); + + /** + * 参数转换 + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "user", target = "userName") + }) + public abstract DataSourceCreateParam createReq2param(UserCreateRequest request); + + /** + * 参数转换 + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "user", target = "userName") + }) + public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java new file mode 100644 index 000000000..57e4e1ae9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import java.util.List; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessBatchCreateRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DataSource Access Object + */ + @NotNull + @NotEmpty + private List accessObjectList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java new file mode 100644 index 000000000..5ec7b1d50 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.admin.api.controller.user.request; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectRequest implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java new file mode 100644 index 000000000..217d14313 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java @@ -0,0 +1,26 @@ + +package ai.chat2db.server.admin.api.controller.user.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Common pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryRequest extends PageQueryRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java new file mode 100644 index 000000000..7edc4f8cb --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.admin.api.controller.user.request; + + +import lombok.Data; + +/** + * @author moji + * @version ConnectionCloneRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceCloneRequest { + + /** + * 主键id + */ + private Long id; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java new file mode 100644 index 000000000..99d2469ac --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java @@ -0,0 +1,105 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import java.util.List; + +import ai.chat2db.server.tools.base.enums.EnvTypeEnum; +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceUpdateRequest { + + /** + * 主键id + */ + @NotNull + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 连接用户 + */ + private String user; + + /** + * 密码 + */ + private String password; + + /** + * 连接类型 + */ + private String type; + + /** + * 环境类型 + * @see EnvTypeEnum + */ + private String envType; + + + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java new file mode 100644 index 000000000..7bd6de285 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java @@ -0,0 +1,41 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + *@author Jiaju Zhuang + */ +@Data +public class UserCreateRequest { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java new file mode 100644 index 000000000..db324b0f0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java @@ -0,0 +1,50 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * The name of the code that belongs to the authorization type, such as user account, team code + */ + private String code; + + /** + * Code that belongs to the authorization type, such as user name, team name + */ + private String name; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java new file mode 100644 index 000000000..f10e6f935 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java @@ -0,0 +1,40 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryVO { + + /** + * 主键 + */ + @NotNull + private Long id; + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; + + /** + * 授权对象 + */ + @NotNull + private DataSourceAccessObjectVO accessObject; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java new file mode 100644 index 000000000..490032b6e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java @@ -0,0 +1,39 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserPageQueryVO { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-common-api/pom.xml new file mode 100644 index 000000000..19f74f79b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/pom.xml @@ -0,0 +1,27 @@ + + + + ai.chat2db + chat2db-server-web + ${revision} + ../pom.xml + + 4.0.0 + chat2db-server-common-api + jar + chat2db-server-common-api + + + + ai.chat2db + chat2db-server-tools-common + + + ai.chat2db + chat2db-server-domain-api + + + + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/EnvironmentVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/EnvironmentVO.java new file mode 100644 index 000000000..f81d4f6c2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/EnvironmentVO.java @@ -0,0 +1,48 @@ +package ai.chat2db.server.common.api.controller.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.EnvironmentStyleEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Environment + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class EnvironmentVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + private Long id; + + /** + * 环境名称 + */ + private String name; + + /** + * 环境缩写 + */ + private String shortName; + + /** + * 样式类型 + * + * @see EnvironmentStyleEnum + */ + private String style; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index dc601d55f..fb7d53f61 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -22,6 +22,10 @@ ai.chat2db chat2db-server-domain-api + + ai.chat2db + chat2db-server-common-api + ai.chat2db chat2db-server-domain-core diff --git a/chat2db-server/chat2db-server-web/pom.xml b/chat2db-server/chat2db-server-web/pom.xml index 2db39633e..6610a1e16 100644 --- a/chat2db-server/chat2db-server-web/pom.xml +++ b/chat2db-server/chat2db-server-web/pom.xml @@ -15,6 +15,7 @@ chat2db-server-web-api chat2db-server-admin-api + chat2db-server-common-api \ No newline at end of file diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 2e6885f90..6990637ac 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -55,6 +55,16 @@ chat2db-server-web-api ${revision} + + ai.chat2db + chat2db-server-admin-api + ${revision} + + + ai.chat2db + chat2db-server-common-api + ${revision} + ai.chat2db chat2db-server-domain-api From 41791e088672dc4058e9eedbe2629d172ea953a5 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 17:56:03 +0800 Subject: [PATCH 0516/1069] Add Team Interface --- .../chat2db/server/domain/api/model/User.java | 1 + .../api/controller/user/UserController.java | 27 ++------ .../controller/user/UserTeamController.java | 63 +++++++++++++++++++ .../user/request/UserCreateRequest.java | 15 +++-- .../user/request/UserUpdateRequest.java | 50 +++++++++++++++ 5 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java index 162dd5aa5..67e31c213 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java @@ -60,5 +60,6 @@ public class User { * * @see ValidStatusEnum */ + @NotNull private String status; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java index f02477467..c833b6839 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserController.java @@ -3,12 +3,9 @@ import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.user.converter.DataSourceAdminConverter; -import ai.chat2db.server.admin.api.controller.user.request.DataSourceCloneRequest; -import ai.chat2db.server.admin.api.controller.user.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; -import ai.chat2db.server.domain.api.param.DataSourceCreateParam; -import ai.chat2db.server.domain.api.param.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -58,8 +55,8 @@ public WebPageResult page(@Valid CommonPageQueryRequest request */ @PostMapping("/create") public DataResult create(@RequestBody UserCreateRequest request) { - DataSourceCreateParam param = dataSourceAdminConverter.createReq2param(request); - return dataSourceService.create(param); + return null; + } /** @@ -70,20 +67,8 @@ public DataResult create(@RequestBody UserCreateRequest request) { * @version 2.1.0 */ @PostMapping("/update") - public ActionResult update(@RequestBody DataSourceUpdateRequest request) { - DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); - return dataSourceService.update(param); - } - - /** - * clone - * - * @param request - * @return - */ - @PostMapping("/clone") - public DataResult clone(@RequestBody DataSourceCloneRequest request) { - return dataSourceService.copyById(request.getId()); + public ActionResult update(@RequestBody UserUpdateRequest request) { + return null; } /** @@ -94,6 +79,6 @@ public DataResult clone(@RequestBody DataSourceCloneRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return dataSourceService.delete(id); + return null; } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java new file mode 100644 index 000000000..7dd7726d0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java @@ -0,0 +1,63 @@ + +package ai.chat2db.server.admin.api.controller.user; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * User Team Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/user/team") +@RestController +public class UserTeamController { + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid CommonPageQueryRequest request) { + return null; + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/create") + public DataResult create(@RequestBody UserCreateRequest request) { + return null; + + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java index 7bd6de285..7634c2c28 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java @@ -1,5 +1,6 @@ package ai.chat2db.server.admin.api.controller.user.request; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -9,12 +10,6 @@ */ @Data public class UserCreateRequest { - /** - * 主键 - */ - @NotNull - private Long id; - /** * 用户名 */ @@ -38,4 +33,12 @@ public class UserCreateRequest { */ @NotNull private String email; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java new file mode 100644 index 000000000..1bec4dd57 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java @@ -0,0 +1,50 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + *@author Jiaju Zhuang + */ +@Data +public class UserUpdateRequest { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} From c91ac6df1be37f0367fdc76836d4b8e2eaadd70b Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 18:27:23 +0800 Subject: [PATCH 0517/1069] Modify packaging script --- .github/workflows/release.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21c7981e1..9ab87a2a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -285,7 +285,7 @@ jobs: content: | { "title": "Windows-release-打包完成通知", - "text": "# Windows-release-打包完成通知 \n ![bang]http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe]http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " + "text": "# Windows-release-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " } # 构建完成通知 @@ -298,7 +298,7 @@ jobs: content: | { "title": "MacOS-amd64-release-构建完成通知", - "text": "# MacOS-amd64-release-打包完成通知 \n ![bang]http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg]http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip]http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) " + "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) " } # 构建完成通知 @@ -311,7 +311,7 @@ jobs: content: | { "title": "MacOS-arm64-release-构建完成通知", - "text": "# MacOS-arm64-release-打包完成通知 \n ![bang]http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg]http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " + "text": "# MacOS-arm64-release-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " } # 构建完成通知 @@ -324,5 +324,5 @@ jobs: content: | { "title": "Linux-test-打包完成通知", - "text": "# Linux-test-打包完成通知 \n ![bang]http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[https://oss-chat2db.alibaba.com/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage]http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage)" + "text": "# Linux-test-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage)" } From a07188aece9c099b6899b699d764b7cedf3f3187 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 18:39:59 +0800 Subject: [PATCH 0518/1069] Modify encoding --- .../server/web/api/controller/rdb/RdbDmlExportController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java index b9f3f5001..1a8ffc208 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java @@ -118,6 +118,7 @@ private void doExportCsv(String sql, HttpServletResponse response, String fileNa ExcelWrapper excelWrapper = new ExcelWrapper(); try { ExcelWriterBuilder excelWriterBuilder = EasyExcel.write(response.getOutputStream()) + .charset(StandardCharsets.UTF_8) .excelType(ExcelTypeEnum.CSV); excelWrapper.setExcelWriterBuilder(excelWriterBuilder); SQLExecutor.getInstance().executeSql(Chat2DBContext.getConnection(), sql, headerList -> { From 3d4e608197995041df109f830bd5d0bea9ece012 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 18:54:10 +0800 Subject: [PATCH 0519/1069] fix: fix ai bug --- .../src/components/Console/index.tsx | 37 +++++++++---------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index e47a84e3b..d1073830b 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -345,26 +345,23 @@ function Console(props: IProps) { }); }; - const addAction = useMemo( - () => [ - { - id: 'explainSQL', - label: i18n('common.text.explainSQL'), - action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_EXPLAIN), - }, - { - id: 'optimizeSQL', - label: i18n('common.text.optimizeSQL'), - action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_OPTIMIZER), - }, - { - id: 'changeSQL', - label: i18n('common.text.conversionSQL'), - action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_2_SQL), - }, - ], - [], - ); + const addAction = [ + { + id: 'explainSQL', + label: i18n('common.text.explainSQL'), + action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_EXPLAIN), + }, + { + id: 'optimizeSQL', + label: i18n('common.text.optimizeSQL'), + action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_OPTIMIZER), + }, + { + id: 'changeSQL', + label: i18n('common.text.conversionSQL'), + action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_2_SQL), + }, + ]; const handleClickRemainBtn = async () => { if (!isChat2DBAi) return; From 165860b26b901822deebace3db1f5c02c2db09c5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 30 Jul 2023 20:05:20 +0800 Subject: [PATCH 0520/1069] fix: fix ai in editor bug --- .../src/components/Console/index.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index d1073830b..071ed1d04 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -11,13 +11,14 @@ import aiServer from '@/service/ai'; import { v4 as uuidv4 } from 'uuid'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import Iconfont from '../Iconfont'; -import { ITreeNode } from '@/typings'; +import { IAiConfig, ITreeNode } from '@/typings'; import { IAIState } from '@/models/ai'; import Popularize from '@/components/Popularize'; import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; import { AiSqlSourceType } from '@/typings/ai'; import i18n from '@/i18n'; +import configService from '@/service/config'; import styles from './index.less'; enum IPromptType { @@ -213,8 +214,14 @@ function Console(props: IProps) { } }; - const handleAiChat = async (content: string, promptType: IPromptType) => { - const { apiKey } = aiModel?.aiConfig || {}; + const handleAIChatInEditor = async (content: string, promptType: IPromptType) => { + const aiConfig = await configService.getAiSystemConfig({}); + handleAiChat(content, promptType, aiConfig); + }; + + const handleAiChat = async (content: string, promptType: IPromptType, aiConfig?: IAiConfig) => { + const { apiKey, aiSqlSource } = aiConfig || props.aiModel?.aiConfig || {}; + const isChat2DBAi = aiSqlSource === AiSqlSourceType.CHAT2DBAI; if (!apiKey && isChat2DBAi) { handleApiKeyEmptyOrGetQrCode(true); return; @@ -349,17 +356,17 @@ function Console(props: IProps) { { id: 'explainSQL', label: i18n('common.text.explainSQL'), - action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_EXPLAIN), + action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_EXPLAIN), }, { id: 'optimizeSQL', label: i18n('common.text.optimizeSQL'), - action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_OPTIMIZER), + action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_OPTIMIZER), }, { id: 'changeSQL', label: i18n('common.text.conversionSQL'), - action: (selectedText: string) => handleAiChat(selectedText, IPromptType.SQL_2_SQL), + action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_2_SQL), }, ]; From 5c02478b610ca21715f65e465bac7fae15e14386 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 30 Jul 2023 20:24:51 +0800 Subject: [PATCH 0521/1069] Auto Upgrade --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d9856c23c..ea6cc5097 100644 --- a/README.md +++ b/README.md @@ -169,7 +169,7 @@ $ yarn run start:web $ cd ../chat2db-server $ mvn clean install # maven 3.8 or later needs to be installed $ cd chat2db-server/chat2db-server-start/target/ -$ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. +$ java -jar -Dloader.path=/lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. ``` ## 📑 Documentation From 02043732b7f77381446764e3528c426ab1c032d2 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 30 Jul 2023 20:41:53 +0800 Subject: [PATCH 0522/1069] release --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0326f5ed2..1bd4fb26d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # 2.0.7 + ## ⭐ New Features - Export query result as file is supported @@ -7,12 +8,20 @@ - Fixed ai config issues [Issue #346](https://github.com/chat2db/Chat2DB/issues/346) +## ⭐ 新特性 + +- 支持导出查询结果 + +## 🐞 问题修复 + +- 修复ai配置 [Issue #346](https://github.com/chat2db/Chat2DB/issues/346) # 2.0.6 ## 🐞 Bug Fixes -- Fixed: When there are too many tables under the selected library, the "New Console" button at the bottom disappears [Issue #314](https://github.com/chat2db/Chat2DB/issues/314) +- Fixed: When there are too many tables under the selected library, the "New Console" button at the bottom + disappears [Issue #314](https://github.com/chat2db/Chat2DB/issues/314) ## 🐞 问题修复 From f69b8ed2657e26dd88e9e0a61984b54be5aac822 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 2 Aug 2023 19:12:52 +0800 Subject: [PATCH 0523/1069] Return update duration --- .../server/web/api/controller/rdb/vo/ExecuteResultVO.java | 5 +++++ .../src/main/java/ai/chat2db/spi/sql/SQLExecutor.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java index 912b9b3df..6ca3ad254 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java @@ -38,6 +38,11 @@ public class ExecuteResultVO { */ private Boolean success; + /** + * 修改行数 查询sql不会返回 + */ + private Integer updateCount; + /** * 展示头的列表 */ diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 6c8928251..8b38199d1 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -184,11 +184,11 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL } } executeResult.setDuration(timeInterval.interval()); - return executeResult; } finally { JdbcUtils.closeResultSet(rs); } } else { + executeResult.setDuration(timeInterval.interval()); // 修改或者其他 executeResult.setUpdateCount(stmt.getUpdateCount()); } From 0ea003cb8e2f54485884779cc28ce35f17c40249 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 2 Aug 2023 19:36:43 +0800 Subject: [PATCH 0524/1069] Repair the Scientific notation in some databases --- CHANGELOG.md | 10 +++++++++ .../java/ai/chat2db/spi/util/JdbcUtils.java | 22 ++++++++++++++----- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bd4fb26d..388921e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.0.8 + +## 🐞 Bug Fixes + +- Repair the Scientific notation in some databases [Issue #378](https://github.com/chat2db/Chat2DB/issues/378) + +## 🐞 问题修复 + +- 修复部分数据库出现科学计数法的情况 [Issue #378](https://github.com/chat2db/Chat2DB/issues/378) + # 2.0.7 ## ⭐ New Features 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 c14d04290..e157eb1b5 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,5 +1,6 @@ package ai.chat2db.spi.util; +import java.math.BigDecimal; import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; @@ -180,6 +181,15 @@ public static String getResultSetValue(ResultSet rs, int index) throws SQLExcept if (obj instanceof LocalDate localDate) { return Objects.toString(localDate); } + if (obj instanceof BigDecimal bigDecimal) { + return bigDecimal.toPlainString(); + } + if (obj instanceof Double d) { + return BigDecimal.valueOf(d).toPlainString(); + } + if (obj instanceof Float f) { + return BigDecimal.valueOf(f).toPlainString(); + } if (obj instanceof Number num) { return Objects.toString(num); } @@ -196,11 +206,11 @@ public static String getResultSetValue(ResultSet rs, int index) throws SQLExcept * @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; // 加载驱动 @@ -213,7 +223,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); @@ -235,7 +245,7 @@ public static DataSourceConnect testConnect(String url, String host, String port } if (session != null) { try { - if(StringUtils.isNotBlank(ssh.getLocalPort())) { + if (StringUtils.isNotBlank(ssh.getLocalPort())) { session.delPortForwardingL(Integer.parseInt(ssh.getLocalPort())); } session.disconnect(); From 4187d9b488ea8ca146290c59c191da71239abcbb Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 2 Aug 2023 19:43:17 +0800 Subject: [PATCH 0525/1069] Fix some cases where data is not displayed --- CHANGELOG.md | 2 ++ chat2db-client/src/components/SearchResult/TableBox/index.tsx | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 388921e40..c5e666e51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,12 @@ ## 🐞 Bug Fixes - Repair the Scientific notation in some databases [Issue #378](https://github.com/chat2db/Chat2DB/issues/378) +- Fix some cases where data is not displayed ## 🐞 问题修复 - 修复部分数据库出现科学计数法的情况 [Issue #378](https://github.com/chat2db/Chat2DB/issues/378) +- 修复部分情况数据不展示 # 2.0.7 diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 5d41a6789..59ec8467b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -180,7 +180,6 @@ export default function TableBox(props: ITableProps) { rowData[columns[index].name] = i; } }); - rowData.key = rowIndex; return rowData; }); } From 0da0d919caff04d1e0c44f094e1023bafcd60eaa Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 2 Aug 2023 19:47:09 +0800 Subject: [PATCH 0526/1069] Upgrade OSS to HTTPS --- .github/workflows/release.yml | 8 ++++---- .github/workflows/release_test.yml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ab87a2a7..7310d0003 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -285,7 +285,7 @@ jobs: content: | { "title": "Windows-release-打包完成通知", - "text": "# Windows-release-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " + "text": "# Windows-release-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB%20Setup%20${{ steps.chat2db_version.outputs.substring }}.exe) " } # 构建完成通知 @@ -298,7 +298,7 @@ jobs: content: | { "title": "MacOS-amd64-release-构建完成通知", - "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) " + "text": "# MacOS-amd64-release-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/chat2db-server-start.zip) " } # 构建完成通知 @@ -311,7 +311,7 @@ jobs: content: | { "title": "MacOS-arm64-release-构建完成通知", - "text": "# MacOS-arm64-release-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " + "text": "# MacOS-arm64-release-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg) " } # 构建完成通知 @@ -324,5 +324,5 @@ jobs: content: | { "title": "Linux-test-打包完成通知", - "text": "# Linux-test-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage](http://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage)" + "text": "# Linux-test-打包完成通知 \n ![bang](https://oss.sqlgpt.cn/static/happy100.jpg) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage](https://oss.sqlgpt.cn/release/${{ steps.chat2db_version.outputs.substring }}/Chat2DB-${{ steps.chat2db_version.outputs.substring }}.AppImage)" } diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index f4fad7d0b..9aa98e916 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -274,7 +274,7 @@ jobs: content: | { "title": "Windows-test-打包完成通知", - "text": "# Windows-test-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Windows下载地址:[http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe](http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.0.${{ github.run_id }}-Test.exe) " + "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) " } # 构建完成通知 @@ -287,7 +287,7 @@ jobs: content: | { "title": "MacOS-amd64-test-构建完成通知", - "text": "# MacOS-amd64-test-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Intel芯片下载地址:[http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg](http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg) \n ### jar包下载地址:[http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/chat2db-server-start.zip](http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/${{ github.run_id }}/chat2db-server-start.zip) " + "text": "# MacOS-amd64-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 }}/${{ github.run_id }}/chat2db-server-start.zip) " } # 构建完成通知 @@ -300,7 +300,7 @@ jobs: content: | { "title": "MacOS-arm64-test-构建完成通知", - "text": "# MacOS-arm64-test-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Apple芯片下载地址:[http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg](http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg) " + "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) " } # 构建完成通知 @@ -313,5 +313,5 @@ jobs: content: | { "title": "Linux-test-打包完成通知", - "text": "# Linux-test-打包完成通知 \n ![bang](http://oss.sqlgpt.cn/static/bang100.gif) \n ### 任务id:[${{ github.run_id }}](https://github.com/chat2db/Chat2DB/actions/runs/${{ github.run_id }}) \n ### Linux下载地址:[http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage](http://oss.sqlgpt.cn/test/99.0.${{ github.run_id }}/Chat2DB-Test-99.0.${{ github.run_id }}-Test.AppImage) " + "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) " } From f2fef4d15cb2d461b76c7a5f27afcbda53a9315d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 2 Aug 2023 21:33:43 +0800 Subject: [PATCH 0527/1069] =?UTF-8?q?feat:=20=E5=8D=87=E7=BA=A7electron?= =?UTF-8?q?=E5=88=B022.3.18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/package.json | 2 +- chat2db-client/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 78c21f787..e84dceaab 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -52,7 +52,7 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", - "electron": "^22.0.0", + "electron": "^22.3.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index b7508191f..9d89d599b 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -4732,10 +4732,10 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== -electron@^22.0.0: - version "22.3.17" - resolved "https://registry.npmmirror.com/electron/-/electron-22.3.17.tgz#90a75f78cc761ed536d8210dd001e142fca78691" - integrity sha512-mo9qD1pOkiibvH+pgETsq9RZF0p3O5ACwxzjk3E2ozMYb9cnJenZyE3jxbs4WqzDCFi+rsm6WWahw3hlPhANXw== +electron@^22.3.0: + version "22.3.18" + resolved "https://registry.npmmirror.com/electron/-/electron-22.3.18.tgz#5ee55633b3912fec9df6d8f039acf2c016274cfc" + integrity sha512-JgjB966ghTBszAX/GgVgDY/2CktWCjTZWGJI0WISRHDudBZ8/WPkI/hIjsMiLQLe0wSTk6S+WHOYbIqyw0I/sg== dependencies: "@electron/get" "^2.0.0" "@types/node" "^16.11.26" From 352d97bb3ebeeec5b21323c1a95b910ab98b96f9 Mon Sep 17 00:00:00 2001 From: "fanjin.fjy" Date: Wed, 2 Aug 2023 21:59:20 +0800 Subject: [PATCH 0528/1069] feat: optimize --- .../SearchResult/TableBox/index.tsx | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 59ec8467b..841ca854f 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -49,7 +49,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` export default function TableBox(props: ITableProps) { const { className, data, config, onConfigChange, onSearchTotal } = props; - const { headerList, dataList, duration, description } = data || {}; + const { headerList, dataList, duration, description, sqlType } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); @@ -175,7 +175,7 @@ export default function TableBox(props: ITableProps) { if (type === TableDataType.DATETIME && i) { rowData[columns[index].name] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); } else if (i === null) { - rowData[columns[index].name] = ''; + rowData[columns[index].name] = ''; } else { rowData[columns[index].name] = i; } @@ -219,9 +219,24 @@ export default function TableBox(props: ITableProps) { return await props.onSearchTotal(); } }; - return ( -
- {columns.length ? ( + const renderContent = () => { + const bottomStatus = ( +
+ {`【${i18n('common.text.result')}】${description}.`} + {`【${i18n('common.text.timeConsuming')}】${duration}ms.`} + {`【${i18n('common.text.searchRow')}】${tableData.length} ${i18n('common.text.row')}.`} +
+ ); + + if (!columns.length || sqlType !== 'SELECT') { + return ( + <> + +
{bottomStatus}
+ + ); + } else { + return ( <>
@@ -249,15 +264,15 @@ export default function TableBox(props: ITableProps) { stickyTop={31} {...pipeline.getProps()} /> -
- {`【${i18n('common.text.result')}】${description}.`} - {`【${i18n('common.text.timeConsuming')}】${duration}ms.`} - {`【${i18n('common.text.searchRow')}】${tableData.length} ${i18n('common.text.row')}.`} -
+ {bottomStatus} - ) : ( - - )} + ); + } + }; + return ( +
+ {renderContent()} + Date: Thu, 3 Aug 2023 08:44:25 +0800 Subject: [PATCH 0529/1069] test electron v25 --- chat2db-client/package.json | 2 +- chat2db-client/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index e84dceaab..d9f7eb2fc 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -52,7 +52,7 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", - "electron": "^22.3.0", + "electron": "^25.3.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 9d89d599b..753f5146d 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -2421,10 +2421,10 @@ resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== -"@types/node@^16.11.26": - version "16.18.38" - resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.38.tgz#1dcdb6c54d02b323f621213745f2e44af30c73e6" - integrity sha512-6sfo1qTulpVbkxECP+AVrHV9OoJqhzCsfTNp5NIG+enM4HyM3HvZCO798WShIXBN0+QtDIcutJCjsVYnQP5rIQ== +"@types/node@^18.11.18": + version "18.17.1" + resolved "https://registry.npmmirror.com/@types/node/-/node-18.17.1.tgz#84c32903bf3a09f7878c391d31ff08f6fe7d8335" + integrity sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw== "@types/parse-json@^4.0.0": version "4.0.0" @@ -4732,13 +4732,13 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== -electron@^22.3.0: - version "22.3.18" - resolved "https://registry.npmmirror.com/electron/-/electron-22.3.18.tgz#5ee55633b3912fec9df6d8f039acf2c016274cfc" - integrity sha512-JgjB966ghTBszAX/GgVgDY/2CktWCjTZWGJI0WISRHDudBZ8/WPkI/hIjsMiLQLe0wSTk6S+WHOYbIqyw0I/sg== +electron@^25.3.0: + version "25.4.0" + resolved "https://registry.npmmirror.com/electron/-/electron-25.4.0.tgz#d45b1cf3e4e96eb5bff5fee704d7aa13b532f3a5" + integrity sha512-VLTRxDhL4UvQbqM7pTNENnJo62cdAPZT92N+B7BZQ5Xfok1wuVPEewIjBot4K7U3EpLUuHn1veeLzho3ihiP+Q== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^16.11.26" + "@types/node" "^18.11.18" extract-zip "^2.0.1" elliptic@^6.5.3: From 6e2dacef0ea2d535219e9cb15cd26187cd897608 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 3 Aug 2023 08:47:17 +0800 Subject: [PATCH 0530/1069] rollback electron version to 22 --- chat2db-client/package.json | 2 +- chat2db-client/yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index d9f7eb2fc..e84dceaab 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -52,7 +52,7 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", - "electron": "^25.3.0", + "electron": "^22.3.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 753f5146d..4900ae4e9 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -2421,10 +2421,10 @@ resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== -"@types/node@^18.11.18": - version "18.17.1" - resolved "https://registry.npmmirror.com/@types/node/-/node-18.17.1.tgz#84c32903bf3a09f7878c391d31ff08f6fe7d8335" - integrity sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw== +"@types/node@^16.11.26": + version "16.18.39" + resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.39.tgz#aa39a1a87a40ef6098ee69689a1acb0c1b034832" + integrity sha512-8q9ZexmdYYyc5/cfujaXb4YOucpQxAV4RMG0himLyDUOEr8Mr79VrqsFI+cQ2M2h89YIuy95lbxuYjxT4Hk4kQ== "@types/parse-json@^4.0.0": version "4.0.0" @@ -4732,13 +4732,13 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== -electron@^25.3.0: - version "25.4.0" - resolved "https://registry.npmmirror.com/electron/-/electron-25.4.0.tgz#d45b1cf3e4e96eb5bff5fee704d7aa13b532f3a5" - integrity sha512-VLTRxDhL4UvQbqM7pTNENnJo62cdAPZT92N+B7BZQ5Xfok1wuVPEewIjBot4K7U3EpLUuHn1veeLzho3ihiP+Q== +electron@^22.3.0: + version "22.3.18" + resolved "https://registry.npmmirror.com/electron/-/electron-22.3.18.tgz#5ee55633b3912fec9df6d8f039acf2c016274cfc" + integrity sha512-JgjB966ghTBszAX/GgVgDY/2CktWCjTZWGJI0WISRHDudBZ8/WPkI/hIjsMiLQLe0wSTk6S+WHOYbIqyw0I/sg== dependencies: "@electron/get" "^2.0.0" - "@types/node" "^18.11.18" + "@types/node" "^16.11.26" extract-zip "^2.0.1" elliptic@^6.5.3: From f8ad2346638b276f8bec32b279bf4060e9fdca13 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 3 Aug 2023 09:35:14 +0800 Subject: [PATCH 0531/1069] feat: add disable-gpu ,try to fix crash on some win11 --- chat2db-client/src/main/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index 4c45f579f..1ed9fe5bc 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -47,6 +47,8 @@ function createWindow() { // const menu = Menu.buildFromTemplate(menuBar); // Menu.setApplicationMenu(menu); +app.commandLine.appendSwitch('disable-gpu'); + app.on('ready', () => { createWindow(); registerAppMenu(); From aa441be2494d735fbc71880dd3108fb33505b5f0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 3 Aug 2023 09:36:59 +0800 Subject: [PATCH 0532/1069] rollback electron version to 22 --- chat2db-client/src/main/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index 1ed9fe5bc..ff82e78f4 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -47,7 +47,7 @@ function createWindow() { // const menu = Menu.buildFromTemplate(menuBar); // Menu.setApplicationMenu(menu); -app.commandLine.appendSwitch('disable-gpu'); +app.commandLine.appendSwitch('--disable-gpu-sandbox'); app.on('ready', () => { createWindow(); From 35ca767d1edc819c66343faf948c7a611bd7dbb8 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 3 Aug 2023 09:36:59 +0800 Subject: [PATCH 0533/1069] feat:add disable-gpu ,try to fix crash on some win11 --- chat2db-client/src/main/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index 1ed9fe5bc..ff82e78f4 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -47,7 +47,7 @@ function createWindow() { // const menu = Menu.buildFromTemplate(menuBar); // Menu.setApplicationMenu(menu); -app.commandLine.appendSwitch('disable-gpu'); +app.commandLine.appendSwitch('--disable-gpu-sandbox'); app.on('ready', () => { createWindow(); From e3850964b8e6055e59281f4ca468de4dfa263005 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Thu, 3 Aug 2023 11:51:28 +0800 Subject: [PATCH 0534/1069] Auto Upgrade --- README_CN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README_CN.md b/README_CN.md index 4450f6a9b..de74739af 100644 --- a/README_CN.md +++ b/README_CN.md @@ -162,7 +162,7 @@ $ yarn run start:web $ cd ../chat2db-server $ mvn clean install # 需要安装maven 3.8以上版本 $ cd chat2db-server/chat2db-server-start/target/ -$ java -jar -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 +$ java -jar -Dloader.path=/lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 ``` ## 📑 文档 From 55702cea30099e331d54e1732ed320d133880ec2 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Thu, 3 Aug 2023 12:24:05 +0800 Subject: [PATCH 0535/1069] Auto Upgrade --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5e666e51..92fcb974c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ + +# 2.0.9 + +## 🐞 Bug Fixes + +-Fix the issue of Windows flash back + +## 🐞 问题修复 + +- 修复windows闪退的问题 + # 2.0.8 ## 🐞 Bug Fixes From 62b52067cc4614bb556c5336ed15164ee7d971a8 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 5 Aug 2023 10:28:03 +0800 Subject: [PATCH 0536/1069] =?UTF-8?q?fix:=E8=A1=A8=E6=A0=BC=E5=A4=8D?= =?UTF-8?q?=E5=88=B6bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.vscode/settings.json | 1 + chat2db-client/package.json | 1 + .../SearchResult/TableBox/index.tsx | 28 ++++++++++--------- chat2db-client/src/utils/index.ts | 3 +- chat2db-client/yarn.lock | 2 +- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 081320b77..221efb36d 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -35,6 +35,7 @@ "USERANDPASSWORD", "ahooks", "antd", + "bgcolor", "chatgpt", "datas", "datasource", diff --git a/chat2db-client/package.json b/chat2db-client/package.json index e84dceaab..b26503837 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -28,6 +28,7 @@ "ahooks": "^3.7.7", "ali-react-table": "^2.6.1", "antd": "^5.6.0", + "copy-to-clipboard": "^3.3.3", "echarts": "^5.4.2", "echarts-for-react": "^3.0.2", "event-source-polyfill": "^1.0.31", diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 841ca854f..f893eec38 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -1,21 +1,22 @@ import React, { useMemo, useState } from 'react'; -import { TableDataType } from '@/constants/table'; -import { IManageResultData, IResultConfig } from '@/typings/database'; -import { formatDate } from '@/utils/date'; import { Button, Dropdown, MenuProps, message, Modal, Space } from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; -import Iconfont from '../../Iconfont'; -import classnames from 'classnames'; -import StateIndicator from '../../StateIndicator'; -import MonacoEditor from '../../Console/MonacoEditor'; -import { useTheme } from '@/hooks/useTheme'; import styled from 'styled-components'; -import { ThemeType } from '@/constants'; +import classnames from 'classnames'; import i18n from '@/i18n'; +import { ThemeType } from '@/constants'; +import { TableDataType } from '@/constants/table'; +import { useTheme } from '@/hooks/useTheme'; +import { IManageResultData, IResultConfig } from '@/typings/database'; import { compareStrings } from '@/utils/sort'; -import MyPagination from '../Pagination'; import { DownOutlined, UserOutlined } from '@ant-design/icons'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; +import { formatDate } from '@/utils/date'; +import { copy } from '@/utils'; +import Iconfont from '../../Iconfont'; +import StateIndicator from '../../StateIndicator'; +import MonacoEditor from '../../Console/MonacoEditor'; +import MyPagination from '../Pagination'; import styles from './index.less'; interface ITableProps { @@ -53,6 +54,7 @@ export default function TableBox(props: ITableProps) { const [viewTableCellData, setViewTableCellData] = useState(null); const [appTheme] = useTheme(); const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); + const [messageApi, contextHolder] = message.useMessage(); const handleExport = (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { props.onExport && props.onExport(data.sql, data.originalSql, exportType, exportSize); @@ -110,8 +112,8 @@ export default function TableBox(props: ITableProps) { } function copyTableCell(data: IViewTableCellData) { - navigator.clipboard.writeText(data?.value || viewTableCellData?.value); - message.success(i18n('common.button.copySuccessfully')); + copy(data?.value || viewTableCellData?.value); + messageApi.success(i18n('common.button.copySuccessfully')); } function handleCancel() { @@ -272,7 +274,6 @@ export default function TableBox(props: ITableProps) { return (
{renderContent()} -
+ {contextHolder}
); } diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 287005ef6..5f98b5ccd 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -1,5 +1,6 @@ import { ThemeType } from '@/constants'; import { ITreeNode } from '@/typings'; +import clipboardCopy from 'copy-to-clipboard'; import lodash from 'lodash'; export function getOsTheme() { @@ -198,7 +199,7 @@ export function isVersionHigher(version: string, currentVersion: string): boolea // Copy export function copy(message: string) { - navigator.clipboard.writeText(message); + clipboardCopy(message); } // 获取应用的一些基本信息 diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 4900ae4e9..92f77a006 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -4093,7 +4093,7 @@ copy-anything@^3.0.2: dependencies: is-what "^4.1.8" -copy-to-clipboard@^3.2.0: +copy-to-clipboard@^3.2.0, copy-to-clipboard@^3.3.3: version "3.3.3" resolved "https://registry.npmmirror.com/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0" integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== From 60ee7737c6a96f2a0dbe8020fbc2c76cafef01c7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 5 Aug 2023 10:35:30 +0800 Subject: [PATCH 0537/1069] =?UTF-8?q?fix:=E6=90=9C=E7=B4=A2=E8=A1=A8?= =?UTF-8?q?=E5=90=8E=E5=AF=BC=E5=87=BAddl=E6=8A=A5=E9=94=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workspace/components/Tree/TreeNodeRightClick/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 17b2b64e6..2b5e30dd8 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -65,7 +65,7 @@ function TreeNodeRightClick(props: IProps) { handle: () => { mysqlServer.exportCreateTableSql({ ...curWorkspaceParams, - tableName: data.name + tableName: data.key } as any).then(res => { setMonacoDefaultValue(res); setMonacoVerifyDialog(true); @@ -272,7 +272,9 @@ function TreeNodeRightClick(props: IProps) { title={`${data.name}-DDL`} open={monacoVerifyDialog} width="600px" - onCancel={(() => { setMonacoVerifyDialog(false) })}> + onCancel={(() => { setMonacoVerifyDialog(false) })} + footer={false} + >
Date: Sat, 5 Aug 2023 10:38:59 +0800 Subject: [PATCH 0538/1069] =?UTF-8?q?fix:=E6=90=9C=E7=B4=A2=E8=A1=A8?= =?UTF-8?q?=E5=90=8E=E6=93=8D=E4=BD=9C=E8=A1=A8=E6=8A=A5=E9=94=99=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/Tree/TreeNodeRightClick/index.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 2b5e30dd8..442d9cb14 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -91,7 +91,7 @@ function TreeNodeRightClick(props: IProps) { text: '新建表', icon: '\ue6b6', handle: () => { - const operationData: IOperationData = { + const operationData = { type: 'new', nodeData: data } @@ -132,9 +132,7 @@ function TreeNodeRightClick(props: IProps) { return { text: data.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: data.pinned ? '\ue61d' : '\ue627', - handle: () => { - handelTop(); - } + handle: handelTop } }, } @@ -143,7 +141,7 @@ function TreeNodeRightClick(props: IProps) { const api = data.pinned ? 'deleteTablePin' : 'addTablePin' mysqlServer[api]({ ...curWorkspaceParams, - tableName: data.name + tableName: data.key } as any).then(res => { dispatch({ type: 'workspace/fetchGetCurTableList', @@ -173,10 +171,10 @@ function TreeNodeRightClick(props: IProps) { } function handleOk() { - if (verifyTableName === data.name) { + if (verifyTableName === data.key) { let p: any = { ...data.extraParams, - tableName: data.name, + tableName: data.key, } mysqlServer.deleteTable(p).then(res => { // notificationApi.success( @@ -257,7 +255,7 @@ function TreeNodeRightClick(props: IProps) { } { setMonacoVerifyDialog(false) })} From ae4cbd4d795e8ec49fd4c3f4745ee9cde2dd9cd2 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 5 Aug 2023 10:45:59 +0800 Subject: [PATCH 0539/1069] UI adapter --- chat2db-client/.umirc.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 5654097a2..56c48c9af 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -44,6 +44,9 @@ export default defineConfig({ changeOrigin: true, }, }, + targets:{ + chrome: 80, + }, headScripts: [ `if (localStorage.getItem('app-local-storage-versions') !== 'v2') { localStorage.clear(); From 04fa7a98a7b211691ec978d74af42dd6fb34a7fa Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 5 Aug 2023 10:58:02 +0800 Subject: [PATCH 0540/1069] =?UTF-8?q?feat:=E6=94=AF=E6=8C=81=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E4=BC=A0=E5=85=A5public=5Fpath?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.umirc.prod.ts | 5 ++--- chat2db-client/package.json | 4 ++-- .../workspace/components/Tree/TreeNodeRightClick/index.tsx | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index 4ca9c1f42..bc5b79872 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -1,8 +1,7 @@ import { defineConfig } from 'umi'; import { extractYarnConfig } from './src/utils/webpack'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); - -// const UMI_PublicPath = process.env.UMI_PublicPath || './static/front/'; +const UMI_PublicPath = process.env.UMI_PublicPath || './static/front/'; const yarn_config = extractYarnConfig(process.argv); const chainWebpack = (config: any, { webpack }: any) => { @@ -14,7 +13,7 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ - publicPath: './static/front/', + publicPath: UMI_PublicPath, chainWebpack, define: { 'process.env.UMI_ENV': process.env.UMI_ENV, diff --git a/chat2db-client/package.json b/chat2db-client/package.json index b26503837..b249ce3c6 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -13,7 +13,7 @@ "build:desktop": "npm run build:web:desktop && npm run build:main:prod", "build:main": "cross-env NODE_ENV=development electron-builder", "build:main:prod": "cross-env NODE_ENV=production electron-builder", - "build:prod": "npm run build:web:prod && npm run build:main:prod", + "build:prod": "npm run build:web:prod && npm run build:main:prod cross-env UMI_PublicPath=${npm_config_public_path}", "build:web": "umi build", "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", @@ -119,4 +119,4 @@ ] } } -} +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 442d9cb14..924365212 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -259,7 +259,8 @@ function TreeNodeRightClick(props: IProps) { open={verifyDialog} onOk={handleOk} width={400} - onCancel={(() => { setVerifyDialog(false) })}> + onCancel={(() => { setVerifyDialog(false) })} + > { setVerifyTableName(e.target.value) }}> {/* 这里后续肯定是要提出去的 */} From a0e2891acc9f5393a5f5fb8fdc634be9eac58f23 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 5 Aug 2023 11:04:21 +0800 Subject: [PATCH 0541/1069] =?UTF-8?q?feat:=E6=94=AF=E6=8C=81=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E4=BC=A0=E5=85=A5public=5Fpath?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.vscode/settings.json | 1 + chat2db-client/package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 221efb36d..ec1df7b99 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -43,6 +43,7 @@ "echarts", "favicons", "findstr", + "gtag", "lsof", "netstat", "pgsql", diff --git a/chat2db-client/package.json b/chat2db-client/package.json index b249ce3c6..57a9f2ce4 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -13,10 +13,10 @@ "build:desktop": "npm run build:web:desktop && npm run build:main:prod", "build:main": "cross-env NODE_ENV=development electron-builder", "build:main:prod": "cross-env NODE_ENV=production electron-builder", - "build:prod": "npm run build:web:prod && npm run build:main:prod cross-env UMI_PublicPath=${npm_config_public_path}", + "build:prod": "npm run build:web:prod && npm run build:main:prod", "build:web": "umi build", "build:web:desktop": "cross-env UMI_ENV=desktop cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", - "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} umi build", + "build:web:prod": "cross-env UMI_ENV=prod cross-env APP_VERSION=${npm_config_app_version} cross-env APP_PORT=${npm_config_app_port} cross-env UMI_PublicPath=${npm_config_public_path} umi build", "postinstall": "umi setup", "lint": "umi lint", "start": "concurrently \"npm run start:web\" \"npm run start:main\"", From 358f08789a377b82baca24f54db829b1484bba45 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 5 Aug 2023 11:15:20 +0800 Subject: [PATCH 0542/1069] UI adapter --- chat2db-client/src/components/Console/ChatInput/index.less | 2 +- chat2db-client/src/components/Console/ChatInput/index.tsx | 2 +- chat2db-client/src/components/Console/MonacoEditor/index.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 1364616d5..e104fdec5 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -63,7 +63,7 @@ padding: 4px 12px; &:hover { - cursor: pointer; + //cursor: pointer; } } diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 2a2fed003..c18b184eb 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -73,7 +73,7 @@ function ChatInput(props: IProps) {
{ - props.onClickRemainBtn && props.onClickRemainBtn(); + // props.onClickRemainBtn && props.onClickRemainBtn(); }} > {i18n('chat.input.remain', remainCnt)} diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index c53ec705b..a412cd070 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -206,7 +206,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { return Object.keys(hintData).map((key) => ({ label: key, kind: monaco.languages.CompletionItemKind.Method, - insertText: key, + insertText: `\`${key}\``, detail: '', })); }; From dbf909e0c4e6681aaeea24203daf72fb53c0bdb0 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 11:24:39 +0800 Subject: [PATCH 0543/1069] test release --- .github/workflows/test.yml | 61 +++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e3ccaa0d9..eb806278f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,30 +1,31 @@ -#name: Test -# -#on: [ push, pull_request ] -# -#jobs: -# test: -# strategy: -# fail-fast: false -# matrix: -# include: -# - os: ubuntu-latest -# runs-on: ${{ matrix.os }} -# steps: -# - name: Check out Git repository -# uses: actions/checkout@main -# -# # 安装jre Windows -# - name: Install Jre for Windows -# uses: actions/setup-java@main -# with: -# java-version: "17" -# distribution: "temurin" -# java-package: "jre" -# -# # 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 -# cat ${{ env.JAVA_HOME }}/conf/security/java.security \ No newline at end of file +name: Test + +on: [ push, pull_request ] + +jobs: + test: + strategy: + fail-fast: false + matrix: + - os: macos-latest + arch: amd64 + - os: ubuntu-latest + runs-on: ${{ matrix.os }} + steps: + - name: Check out Git repository + uses: actions/checkout@main + + # 安装jre Windows + - name: Install Jre for Windows + uses: actions/setup-java@main + with: + java-version: "17" + distribution: "temurin" + java-package: "jre" + + # java.security 开放tls1 Linux + - name: Enable tls1 + run: | + ls ${{ env.JAVA_HOME }} + ls ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ + cat ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ADDITIONAL_LICENSE_INFO \ No newline at end of file From 1ee637396e02b80b441822e18e963350733dd008 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 11:25:53 +0800 Subject: [PATCH 0544/1069] test release --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index eb806278f..f46d7d4cb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,9 +7,10 @@ jobs: strategy: fail-fast: false matrix: - - os: macos-latest - arch: amd64 - - os: ubuntu-latest + include: + - os: macos-latest + arch: amd64 + - os: ubuntu-latest runs-on: ${{ matrix.os }} steps: - name: Check out Git repository From 1230b933255645f0f6098c0a6c43ba6364a728e6 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 11:34:43 +0800 Subject: [PATCH 0545/1069] test release --- .github/workflows/test.yml | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f46d7d4cb..dbc7a632a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,32 +1,32 @@ -name: Test - -on: [ push, pull_request ] - -jobs: - test: - strategy: - fail-fast: false - matrix: - include: - - os: macos-latest - arch: amd64 - - os: ubuntu-latest - runs-on: ${{ matrix.os }} - steps: - - name: Check out Git repository - uses: actions/checkout@main - - # 安装jre Windows - - name: Install Jre for Windows - uses: actions/setup-java@main - with: - java-version: "17" - distribution: "temurin" - java-package: "jre" - - # java.security 开放tls1 Linux - - name: Enable tls1 - run: | - ls ${{ env.JAVA_HOME }} - ls ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ - cat ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ADDITIONAL_LICENSE_INFO \ No newline at end of file +#name: Test +# +#on: [ push, pull_request ] +# +#jobs: +# test: +# strategy: +# fail-fast: false +# matrix: +# include: +# - os: macos-latest +# arch: amd64 +# - os: ubuntu-latest +# runs-on: ${{ matrix.os }} +# steps: +# - name: Check out Git repository +# uses: actions/checkout@main +# +# # 安装jre Windows +# - name: Install Jre for Windows +# uses: actions/setup-java@main +# with: +# java-version: "17" +# distribution: "temurin" +# java-package: "jre" +# +# # java.security 开放tls1 Linux +# - name: Enable tls1 +# run: | +# ls ${{ env.JAVA_HOME }} +# ls ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ +# cat ${{ env.JAVA_HOME }}/legal/jdk.zipfs/ADDITIONAL_LICENSE_INFO \ No newline at end of file From c72c3c92cdaa329398a7c8fcdbb33763ea77bb37 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 12:39:33 +0800 Subject: [PATCH 0546/1069] test release --- chat2db-client/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 57a9f2ce4..98cd1d3f1 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -80,7 +80,6 @@ "files": [ "dist/**/*", "!node_modules", - "static/", "src/main", "versions/**/*", "package.json" From a8277224e5639c77daeb8692fe64a100f90591ba Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 12:40:21 +0800 Subject: [PATCH 0547/1069] test release --- chat2db-client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 98cd1d3f1..57a9f2ce4 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -80,6 +80,7 @@ "files": [ "dist/**/*", "!node_modules", + "static/", "src/main", "versions/**/*", "package.json" From db91a7f28bc50eb1964831865367312257883829 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 14:17:35 +0800 Subject: [PATCH 0548/1069] Modify some team codes --- .../chat2db/server/domain/api/model/Team.java | 48 +++++++++++++ ...0\346\210\267\346\235\203\351\231\220.sql" | 8 +-- .../test/mybatis/MybatisGeneratorTest.java | 4 +- .../api/controller/team/vo/SimpleTeamVO.java | 37 ++++++++++ .../user/DataSourceAccessController.java | 70 ------------------- .../user/vo/UserTeamPageQueryVO.java | 35 ++++++++++ 6 files changed, 127 insertions(+), 75 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java new file mode 100644 index 000000000..c648cec9f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java @@ -0,0 +1,48 @@ +package ai.chat2db.server.domain.api.model; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Team implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + private Long id; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; + +} diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" index 3fd674b12..3c8cda587 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -53,7 +53,7 @@ CREATE TABLE IF NOT EXISTS `team` create UNIQUE INDEX uk_team_code on team (code); -CREATE TABLE IF NOT EXISTS `team_dbhub_user` +CREATE TABLE IF NOT EXISTS `team_user` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', @@ -61,14 +61,14 @@ CREATE TABLE IF NOT EXISTS `team_dbhub_user` `create_user_id` bigint(20) unsigned NOT NULL COMMENT '创建人用户id', `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', `team_id` bigint(20) unsigned NOT NULL COMMENT '团队id', - `dbhub_user_id` bigint(20) unsigned NOT NULL COMMENT '用户id', + `user_id` bigint(20) unsigned NOT NULL COMMENT '用户id', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='用户团队表' ; -create INDEX idx_team_dbhub_user_team_id on team_dbhub_user (`team_id`); -create INDEX idx_team_dbhub_user_dbhub_user_id on team_dbhub_user (`dbhub_user_id`); +create INDEX idx_team_user_team_id on team_user (`team_id`); +create INDEX idx_team_user_user_id on team_user (`user_id`); CREATE TABLE IF NOT EXISTS `data_source_access` ( diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index d8b8be607..12ea9944d 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -32,7 +32,9 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("data_source")); //doGenerator(Lists.newArrayList("operation_log")); //doGenerator(Lists.newArrayList("operation_saved")); - doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access","dbhub_user")); + //doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access", + // "dbhub_user")); + doGenerator(Lists.newArrayList("team_user")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java new file mode 100644 index 000000000..f6b0bf5f2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java @@ -0,0 +1,37 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import lombok.Data; + +/** + * team + * + * @author Jiaju Zhuang + */ +@Data +public class SimpleTeamVO { + + /** + * 主键 + */ + private Long id; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java deleted file mode 100644 index 65233dab0..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/DataSourceAccessController.java +++ /dev/null @@ -1,70 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user; - -import ai.chat2db.server.admin.api.controller.user.converter.DataSourceAdminConverter; -import ai.chat2db.server.admin.api.controller.user.request.DataSourceAccessBatchCreateRequest; -import ai.chat2db.server.admin.api.controller.user.request.DataSourceAccessPageQueryRequest; -import ai.chat2db.server.admin.api.controller.user.vo.DataSourceAccessPageQueryVO; -import ai.chat2db.server.domain.api.service.DataSourceService; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Data Source Access Management - * - * @author Jiaju Zhuang - */ -@RequestMapping("/api/admin/data/source/access") -@RestController -public class DataSourceAccessController { - - @Resource - private DataSourceService dataSourceService; - @Resource - private DataSourceAdminConverter dataSourceAdminConverter; - - /** - * Pagination query - * - * @param request - * @return - * @version 2.1.0 - */ - @GetMapping("/page") - public WebPageResult page(@Valid DataSourceAccessPageQueryRequest request) { - return null; - } - - /** - * batch - * - * @param request - * @return - * @version 2.1.0 - */ - @PostMapping("/batch-create") - public ActionResult batchCreate(@RequestBody DataSourceAccessBatchCreateRequest request) { - return null; - } - - /** - * delete - * - * @param id - * @return - */ - @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return null; - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java new file mode 100644 index 000000000..554985cfc --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java @@ -0,0 +1,35 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserTeamPageQueryVO { + + /** + * user id + */ + private Long userId; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; +} From 2060ec88c659d39cb545535a6650bb2529b0d680 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 5 Aug 2023 14:26:46 +0800 Subject: [PATCH 0549/1069] add column data type --- .../src/main/java/ai/chat2db/spi/util/ResultSetUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java index 2d11f3be0..3dae5f80e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java @@ -70,7 +70,7 @@ public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { tableColumn.setName(getString(resultSet, "COLUMN_NAME")); tableColumn.setComment(getString(resultSet, "REMARKS")); tableColumn.setDefaultValue(getString(resultSet, "COLUMN_DEF")); - tableColumn.setTypeName(getString(resultSet, "TYPE_NAME")); + tableColumn.setColumnType(getString(resultSet, "TYPE_NAME")); tableColumn.setColumnSize(resultSet.getInt("COLUMN_SIZE")); tableColumn.setDataType(resultSet.getInt("DATA_TYPE")); tableColumn.setNullable(resultSet.getInt("NULLABLE") == 1); From 39d12c1d5f52f375b2c1c74a2c238702758be027 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 5 Aug 2023 14:56:33 +0800 Subject: [PATCH 0550/1069] Complete interface definition --- .../chat2db/server/domain/api/model/Team.java | 10 ++ .../{TeamDbhubUserDO.java => TeamUserDO.java} | 13 ++- ...hubUserMapper.java => TeamUserMapper.java} | 6 +- ...DbhubUserMapper.xml => TeamUserMapper.xml} | 2 +- .../DataSourceAccessController.java | 4 +- .../datasource/DataSourceController.java | 2 +- .../datasource/vo/SimpleDataSourceVO.java | 39 +++++++ .../api/controller/team/TeamController.java | 76 +++++++++++++ .../team/TeamDataSourceController.java | 63 +++++++++++ .../controller/team/TeamUserController.java | 63 +++++++++++ .../team/request/TeamCreateRequest.java | 38 +++++++ .../TeamDataSourceBatchCreateRequest.java | 35 ++++++ .../team/request/TeamUpdateRequest.java | 43 +++++++ .../request/TeamUserBatchCreateRequest.java | 34 ++++++ .../team/vo/TeamDataSourcePageQueryVO.java | 24 ++++ .../controller/team/vo/TeamPageQueryVO.java | 35 ++++++ .../team/vo/TeamUserPageQueryVO.java | 28 +++++ .../user/UserDataSourceController.java | 63 +++++++++++ .../controller/user/UserTeamController.java | 10 +- .../converter/DataSourceAdminConverter.java | 2 +- .../DataSourceAccessObjectRequest.java | 41 ------- .../DataSourceAccessPageQueryRequest.java | 26 ----- .../user/request/DataSourceCloneRequest.java | 19 ---- .../user/request/DataSourceUpdateRequest.java | 105 ------------------ ... => UserDataSourceBatchCreateRequest.java} | 11 +- .../request/UserTeamBatchCreateRequest.java | 34 ++++++ .../user/vo/DataSourceAccessObjectVO.java | 50 --------- .../user/vo/DataSourceAccessPageQueryVO.java | 40 ------- .../api/controller/user/vo/SimpleUserVO.java | 39 +++++++ .../user/vo/UserDataSourcePageQueryVO.java | 24 ++++ .../user/vo/UserTeamPageQueryVO.java | 21 ++-- 31 files changed, 680 insertions(+), 320 deletions(-) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/{TeamDbhubUserDO.java => TeamUserDO.java} (87%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/{TeamDbhubUserMapper.java => TeamUserMapper.java} (53%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/{TeamDbhubUserMapper.xml => TeamUserMapper.xml} (91%) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceController.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java rename chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/{DataSourceAccessBatchCreateRequest.java => UserDataSourceBatchCreateRequest.java} (69%) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java index c648cec9f..07a021566 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java @@ -4,6 +4,7 @@ import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -26,16 +27,19 @@ public class Team implements Serializable { /** * 主键 */ + @NotNull private Long id; /** * 团队编码 */ + @NotNull private String code; /** * 团队名称 */ + @NotNull private String name; /** @@ -43,6 +47,12 @@ public class Team implements Serializable { * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ + @NotNull private String status; + /** + * 团队描述 + */ + private String description; + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamUserDO.java similarity index 87% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamUserDO.java index 3f9c614c2..f9f3fbbf8 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDbhubUserDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamUserDO.java @@ -1,10 +1,11 @@ package ai.chat2db.server.domain.repository.entity; +import java.io.Serializable; +import java.time.LocalDateTime; + import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; @@ -14,12 +15,12 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-05 */ @Getter @Setter -@TableName("TEAM_DBHUB_USER") -public class TeamDbhubUserDO implements Serializable { +@TableName("TEAM_USER") +public class TeamUserDO implements Serializable { private static final long serialVersionUID = 1L; @@ -57,5 +58,5 @@ public class TeamDbhubUserDO implements Serializable { /** * 用户id */ - private Long dbhubUserId; + private Long userId; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserMapper.java similarity index 53% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserMapper.java index 46182c229..43998295d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamDbhubUserMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserMapper.java @@ -1,6 +1,6 @@ package ai.chat2db.server.domain.repository.mapper; -import ai.chat2db.server.domain.repository.entity.TeamDbhubUserDO; +import ai.chat2db.server.domain.repository.entity.TeamUserDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; /** @@ -9,8 +9,8 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-05 */ -public interface TeamDbhubUserMapper extends BaseMapper { +public interface TeamUserMapper extends BaseMapper { } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamUserMapper.xml similarity index 91% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamUserMapper.xml index 8308dde26..48dbb44d0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamDbhubUserMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/ai/chat2db/server/domain/repository/TeamUserMapper.xml @@ -1,5 +1,5 @@ - + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java index 2b6a6e87b..c0b3e8ec4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessController.java @@ -23,7 +23,7 @@ * * @author Jiaju Zhuang */ -@RequestMapping("/api/admin/data/source/access") +@RequestMapping("/api/admin/data_source/access") @RestController public class DataSourceAccessController { @@ -51,7 +51,7 @@ public WebPageResult page(@Valid DataSourceAccessPa * @return * @version 2.1.0 */ - @PostMapping("/batch-create") + @PostMapping("/batch_create") public ActionResult batchCreate(@RequestBody DataSourceAccessBatchCreateRequest request) { return null; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java index 8025fd03a..c8317a1b2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceController.java @@ -28,7 +28,7 @@ * * @author Jiaju Zhuang */ -@RequestMapping("/api/admin/data/source") +@RequestMapping("/api/admin/data_source") @RestController public class DataSourceController { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java new file mode 100644 index 000000000..6680777d7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java @@ -0,0 +1,39 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import ai.chat2db.server.common.api.controller.vo.EnvironmentVO; +import lombok.Data; + +/** + * Data Source + * + * @author Jiaju Zhuang + */ +@Data +public class SimpleDataSourceVO { + + /** + * 主键id + */ + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 环境id + */ + private Long environmentId; + + /** + * 环境 + */ + private EnvironmentVO environment; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamController.java new file mode 100644 index 000000000..ffd5d70e1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamController.java @@ -0,0 +1,76 @@ + +package ai.chat2db.server.admin.api.controller.team; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Team Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/team") +@RestController +public class TeamController { + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid CommonPageQueryRequest request) { + return null; + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/create") + public DataResult create(@RequestBody TeamCreateRequest request) { + return null; + + } + + /** + * update + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/update") + public ActionResult update(@RequestBody TeamUpdateRequest request) { + return null; + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceController.java new file mode 100644 index 000000000..0463e8fff --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceController.java @@ -0,0 +1,63 @@ + +package ai.chat2db.server.admin.api.controller.team; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamDataSourceBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Team Data Source Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/team/data_source") +@RestController +public class TeamDataSourceController { + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid CommonPageQueryRequest request) { + return null; + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/batch_create") + public DataResult create(@RequestBody TeamDataSourceBatchCreateRequest request) { + return null; + + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserController.java new file mode 100644 index 000000000..1a7e877b9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserController.java @@ -0,0 +1,63 @@ + +package ai.chat2db.server.admin.api.controller.team; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamUserBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Team User Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/team/user") +@RestController +public class TeamUserController { + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid CommonPageQueryRequest request) { + return null; + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/batch_create") + public DataResult create(@RequestBody TeamUserBatchCreateRequest request) { + return null; + + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java new file mode 100644 index 000000000..1b3bcf9a2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java @@ -0,0 +1,38 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class TeamCreateRequest { + + /** + * 团队编码 + */ + @NotNull + private String code; + + /** + * 团队名称 + */ + @NotNull + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + @NotNull + private String status; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java new file mode 100644 index 000000000..6c3f20f74 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import java.util.List; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamDataSourceBatchCreateRequest { + + /** + * team id + */ + private Long teamId; + + + /** + * Data Source id list + */ + @NotNull + @NotEmpty + private List dataSourceIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java new file mode 100644 index 000000000..584fd05ef --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java @@ -0,0 +1,43 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * update + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUpdateRequest { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 团队编码 + */ + @NotNull + private String code; + + /** + * 团队名称 + */ + @NotNull + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + @NotNull + private String status; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java new file mode 100644 index 000000000..64a29215e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import java.util.List; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserBatchCreateRequest { + + /** + * team id + */ + private Long teamId; + + /** + * user id list + */ + @NotNull + @NotEmpty + private List userIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java new file mode 100644 index 000000000..6a8a652a2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java @@ -0,0 +1,24 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamDataSourcePageQueryVO { + + /** + * team id + */ + private Long teamId; + + /** + * Data Source + */ + private SimpleDataSourceVO dataSource; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java new file mode 100644 index 000000000..d04846015 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java @@ -0,0 +1,35 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamPageQueryVO { + /** + * 主键 + */ + private Long id; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java new file mode 100644 index 000000000..a5520a23e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java @@ -0,0 +1,28 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserPageQueryVO { + /** + * 主键 + */ + private Long id; + + /** + * team id + */ + private Long teamId; + + /** + * user + */ + private SimpleUserVO user; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceController.java new file mode 100644 index 000000000..e42066b23 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceController.java @@ -0,0 +1,63 @@ + +package ai.chat2db.server.admin.api.controller.user; + +import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * User Data Source Management + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/admin/user/data_source") +@RestController +public class UserDataSourceController { + + /** + * Pagination query + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/page") + public WebPageResult page(@Valid CommonPageQueryRequest request) { + return null; + } + + /** + * create + * + * @param request + * @return + * @version 2.1.0 + */ + @PostMapping("/batch_create") + public DataResult create(@RequestBody UserTeamBatchCreateRequest request) { + return null; + + } + + /** + * delete + * + * @param id + * @return + */ + @DeleteMapping("/{id}") + public ActionResult delete(@PathVariable Long id) { + return null; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java index 7dd7726d0..884723080 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamController.java @@ -2,8 +2,8 @@ package ai.chat2db.server.admin.api.controller.user; import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; -import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; -import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; +import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; @@ -33,7 +33,7 @@ public class UserTeamController { * @version 2.1.0 */ @GetMapping("/page") - public WebPageResult page(@Valid CommonPageQueryRequest request) { + public WebPageResult page(@Valid CommonPageQueryRequest request) { return null; } @@ -44,8 +44,8 @@ public WebPageResult page(@Valid CommonPageQueryRequest request * @return * @version 2.1.0 */ - @PostMapping("/create") - public DataResult create(@RequestBody UserCreateRequest request) { + @PostMapping("/batch_create") + public DataResult create(@RequestBody UserTeamBatchCreateRequest request) { return null; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java index 9889d7b1e..226254463 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/DataSourceAdminConverter.java @@ -1,7 +1,7 @@ package ai.chat2db.server.admin.api.controller.user.converter; import ai.chat2db.server.admin.api.controller.common.request.CommonPageQueryRequest; -import ai.chat2db.server.admin.api.controller.user.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; import ai.chat2db.server.domain.api.param.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.DataSourcePageQueryParam; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java deleted file mode 100644 index 5ec7b1d50..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessObjectRequest.java +++ /dev/null @@ -1,41 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.request; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * DataSource Access Object - * It could be a user or a team - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessObjectRequest implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long id; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java deleted file mode 100644 index 217d14313..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessPageQueryRequest.java +++ /dev/null @@ -1,26 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.request; - -import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Common pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessPageQueryRequest extends PageQueryRequest { - - /** - * 数据源id - */ - @NotNull - private Long dataSourceId; - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java deleted file mode 100644 index 7edc4f8cb..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceCloneRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.request; - - -import lombok.Data; - -/** - * @author moji - * @version ConnectionCloneRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ - * @date 2022/09/16 - */ -@Data -public class DataSourceCloneRequest { - - /** - * 主键id - */ - private Long id; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java deleted file mode 100644 index 99d2469ac..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceUpdateRequest.java +++ /dev/null @@ -1,105 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.request; - -import java.util.List; - -import ai.chat2db.server.tools.base.enums.EnvTypeEnum; -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * @author moji - * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ - * @date 2022/09/16 - */ -@Data -public class DataSourceUpdateRequest { - - /** - * 主键id - */ - @NotNull - private Long id; - - /** - * 连接别名 - */ - private String alias; - - /** - * 连接地址 - */ - private String url; - - /** - * 连接用户 - */ - private String user; - - /** - * 密码 - */ - private String password; - - /** - * 连接类型 - */ - private String type; - - /** - * 环境类型 - * @see EnvTypeEnum - */ - private String envType; - - - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java similarity index 69% rename from chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java rename to chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java index 57e4e1ae9..65e70b022 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/DataSourceAccessBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java @@ -18,18 +18,17 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class DataSourceAccessBatchCreateRequest { +public class UserDataSourceBatchCreateRequest { /** - * 数据源id + * user id */ - @NotNull - private Long dataSourceId; + private Long userId; /** - * DataSource Access Object + * Data Source id list */ @NotNull @NotEmpty - private List accessObjectList; + private List dataSourceIdList; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java new file mode 100644 index 000000000..68c74ef6d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import java.util.List; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UserTeamBatchCreateRequest { + + /** + * user id + */ + private Long userId; + + /** + * team id list + */ + @NotNull + @NotEmpty + private List teamIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java deleted file mode 100644 index db324b0f0..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessObjectVO.java +++ /dev/null @@ -1,50 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.vo; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * DataSource Access Object - * It could be a user or a team - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessObjectVO implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long id; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - - /** - * The name of the code that belongs to the authorization type, such as user account, team code - */ - private String code; - - /** - * Code that belongs to the authorization type, such as user name, team name - */ - private String name; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java deleted file mode 100644 index f10e6f935..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/DataSourceAccessPageQueryVO.java +++ /dev/null @@ -1,40 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.vo; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessPageQueryVO { - - /** - * 主键 - */ - @NotNull - private Long id; - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - @NotNull - private String accessObjectType; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - @NotNull - private Long accessObjectId; - - /** - * 授权对象 - */ - @NotNull - private DataSourceAccessObjectVO accessObject; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java new file mode 100644 index 000000000..8b8f88071 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java @@ -0,0 +1,39 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * user + * + * @author Jiaju Zhuang + */ +@Data +public class SimpleUserVO { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java new file mode 100644 index 000000000..7aed582c4 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java @@ -0,0 +1,24 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserDataSourcePageQueryVO { + + /** + * user id + */ + private Long userId; + + /** + * Data Source + */ + private SimpleDataSourceVO dataSource; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java index 554985cfc..56424c24b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java @@ -1,6 +1,7 @@ package ai.chat2db.server.admin.api.controller.user.vo; +import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; import lombok.Data; /** @@ -10,26 +11,18 @@ */ @Data public class UserTeamPageQueryVO { - - /** - * user id - */ - private Long userId; - /** - * 团队编码 + * 主键 */ - private String code; + private Long id; /** - * 团队名称 + * user id */ - private String name; + private Long userId; /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + * 团队 */ - private String status; + private SimpleTeamVO team; } From 9ef9d2618f73a33ce622d033dfc9bcc34dbe5f3a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 5 Aug 2023 15:03:35 +0800 Subject: [PATCH 0551/1069] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=88=97=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E7=B1=BB=E5=9E=8B=E6=B3=A8=E9=87=8A=E8=A1=A8=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/readme.md | 11 +- .../src/components/Console/index.tsx | 15 ++- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/zh-cn/common.ts | 1 + .../main/workspace/components/Tree/index.less | 3 +- .../main/workspace/components/Tree/index.tsx | 116 +++++++----------- chat2db-client/src/typings/tree.ts | 9 +- 7 files changed, 73 insertions(+), 83 deletions(-) diff --git a/chat2db-client/readme.md b/chat2db-client/readme.md index 0e3c8be6e..9c884c1af 100644 --- a/chat2db-client/readme.md +++ b/chat2db-client/readme.md @@ -22,10 +22,15 @@ 注意:因为electron包比较难下载,如果yarn时electron下载失败或超时,可以删除掉chat2db-client/package.json下的electron,再次yarn ## TS书写规范 - 1. 所有的interfase 与 type 必须已I开头 - `interfase IState { name: string }` // good - `interfase State { name: string }` // bad + 1. 所有的interface 与 type 必须已I开头 + `interface IState { name: string }` // good + `interface State { name: string }` // bad +## 如何在js与css中使用颜色 + 具体转换在 /theme/index.ts 中的 InjectThemeVar + - js 在window._AppThemePack中去取 eg:`window._AppThemePack.controlItemBgActive` // good + - css eg: `background: var(--control-item-bg-active)` // good + - css `color: #fff` ` // bad ## 如何使用国际化 diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 071ed1d04..245c5cd5e 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -437,7 +437,7 @@ function Console(props: IProps) { onExecute={executeSQL} options={props.editorOptions} tables={props.tables} - // onChange={} + // onChange={} /> {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> @@ -466,8 +466,17 @@ function Console(props: IProps) {
From 7128e5dbd9a44f7ba2e3b59156d562cea9893fc1 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 20:19:34 +0800 Subject: [PATCH 0570/1069] feat: test linux --- .github/workflows/test_linux.yml | 136 +++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/test_linux.yml diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml new file mode 100644 index 000000000..83e0c2403 --- /dev/null +++ b/.github/workflows/test_linux.yml @@ -0,0 +1,136 @@ +# Workflow's name +name: Build Test Client + +# Workflow's trigger +# 在release_test 分支收到推送的时候触发 +on: [push, pull_request] + +jobs: + release: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + runs-on: ${{ matrix.os }} + + steps: + - name: Check out git repository + uses: actions/checkout@main + + # 安装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 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 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 + + # 编译服务端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 + + # Linux + - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" + + # 准备要需要的数据 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: "latest" + - 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 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) " + } From ace32a854b325a2a99b2435855aa1059de943b58 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 20:26:35 +0800 Subject: [PATCH 0571/1069] test linux --- .github/workflows/test_linux.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 83e0c2403..d0024e3c9 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -1,5 +1,5 @@ # Workflow's name -name: Build Test Client +name: Build Linux Client # Workflow's trigger # 在release_test 分支收到推送的时候触发 @@ -94,14 +94,14 @@ jobs: rm -r dist # Linux - - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" + # - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" # 准备要需要的数据 Linux - name: Prepare upload for Linux From bfb8d7fe285cb441053c1d7a5a1d2f82fc8c0014 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 20:27:34 +0800 Subject: [PATCH 0572/1069] test linux --- .github/workflows/test_linux.yml | 16 ++++++++-------- chat2db-client/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index d0024e3c9..fcd143118 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -94,14 +94,14 @@ jobs: rm -r dist # Linux - # - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" + - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" # 准备要需要的数据 Linux - name: Prepare upload for Linux diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 9297ff704..7e511e927 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -80,7 +80,7 @@ "files": [ "dist/**/*", "!node_modules", - "static/", + "src/main", "versions/**/*", "package.json" From 785f7247967a8e9a1109ac57e9de5272a86c255e Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 20:49:44 +0800 Subject: [PATCH 0573/1069] test linux --- .github/workflows/test_linux.yml | 5 +++++ chat2db-client/package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index fcd143118..dac4e74b3 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -93,6 +93,11 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist + - name: Test File + run: | + cd chat2db-client/static/jre/legal/java.base + ls -la + # Linux - name: Build/release Electron app for Linux if: ${{ runner.os == 'Linux' }} diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 7e511e927..9297ff704 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -80,7 +80,7 @@ "files": [ "dist/**/*", "!node_modules", - + "static/", "src/main", "versions/**/*", "package.json" From d69f16443cd4b87218c2a1dc39c6409de7f74404 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 20:57:30 +0800 Subject: [PATCH 0574/1069] test linux --- .github/workflows/test_linux.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index dac4e74b3..7f9774a42 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -97,6 +97,8 @@ jobs: run: | cd chat2db-client/static/jre/legal/java.base ls -la + rm ADDITIONAL_LICENSE_INFO + ls -la # Linux - name: Build/release Electron app for Linux From 9efbcadeb337ec8118be47b2375422fdc3c4f7dc Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 21:03:34 +0800 Subject: [PATCH 0575/1069] test linux --- .github/workflows/test_linux.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 7f9774a42..f1bc98481 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -99,6 +99,10 @@ jobs: ls -la rm ADDITIONAL_LICENSE_INFO ls -la + cd jdk.zipfs + ls -la + rm ADDITIONAL_LICENSE_INFO + ls -la # Linux - name: Build/release Electron app for Linux From f2343419db2d63d9a127eba8280ca8214833406e Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 21:07:25 +0800 Subject: [PATCH 0576/1069] test linux --- .github/workflows/test_linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index f1bc98481..58b4438ed 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -99,7 +99,7 @@ jobs: ls -la rm ADDITIONAL_LICENSE_INFO ls -la - cd jdk.zipfs + cd ../jdk.zipfs ls -la rm ADDITIONAL_LICENSE_INFO ls -la From 4420c9be9316e718721ebdff0719bd395f87894a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 21:28:00 +0800 Subject: [PATCH 0577/1069] test linux --- .github/workflows/test_linux.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 58b4438ed..b00594de0 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-java@main with: java-version: "17" - distribution: "temurin" + distribution: "amazon" java-package: "jre" # java.security 开放tls1 Linux @@ -93,16 +93,16 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist - - name: Test File - run: | - cd chat2db-client/static/jre/legal/java.base - ls -la - rm ADDITIONAL_LICENSE_INFO - ls -la - cd ../jdk.zipfs - ls -la - rm ADDITIONAL_LICENSE_INFO - ls -la + # - name: Test File + # run: | + # cd chat2db-client/static/jre/legal/java.base + # ls -la + # rm ADDITIONAL_LICENSE_INFO + # ls -la + # cd ../jdk.zipfs + # ls -la + # rm ADDITIONAL_LICENSE_INFO + # ls -la # Linux - name: Build/release Electron app for Linux From 50ac6144be28534e58532d16683c76762a379b6d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 21:34:03 +0800 Subject: [PATCH 0578/1069] test linux --- .github/workflows/test_linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index b00594de0..f75cd231d 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -24,7 +24,7 @@ jobs: uses: actions/setup-java@main with: java-version: "17" - distribution: "amazon" + distribution: "corretto" java-package: "jre" # java.security 开放tls1 Linux From b0af9f094aa1796f09cf56f4e636d35db46ab2ff Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 21:59:35 +0800 Subject: [PATCH 0579/1069] test linux --- .github/workflows/test_linux.yml | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index f75cd231d..0645acd02 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -23,7 +23,7 @@ jobs: if: ${{ runner.os == 'Linux' }} uses: actions/setup-java@main with: - java-version: "17" + java-version: "temurin" distribution: "corretto" java-package: "jre" @@ -93,16 +93,12 @@ jobs: cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist - # - name: Test File - # run: | - # cd chat2db-client/static/jre/legal/java.base - # ls -la - # rm ADDITIONAL_LICENSE_INFO - # ls -la - # cd ../jdk.zipfs - # ls -la - # rm ADDITIONAL_LICENSE_INFO - # ls -la + - name: Test File + run: | + cd chat2db-client/static/jre/ + ls -la + rm -rf legal + ls -la # Linux - name: Build/release Electron app for Linux From f0e56cadb9759e3c398beb4f9253927205144ab4 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 6 Aug 2023 22:03:01 +0800 Subject: [PATCH 0580/1069] test linux --- .github/workflows/test_linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 0645acd02..1dc5fca38 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -23,8 +23,8 @@ jobs: if: ${{ runner.os == 'Linux' }} uses: actions/setup-java@main with: - java-version: "temurin" - distribution: "corretto" + java-version: "17" + distribution: "temurin" java-package: "jre" # java.security 开放tls1 Linux From e78293cbc77d97c3ec2749db30b5a85f9e85655a Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Mon, 7 Aug 2023 15:54:06 +0800 Subject: [PATCH 0581/1069] Update test_linux.yml 10824 --- .github/workflows/test_linux.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index 1dc5fca38..a5a1b41dd 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -62,7 +62,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 + yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10824 cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front # 编译服务端java版本 @@ -89,7 +89,7 @@ jobs: - name: Prepare Build Electron run: | cd chat2db-client - yarn run build:web:desktop --app_version=99.0.${{ github.run_id }} --app_port=10822 + yarn run build:web:desktop --app_version=99.0.${{ github.run_id }} --app_port=10824 cp -r dist ./versions/99.0.${{ github.run_id }}/ rm -r dist From 2e05e6563ee1d04bdd5a670a350150286d0f2000 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 7 Aug 2023 22:37:23 +0800 Subject: [PATCH 0582/1069] feat: Configure Linux packaging --- .github/workflows/release.yml | 10 +- .github/workflows/release_test.yml | 10 +- .github/workflows/test_linux.yml | 286 ++++++++++++++--------------- 3 files changed, 161 insertions(+), 145 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7310d0003..da04d5610 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -109,6 +109,7 @@ jobs: 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: | @@ -220,6 +221,14 @@ jobs: xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-${{ steps.chat2db_version.outputs.substring }}-arm64.dmg # 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 @@ -261,7 +270,6 @@ jobs: run: | mkdir oss_temp_file cp -r chat2db-client/release/*.AppImage ./oss_temp_file - cp -r chat2db-client/release/*_amd64.deb ./oss_temp_file # 把文件上传到OSS 方便下载 - name: Set up oss utils diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index e5498aa82..d71beff83 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -101,7 +101,7 @@ jobs: cp -r $JAVA_HOME chat2db-client/static/jre chmod -R 777 chat2db-client/static/jre/ - # 复制jre macOS + # 复制jre Linux - name: Copy Jre for Linux if: ${{ runner.os == 'Linux' }} run: | @@ -210,6 +210,14 @@ jobs: xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg # 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 diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index a5a1b41dd..df8a1529d 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -1,143 +1,143 @@ -# Workflow's name -name: Build Linux Client - -# Workflow's trigger -# 在release_test 分支收到推送的时候触发 -on: [push, pull_request] - -jobs: - release: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - runs-on: ${{ matrix.os }} - - steps: - - name: Check out git repository - uses: actions/checkout@main - - # 安装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 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 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=10824 - cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front - - # 编译服务端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=10824 - cp -r dist ./versions/99.0.${{ github.run_id }}/ - rm -r dist - - - name: Test File - run: | - cd chat2db-client/static/jre/ - ls -la - rm -rf legal - ls -la - - # Linux - - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" - - # 准备要需要的数据 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: "latest" - - 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 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) " - } +# # Workflow's name +# name: Build Linux Client + +# # Workflow's trigger +# # 在release_test 分支收到推送的时候触发 +# on: [push, pull_request] + +# jobs: +# release: +# strategy: +# fail-fast: false +# matrix: +# include: +# - os: ubuntu-latest +# runs-on: ${{ matrix.os }} + +# steps: +# - name: Check out git repository +# uses: actions/checkout@main + +# # 安装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 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 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=10824 +# cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front + +# # 编译服务端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=10824 +# cp -r dist ./versions/99.0.${{ github.run_id }}/ +# rm -r dist + +# - name: Test File +# run: | +# cd chat2db-client/static/jre/ +# ls -la +# rm -rf legal +# ls -la + +# # Linux +# - 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=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" + +# # 准备要需要的数据 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: "latest" +# - 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 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) " +# } From 56931a1c743a3f97839c8057f12ec447c1426d77 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 7 Aug 2023 22:48:48 +0800 Subject: [PATCH 0583/1069] If the database name contains the name of the current database, the current database is placed in the first place --- chat2db-server/chat2db-spi/pom.xml | 5 +++++ .../main/java/ai/chat2db/spi/ssh/SSHManager.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/chat2db-server/chat2db-spi/pom.xml b/chat2db-server/chat2db-spi/pom.xml index e1f021b07..2824e27c1 100644 --- a/chat2db-server/chat2db-spi/pom.xml +++ b/chat2db-server/chat2db-spi/pom.xml @@ -36,6 +36,11 @@ jsch 0.2.9 + + org.bouncycastle + bcprov-jdk18on + 1.71 + com.oracle.ojdbc orai18n diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java index 92053106a..93dc2fdf8 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ssh/SSHManager.java @@ -1,13 +1,17 @@ package ai.chat2db.spi.ssh; +import java.security.Security; + import ai.chat2db.server.tools.common.exception.ConnectionException; import ai.chat2db.spi.model.SSHInfo; import cn.hutool.core.net.NetUtil; import cn.hutool.extra.ssh.JschUtil; +import com.jcraft.jsch.JSch; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * @author jipengfei @@ -16,6 +20,16 @@ @Slf4j public class SSHManager { + static { + try { + Security.insertProviderAt(new BouncyCastleProvider(), 1); + JSch.setConfig("kex", JSch.getConfig("kex") + ",diffie-hellman-group1-sha1"); + JSch.setConfig("server_host_key", JSch.getConfig("server_host_key") + ",ssh-rsa,ssh-dss"); + }catch (Exception e){ + log.error("SSHManager init error",e); + } + } + public static Session getSSHSession(SSHInfo ssh) { Session session = null; try { From 93958e5d04f0d894505bfec5e0151bef1c01011b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 7 Aug 2023 23:27:41 +0800 Subject: [PATCH 0584/1069] =?UTF-8?q?fix:=E8=A1=A8=E5=A4=87=E6=B3=A8?= =?UTF-8?q?=E4=B8=8D=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/main/workspace/components/Tree/index.tsx | 1 - .../src/pages/main/workspace/components/Tree/treeConfig.tsx | 2 ++ chat2db-client/src/service/sql.ts | 1 + chat2db-client/src/typings/tree.ts | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index f322261d3..c54f6cd1c 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -35,7 +35,6 @@ function Tree(props: IProps) { const [treeData, setTreeData] = useState(); const [searchedTreeData, setSearchedTreeData] = useState(null); - useEffect(() => { setTreeData(initialData); }, [initialData]) diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index 9b5a5eb31..d105bd7f6 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -178,6 +178,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { treeNodeType: TreeNodeType.TABLE, key: t.name, pinned: t.pinned, + comment: t.comment, extraParams: { ...params.extraParams, tableName: t.name @@ -239,6 +240,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { key: item.name, isLeaf: true, columnType: item.columnType, + comment: item.comment, } }) r(tableList); diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 89144cba7..0064c33bb 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -64,6 +64,7 @@ export interface IColumn { numericPrecision: number; numericScale: number; characterMaximumLength: number; + comment: string; } export interface ISchemaParams { diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index 4996207c5..d9db7c767 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -18,5 +18,5 @@ export interface ITreeNode { columnType?: string; // 列的类型 extraParams?: IExtraParams; pinned?: boolean; // 是否置顶 - comment: string; // 表列的注释 + comment?: string; // 表列的注释 } \ No newline at end of file From b5ffb3b8d864e28e4471b8df04b80afe4b298417 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Mon, 7 Aug 2023 23:51:15 +0800 Subject: [PATCH 0585/1069] fix sqlserver connection rest --- .../plugin/sqlserver/SqlServerMetaData.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) 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 4fe9defb0..d40d353ee 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 @@ -2,10 +2,13 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.List; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.Database; import ai.chat2db.spi.sql.SQLExecutor; +import com.google.common.collect.Lists; public class SqlServerMetaData extends DefaultMetaService implements MetaData { private String functionSQL @@ -59,4 +62,24 @@ public String tableDDL(Connection connection, String databaseName, String schema return null; }); } + @Override + public List databases(Connection connection) { + List databases = Lists.newArrayList(); + SQLExecutor.getInstance().executeSql(connection, "SELECT name " + + "FROM sys.databases", + resultSet -> { + try { + while (resultSet.next()) { + Database database = new Database(); + String databaseName = resultSet.getString("name"); + database.setName(databaseName); + databases.add(database); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + return databases; + } } From 291105dfd18083d0d8264092a36a66a7194b630f Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Tue, 8 Aug 2023 16:53:37 +0800 Subject: [PATCH 0586/1069] Update release_test.yml --- .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 d71beff83..075e5b2a2 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -72,19 +72,19 @@ jobs: - 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" + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, 3DES_EDE_CBC, 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 + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, 3DES_EDE_CBC, 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 + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, 3DES_EDE_CBC, TLSv1.1,\)\(.*\)/\1\2\4/" ${{ env.JAVA_HOME }}/conf/security/java.security # 复制jre Windows - name: Copy Jre for Windows From d65708a6bc6e49fdd0072bc78357c6a4a649b323 Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Tue, 8 Aug 2023 17:15:29 +0800 Subject: [PATCH 0587/1069] Update release_test.yml --- .github/workflows/release_test.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 075e5b2a2..6a357a2f3 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -72,19 +72,22 @@ jobs: - name: Enable tls1 if: ${{ runner.os == 'Windows' }} run: | - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, 3DES_EDE_CBC, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.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" + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( 3DES_EDE_CBC,\)\(.*\)/\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, 3DES_EDE_CBC, 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 + sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( 3DES_EDE_CBC,\)\(.*\)/\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, 3DES_EDE_CBC, TLSv1.1,\)\(.*\)/\1\2\4/" ${{ env.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 + sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( 3DES_EDE_CBC,\)\(.*\)/\1\2\4/" ${{ env.JAVA_HOME }}/conf/security/java.security # 复制jre Windows - name: Copy Jre for Windows From 556aa8a979fcebf0f6e7a4a2de9ec82428aed98e Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 8 Aug 2023 17:46:15 +0800 Subject: [PATCH 0588/1069] Fix sqlserver connection rest error --- .github/workflows/release_test.yml | 3 --- .../src/components/CreateConnection/config/dataSource.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 6a357a2f3..d71beff83 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -73,21 +73,18 @@ jobs: if: ${{ runner.os == 'Windows' }} run: | sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( TLSv1, TLSv1.1,\)\(.*\)/\1\2\4/" "${{ env.JAVA_HOME }}/conf/security/java.security" - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( 3DES_EDE_CBC,\)\(.*\)/\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 - sed -i '' "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( 3DES_EDE_CBC,\)\(.*\)/\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 - sed -i "s/\(^jdk.tls.disabledAlgorithms=\)\(.*\)\( 3DES_EDE_CBC,\)\(.*\)/\1\2\4/" ${{ env.JAVA_HOME }}/conf/security/java.security # 复制jre Windows - name: Copy Jre for Windows diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index 2dbb0ffde..224b04d02 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -606,7 +606,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ extendInfo: [ { "key": "encrypt", - "value": "true" + "value": "false" }, { "key": "trustServerCertificate", From 323b5d01269d3ceb8583291dd79e59db55a96b41 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Tue, 8 Aug 2023 17:54:59 +0800 Subject: [PATCH 0589/1069] Fix sqlserver connection rest error --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dae6fa99f..dfa94d374 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +# 2.0.11 + +## 🐞 Bug Fixes + +- Fix the issue where SSH does not support older versions of encryption algorithms +- Fix the issue of SQL Server 2008 not being able to connect +- Fix the issue of not being able to view table name notes and field notes + +## 🐞 问题修复 + +- 修复 SSH 不支持老版本加密算法的问题 +- 修复 SQLServer2008 无法连接的问题 +- 修复无法查看表名备注、字段备注的问题 # 2.0.10 From 7e541c1345e5c80e5b1e8523bfc27990de16d8c6 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 12 Aug 2023 15:43:15 +0800 Subject: [PATCH 0590/1069] support view trigger producer function --- README_CN.md | 2 +- .../CreateConnection/config/dataSource.ts | 480 +++++++++--------- .../chat2db/plugin/mysql/MysqlMetaData.java | 114 ++++- .../chat2db/plugin/oracle/OracleDBManage.java | 22 + .../chat2db/plugin/oracle/OracleMetaData.java | 111 +++- .../domain/api/service/ViewService.java | 8 + .../core/impl/DataSourceServiceImpl.java | 7 +- .../domain/core/impl/ViewServiceImpl.java | 15 + .../web/api/aspect/ConnectionInfoHandler.java | 15 +- .../controller/rdb/FunctionController.java | 10 +- .../controller/rdb/ProcedureController.java | 8 +- .../api/controller/rdb/TriggerController.java | 8 +- .../api/controller/rdb/ViewController.java | 38 +- .../web/api/controller/rdb/vo/TableVO.java | 5 + .../web/api/controller/rdb/vo/ViewVO.java | 9 + .../main/java/ai/chat2db/spi/DBManage.java | 57 ++- .../main/java/ai/chat2db/spi/MetaData.java | 34 +- .../ai/chat2db/spi/jdbc/DefaultDBManage.java | 15 + .../chat2db/spi/jdbc/DefaultMetaService.java | 15 + .../java/ai/chat2db/spi/model/Function.java | 3 + .../java/ai/chat2db/spi/model/Procedure.java | 2 + .../main/java/ai/chat2db/spi/model/Table.java | 5 + .../java/ai/chat2db/spi/model/Trigger.java | 20 + .../java/ai/chat2db/spi/sql/ConnectInfo.java | 17 + .../ai/chat2db/spi/util/ResultSetUtils.java | 1 + 25 files changed, 745 insertions(+), 276 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ViewVO.java diff --git a/README_CN.md b/README_CN.md index de74739af..110885dc7 100644 --- a/README_CN.md +++ b/README_CN.md @@ -42,7 +42,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 - 🌈 AI 智能助手,支持自然语言转 SQL、SQL 转自然语言、SQL 优化建议 - 🔥 智能报表,利用AIGC能力,一句话生成报表。 - 👭 支持个人模式、支持团队协作模式,让研发协同效率更高。 -- 🔌 除支持目前主流数据库外,还支持国产数据库如:达梦、Oceanbase、北大金仓。 +- 🔌 除支持目前主流数据库外,还支持国产数据库如:达梦、Oceanbase、人大金仓。 - ⚙️ 强大的数据管理能力,支持数据表、视图、存储过程、函数、触发器、索引、序列、用户、角色、授权等管理 - 🛡 前端使用 Electron 开发,提供 Windows、Mac、Linux 客户端、网页版本一体化的解决方案 - 🎁 支持环境隔离、线上、日常数据权限分离 diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index 224b04d02..ce3d2810b 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -1476,126 +1476,126 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ type: DatabaseTypeCode.OCEANBASE }, //redis - { - baseInfo: { - items: [ - { - defaultValue: '@localhost', - inputType: InputType.INPUT, - labelNameCN: '名称', - labelNameEN: 'Name', - name: 'alias', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: 'localhost', - inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'Host', - name: 'host', - required: true, - styles: { - width: '70%', - } - }, - { - defaultValue: '6379', - inputType: InputType.INPUT, - labelNameCN: '端口', - labelNameEN: 'Port', - name: 'port', - labelTextAlign: 'right', - required: true, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: AuthenticationType.USERANDPASSWORD, - inputType: InputType.SELECT, - labelNameCN: '身份验证', - labelNameEN: 'Authentication', - name: 'authenticationType', - required: true, - selects: [ - { - items: [ - { - defaultValue: 'root', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'User', - name: 'user', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ], - label: 'User&Password', - value: AuthenticationType.USERANDPASSWORD, - }, - { - label: 'NONE', - value: AuthenticationType.NONE, - items: [], - - }, - ], - styles: { - width: '50%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '数据库', - labelNameEN: 'Database', - name: 'database', - required: false, - styles: { - width: '100%', - } - }, - { - defaultValue: 'jdbc:redis://localhost:6379', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'jdbc:redis://{host}:{port}/{database}', - }, - ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.REDIS - }, +// { +// baseInfo: { +// items: [ +// { +// defaultValue: '@localhost', +// inputType: InputType.INPUT, +// labelNameCN: '名称', +// labelNameEN: 'Name', +// name: 'alias', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// { +// defaultValue: 'localhost', +// inputType: InputType.INPUT, +// labelNameCN: '主机', +// labelNameEN: 'Host', +// name: 'host', +// required: true, +// styles: { +// width: '70%', +// } +// }, +// { +// defaultValue: '6379', +// inputType: InputType.INPUT, +// labelNameCN: '端口', +// labelNameEN: 'Port', +// name: 'port', +// labelTextAlign: 'right', +// required: true, +// styles: { +// width: '30%', +// labelWidthEN: '40px', +// labelWidthCN: '40px', +// labelAlign: 'right' +// } +// }, +// { +// defaultValue: AuthenticationType.USERANDPASSWORD, +// inputType: InputType.SELECT, +// labelNameCN: '身份验证', +// labelNameEN: 'Authentication', +// name: 'authenticationType', +// required: true, +// selects: [ +// { +// items: [ +// { +// defaultValue: 'root', +// inputType: InputType.INPUT, +// labelNameCN: '用户名', +// labelNameEN: 'User', +// name: 'user', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// { +// defaultValue: '', +// inputType: InputType.PASSWORD, +// labelNameCN: '密码', +// labelNameEN: 'Password', +// name: 'password', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// ], +// label: 'User&Password', +// value: AuthenticationType.USERANDPASSWORD, +// }, +// { +// label: 'NONE', +// value: AuthenticationType.NONE, +// items: [], +// +// }, +// ], +// styles: { +// width: '50%', +// } +// }, +// { +// defaultValue: '', +// inputType: InputType.INPUT, +// labelNameCN: '数据库', +// labelNameEN: 'Database', +// name: 'database', +// required: false, +// styles: { +// width: '100%', +// } +// }, +// { +// defaultValue: 'jdbc:redis://localhost:6379', +// inputType: InputType.INPUT, +// labelNameCN: 'URL', +// labelNameEN: 'URL', +// name: 'url', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// +// ], +// pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, +// template: 'jdbc:redis://{host}:{port}/{database}', +// }, +// ssh: sshConfig, +// extendInfo: [ +// +// ], +// type: DatabaseTypeCode.REDIS +// }, //hive { baseInfo: { @@ -1839,124 +1839,124 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ type: DatabaseTypeCode.KINGBASE }, //MONGODB - { - baseInfo: { - items: [ - { - defaultValue: '@localhost', - inputType: InputType.INPUT, - labelNameCN: '名称', - labelNameEN: 'Name', - name: 'alias', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: 'localhost', - inputType: InputType.INPUT, - labelNameCN: '主机', - labelNameEN: 'Host', - name: 'host', - required: true, - styles: { - width: '70%', - } - }, - { - defaultValue: '27017', - inputType: InputType.INPUT, - labelNameCN: '端口', - labelNameEN: 'Port', - name: 'port', - labelTextAlign: 'right', - required: true, - styles: { - width: '30%', - labelWidthEN: '40px', - labelWidthCN: '40px', - labelAlign: 'right' - } - }, - { - defaultValue: AuthenticationType.USERANDPASSWORD, - inputType: InputType.SELECT, - labelNameCN: '身份验证', - labelNameEN: 'Authentication', - name: 'authenticationType', - required: true, - selects: [ - { - items: [ - { - defaultValue: 'root', - inputType: InputType.INPUT, - labelNameCN: '用户名', - labelNameEN: 'User', - name: 'user', - required: true, - styles: { - width: '100%', - } - }, - { - defaultValue: '', - inputType: InputType.PASSWORD, - labelNameCN: '密码', - labelNameEN: 'Password', - name: 'password', - required: true, - styles: { - width: '100%', - } - }, - ], - label: 'User&Password', - value: AuthenticationType.USERANDPASSWORD, - }, - { - label: 'NONE', - value: AuthenticationType.NONE, - items: [], - - }, - ], - styles: { - width: '50%', - } - }, - { - defaultValue: '', - inputType: InputType.INPUT, - labelNameCN: '数据库', - labelNameEN: 'Database', - name: 'database', - required: false, - styles: { - width: '100%', - } - }, - { - defaultValue: 'mongodb://localhost:27017', - inputType: InputType.INPUT, - labelNameCN: 'URL', - labelNameEN: 'URL', - name: 'url', - required: true, - styles: { - width: '100%', - } - }, - - ], - pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, - template: 'mongodb://{host}:{port}/{database}', - }, - ssh: sshConfig, - extendInfo: [ - - ], - type: DatabaseTypeCode.MONGODB - }, +// { +// baseInfo: { +// items: [ +// { +// defaultValue: '@localhost', +// inputType: InputType.INPUT, +// labelNameCN: '名称', +// labelNameEN: 'Name', +// name: 'alias', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// { +// defaultValue: 'localhost', +// inputType: InputType.INPUT, +// labelNameCN: '主机', +// labelNameEN: 'Host', +// name: 'host', +// required: true, +// styles: { +// width: '70%', +// } +// }, +// { +// defaultValue: '27017', +// inputType: InputType.INPUT, +// labelNameCN: '端口', +// labelNameEN: 'Port', +// name: 'port', +// labelTextAlign: 'right', +// required: true, +// styles: { +// width: '30%', +// labelWidthEN: '40px', +// labelWidthCN: '40px', +// labelAlign: 'right' +// } +// }, +// { +// defaultValue: AuthenticationType.USERANDPASSWORD, +// inputType: InputType.SELECT, +// labelNameCN: '身份验证', +// labelNameEN: 'Authentication', +// name: 'authenticationType', +// required: true, +// selects: [ +// { +// items: [ +// { +// defaultValue: 'root', +// inputType: InputType.INPUT, +// labelNameCN: '用户名', +// labelNameEN: 'User', +// name: 'user', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// { +// defaultValue: '', +// inputType: InputType.PASSWORD, +// labelNameCN: '密码', +// labelNameEN: 'Password', +// name: 'password', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// ], +// label: 'User&Password', +// value: AuthenticationType.USERANDPASSWORD, +// }, +// { +// label: 'NONE', +// value: AuthenticationType.NONE, +// items: [], +// +// }, +// ], +// styles: { +// width: '50%', +// } +// }, +// { +// defaultValue: '', +// inputType: InputType.INPUT, +// labelNameCN: '数据库', +// labelNameEN: 'Database', +// name: 'database', +// required: false, +// styles: { +// width: '100%', +// } +// }, +// { +// defaultValue: 'mongodb://localhost:27017', +// inputType: InputType.INPUT, +// labelNameCN: 'URL', +// labelNameEN: 'URL', +// name: 'url', +// required: true, +// styles: { +// width: '100%', +// } +// }, +// +// ], +// pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, +// template: 'mongodb://{host}:{port}/{database}', +// }, +// ssh: sshConfig, +// extendInfo: [ +// +// ], +// type: DatabaseTypeCode.MONGODB +// }, ]; 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 d98f6b7b2..bbccea5ea 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 @@ -2,18 +2,24 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; public class MysqlMetaData extends DefaultMetaService implements MetaData { @Override - public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { + public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String tableName) { String sql = "SHOW CREATE TABLE " + format(databaseName) + "." - + format( tableName); - return SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> { + + format(tableName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("Create Table"); @@ -28,4 +34,106 @@ public String tableDDL(Connection connection, @NotEmpty String databaseName, Str public static String format(String tableName) { return "`" + tableName + "`"; } + + private static String ROUTINES_SQL + = "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + + "routine_name = '%s';"; + + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL,"FUNCTION", databaseName, functionName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + function.setRemarks(resultSet.getString("ROUTINE_COMMENT")); + function.setFunctionName(functionName); + function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); + return function; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + + } + + private static String TRIGGER_SQL + = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; + + private static String TRIGGER_SQL_LIST + = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; + + + @Override + public List triggers(Connection connection,String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + String sql = String.format(TRIGGER_SQL_LIST, databaseName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return triggers; + }); + } + + + @Override + public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, + String triggerName) { + + String sql = String.format(TRIGGER_SQL, databaseName, triggerName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setTriggerName(triggerName); + trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); + return trigger; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } + + @Override + public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, + String procedureName) { + String sql = String.format(ROUTINES_SQL,"PROCEDURE", databaseName, procedureName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); + procedure.setProcedureName(procedureName); + procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); + return procedure; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java index 09f96f401..f6a868785 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/OracleDBManage.java @@ -1,8 +1,30 @@ package ai.chat2db.plugin.oracle; +import java.sql.Connection; +import java.sql.SQLException; + import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; +import ai.chat2db.spi.sql.SQLExecutor; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; public class OracleDBManage extends DefaultDBManage implements DBManage { + @Override + public void connectDatabase(Connection connection, String database) { + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { + return; + } + String schemaName = connectInfo.getSchemaName(); + try { + SQLExecutor.getInstance().execute(connection, "ALTER SESSION SET CURRENT_SCHEMA = \"" + schemaName + "\";"); + } catch (SQLException e) { + + } + } + } 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 bb9b87f82..9346b4092 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 @@ -2,17 +2,23 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; +import jakarta.validation.constraints.NotEmpty; public class OracleMetaData extends DefaultMetaService implements MetaData { @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { - String sql = "select dbms_metadata.get_ddl('TABLE','"+tableName+"') as sql from dual," - + "user_tables where table_name = '" + tableName + "'"; - return SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> { + String sql = "select dbms_metadata.get_ddl('TABLE','" + tableName + "') as sql from dual," + + "user_tables where table_name = '" + tableName + "'"; + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); @@ -24,4 +30,103 @@ public String tableDDL(Connection connection, String databaseName, String schema return null; }); } + + private static String ROUTINES_SQL + = "SELECT LINE, TEXT " + + "FROM ALL_SOURCE " + + "WHERE TYPE = '%s' AND NAME = '%s' " + + "ORDER BY LINE;"; + + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL, "FUNCTION", functionName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + StringBuilder sb = new StringBuilder(); + while (resultSet.next()) { + sb.append(resultSet.getString("TEXT") + "\n"); + } + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(functionName); + function.setFunctionBody(sb.toString()); + return function; + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + + } + + private static String TRIGGER_SQL_LIST + = "SELECT TRIGGER_NAME " + + "FROM ALL_TRIGGERS WHERE OWNER = '%s';"; + + @Override + public List triggers(Connection connection, String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + return SQLExecutor.getInstance().executeSql(connection, String.format(TRIGGER_SQL_LIST, schemaName), + resultSet -> { + try { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return triggers; + }); + } + + @Override + public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, + String triggerName) { + + String sql = String.format(ROUTINES_SQL, "TRIGGER", triggerName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + StringBuilder sb = new StringBuilder(); + while (resultSet.next()) { + sb.append(resultSet.getString("TEXT") + "\n"); + } + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setTriggerName(triggerName); + trigger.setTriggerBody(resultSet.getString(sb.toString())); + return trigger; + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } + + @Override + public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, + String procedureName) { + String sql = String.format(ROUTINES_SQL, "PROCEDURE", procedureName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + StringBuilder sb = new StringBuilder(); + while (resultSet.next()) { + sb.append(resultSet.getString("TEXT") + "\n"); + } + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setProcedureName(procedureName); + procedure.setProcedureBody(sb.toString()); + return procedure; + } catch (SQLException e) { + throw new RuntimeException(e); + } + }); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java index b79fe9c3e..82d297016 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ViewService.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.api.service; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Table; import jakarta.validation.constraints.NotEmpty; @@ -19,4 +20,11 @@ public interface ViewService { ListResult
views(@NotEmpty String databaseName, String schemaName); + /** + * Querying the details of a view. + * + * @param databaseName + * @return + */ + DataResult
detail(@NotEmpty String databaseName, String schemaName,String tableName); } 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 3859791a7..80a767a0b 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 @@ -1,7 +1,6 @@ package ai.chat2db.server.domain.core.impl; import java.sql.Connection; -import java.sql.SQLException; import java.time.LocalDateTime; import java.util.List; @@ -72,6 +71,10 @@ private void preWarmingData(Long dataSourceId) { DataResult dataResult = queryById(dataSourceId); if (dataResult.success() && dataResult.getData() != null) { DataSource dataSource = dataResult.getData(); + DriverConfig driverConfig = dataSource.getDriverConfig(); + if (driverConfig == null || StringUtils.isBlank(driverConfig.getJdbcDriver())) { + return; + } try (Connection connection = IDriverManager.getConnection(dataSource.getUrl(), dataSource.getUserName(), dataSource.getPassword(), dataSource.getDriverConfig(), dataSource.getExtendMap())) { DatabaseQueryAllParam databaseQueryAllParam = new DatabaseQueryAllParam(); @@ -80,7 +83,7 @@ private void preWarmingData(Long dataSourceId) { databaseQueryAllParam.setDbType(dataSource.getType()); databaseQueryAllParam.setRefresh(true); databaseService.queryAll(databaseQueryAllParam); - } catch (SQLException e) { + } catch (Exception e) { log.error("preWarmingData error", e); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java index b9145046e..b9ec48462 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java @@ -1,7 +1,9 @@ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.ViewService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.stereotype.Service; @@ -13,4 +15,17 @@ public class ViewServiceImpl implements ViewService { public ListResult
views(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().views(Chat2DBContext.getConnection(),databaseName, schemaName)); } + + @Override + public DataResult
detail(String databaseName, String schemaName, String tableName) { + MetaData metaSchema = Chat2DBContext.getMetaData(); + String ddl = metaSchema.tableDDL(Chat2DBContext.getConnection(), databaseName, schemaName, tableName); + Table table = new Table(); + table.setDdl(ddl); + table.setName(tableName); + table.setSchemaName(schemaName); + table.setDatabaseName(databaseName); + return DataResult.of(table); + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index 89e39bd7c..c1f37d7e5 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -5,6 +5,7 @@ import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import ai.chat2db.spi.config.DriverConfig; @@ -36,11 +37,16 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr if (params != null && params.length > 0) { for (int i = 0; i < params.length; i++) { Object param = params[i]; - if (param instanceof DataSourceConsoleRequestInfo) { + if(param instanceof DataSourceBaseRequest){ + Long dataSourceId = ((DataSourceBaseRequest)param).getDataSourceId(); + String schemaName = ((DataSourceBaseRequest)param).getSchemaName(); + String database = ((DataSourceBaseRequest)param).getDatabaseName(); + Chat2DBContext.putContext(toInfo(dataSourceId, database, null,schemaName)); + }else if (param instanceof DataSourceConsoleRequestInfo) { Long dataSourceId = ((DataSourceConsoleRequestInfo)param).getDataSourceId(); Long consoleId = ((DataSourceConsoleRequestInfo)param).getConsoleId(); String database = ((DataSourceConsoleRequestInfo)param).getDatabaseName(); - Chat2DBContext.putContext(toInfo(dataSourceId, database, consoleId)); + Chat2DBContext.putContext(toInfo(dataSourceId, database, consoleId,null)); } else if (param instanceof DataSourceBaseRequestInfo) { Long dataSourceId = ((DataSourceBaseRequestInfo)param).getDataSourceId(); String database = ((DataSourceBaseRequestInfo)param).getDatabaseName(); @@ -54,7 +60,7 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr } } - public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { + public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId,String schemaName) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); if (!result.success() || dataSource == null) { @@ -69,6 +75,7 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { connectInfo.setDbType(dataSource.getType()); connectInfo.setUrl(dataSource.getUrl()); connectInfo.setDatabase(database); + connectInfo.setSchemaName(schemaName); connectInfo.setConsoleOwn(false); connectInfo.setDriver(dataSource.getDriver()); connectInfo.setSsh(dataSource.getSsh()); @@ -86,6 +93,6 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId) { } public ConnectInfo toInfo(Long dataSourceId, String database) { - return toInfo(dataSourceId, database, null); + return toInfo(dataSourceId, database, null,null); } } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java index aaad10328..97797d2b7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java @@ -2,6 +2,7 @@ import ai.chat2db.server.domain.api.service.FunctionService; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; import ai.chat2db.spi.model.Function; @@ -19,9 +20,12 @@ public class FunctionController { @Autowired private FunctionService functionService; - @GetMapping("/list") - public ListResult list(@Valid TableBriefQueryRequest request) { - return functionService.functions(request.getDatabaseName(), request.getSchemaName()); + public WebPageResult list(@Valid TableBriefQueryRequest request) { + ListResult functionListResult = functionService.functions(request.getDatabaseName(), + request.getSchemaName()); + return WebPageResult.of(functionListResult.getData(), Long.valueOf(functionListResult.getData().size()), 1, + functionListResult.getData().size()); } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java index 3dd2e28bc..39b63fd60 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java @@ -2,6 +2,7 @@ import ai.chat2db.server.domain.api.service.ProcedureService; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; import ai.chat2db.spi.model.Procedure; @@ -18,7 +19,10 @@ public class ProcedureController { private ProcedureService procedureService; @GetMapping("/list") - public ListResult list(@Valid TableBriefQueryRequest request) { - return procedureService.procedures(request.getDatabaseName(), request.getSchemaName()); + public WebPageResult list(@Valid TableBriefQueryRequest request) { + ListResult procedureListResult = procedureService.procedures(request.getDatabaseName(), + request.getSchemaName()); + return WebPageResult.of(procedureListResult.getData(), Long.valueOf(procedureListResult.getData().size()), 1, + procedureListResult.getData().size()); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java index da7bcb979..b01a10fab 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java @@ -2,6 +2,7 @@ import ai.chat2db.server.domain.api.service.TriggerService; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; import ai.chat2db.spi.model.Trigger; @@ -19,9 +20,10 @@ public class TriggerController { @Autowired private TriggerService triggerService; - @GetMapping("/list") - public ListResult list(@Valid TableBriefQueryRequest request) { - return triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); + public WebPageResult list(@Valid TableBriefQueryRequest request) { + ListResult listResult = triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); + return WebPageResult.of(listResult.getData(), Long.valueOf(listResult.getData().size()), 1, + listResult.getData().size()); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java index f2daa2ab3..7fdf01d23 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ViewController.java @@ -2,16 +2,27 @@ import java.util.List; +import ai.chat2db.server.domain.api.param.DropParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.api.service.ViewService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -22,14 +33,39 @@ public class ViewController { @Autowired private ViewService viewService; + @Autowired + private TableService tableService; @Autowired private RdbWebConverter rdbWebConverter; @GetMapping("/list") - public ListResult list(@Valid TableBriefQueryRequest request) { + public WebPageResult list(@Valid TableBriefQueryRequest request) { ListResult
tableDTOPageResult = viewService.views(request.getDatabaseName(), request.getSchemaName()); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); + return WebPageResult.of(tableVOS, Long.valueOf(tableVOS.size()), 1, tableVOS.size()); + } + + + @GetMapping("/column_list") + public ListResult columnList(@Valid TableDetailQueryRequest request) { + TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); + List tableColumns = tableService.queryColumns(queryParam); + List tableVOS = rdbWebConverter.columnDto2vo(tableColumns); return ListResult.of(tableVOS); } + + + @GetMapping("/detail") + public DataResult detail(@Valid TableDetailQueryRequest request) { + DataResult
dataResult = viewService.detail(request.getDatabaseName(),request.getSchemaName(),request.getTableName()); + TableVO tableVO = rdbWebConverter.tableDto2vo(dataResult.getData()); + return DataResult.of(tableVO); + } + @PostMapping("/delete") + public ActionResult delete(@Valid TableDeleteRequest request) { + DropParam dropParam = rdbWebConverter.tableDelete2dropParam(request); + return tableService.drop(dropParam); + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java index d4199fe37..13f49b684 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java @@ -36,4 +36,9 @@ public class TableVO { * 是否已经被固定 */ private boolean pinned; + + /** + * ddl + */ + private String ddl; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ViewVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ViewVO.java new file mode 100644 index 000000000..42094ead3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ViewVO.java @@ -0,0 +1,9 @@ +//package ai.chat2db.server.web.api.controller.rdb.vo; +// +//public class ViewVO extends TableVO{ +// +// /** +// * 视图脚本 +// */ +// private String script; +//} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java index 75c8235ce..40a7d0077 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/DBManage.java @@ -11,9 +11,9 @@ */ public interface DBManage { - /** * Create connection + * * @param connectInfo */ Connection getConnection(ConnectInfo connectInfo); @@ -21,53 +21,54 @@ public interface DBManage { /** * @param database */ - void connectDatabase(Connection connection,String database); + void connectDatabase(Connection connection, String database); /** * 修改数据库名称 + * * @param databaseName * @param newDatabaseName */ - void modifyDatabase(Connection connection,String databaseName, String newDatabaseName); - + void modifyDatabase(Connection connection, String databaseName, String newDatabaseName); /** * 创建数据库 + * * @param databaseName */ - void createDatabase(Connection connection,String databaseName); - + void createDatabase(Connection connection, String databaseName); /** * 删除数据库 + * * @param databaseName */ - void dropDatabase(Connection connection,String databaseName); - - + void dropDatabase(Connection connection, String databaseName); /** * 创建schema + * * @param databaseName * @param schemaName */ - void createSchema(Connection connection,String databaseName, String schemaName); + void createSchema(Connection connection, String databaseName, String schemaName); /** * 删除schema + * * @param databaseName * @param schemaName */ - void dropSchema(Connection connection,String databaseName, String schemaName); + void dropSchema(Connection connection, String databaseName, String schemaName); /** * 修改schema + * * @param databaseName * @param schemaName * @param newSchemaName */ - void modifySchema(Connection connection,String databaseName, String schemaName, String newSchemaName); - + void modifySchema(Connection connection, String databaseName, String schemaName, String newSchemaName); /** * 删除表结构 @@ -77,4 +78,34 @@ public interface DBManage { * @return */ void dropTable(Connection connection,@NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); + + /** + * 删除函数 + * + * @param databaseName + * @param functionName + * @return + */ + void dropFunction(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String functionName); + + /** + * 删除触发器 + * + * @param databaseName + * @param triggerName + * @return + */ + void dropTrigger(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String triggerName); + + /** + * 删除存储过程 + * + * @param databaseName + * @param triggerName + * @return + */ + void dropProcedure(Connection connection, @NotEmpty String databaseName, String schemaName, + @NotEmpty String triggerName); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 6f2967625..f1a0472a4 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -15,8 +15,8 @@ public interface MetaData { /** * Query all databases. - * @param connection * + * @param connection * @return */ List databases(Connection connection); @@ -128,4 +128,36 @@ List columns(Connection connection, @NotEmpty String databaseName, List indexes(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName); + /** + * Querying function detail under a schema. + * + * @param connection + * @param databaseName + * @param schemaName + * @param functionName + * @return + */ + Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName); + + /** + * Querying trigger under a schema. + * + * @param connection + * @param databaseName + * @param schemaName + * @param triggerName + * @return + */ + Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, String triggerName); + + /** + * Querying all procedures under a schema. + * + * @param connection + * @param schemaName + * @param databaseName + * @param procedureName + * @return + */ + Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName,String procedureName); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java index bf48aebd5..3e14efe25 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java @@ -116,6 +116,21 @@ public void modifySchema(Connection connection,String databaseName, String schem } + @Override + public void dropFunction(Connection connection, String databaseName, String schemaName, String functionName) { + + } + + @Override + public void dropTrigger(Connection connection, String databaseName, String schemaName, String triggerName) { + + } + + @Override + public void dropProcedure(Connection connection, String databaseName, String schemaName, String triggerName) { + + } + @Override public void dropTable(Connection connection,String databaseName, String schemaName, String tableName) { String sql = "DROP TABLE "+ tableName ; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 4999c88fe..117123130 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -90,4 +90,19 @@ public List columns(Connection connection,String databaseName, Stri public List indexes(Connection connection,String databaseName, String schemaName, String tableName) { return SQLExecutor.getInstance().indexes(connection,databaseName, schemaName, tableName); } + + @Override + public Function function(Connection connection, String databaseName, String schemaName, String functionName) { + return null; + } + + @Override + public Trigger trigger(Connection connection, String databaseName, String schemaName, String triggerName) { + return null; + } + + @Override + public Procedure procedure(Connection connection, String databaseName, String schemaName, String procedureName) { + return null; + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java index eab8fb5f0..13876e75f 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java @@ -37,4 +37,7 @@ public class Function { private Short functionType; private String specificName; + + private String functionBody; + } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java index 2592e3b89..97c4986f8 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java @@ -37,4 +37,6 @@ public class Procedure { private Short procedureType; private String specificName; + + private String procedureBody; } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java index ab6906f24..188c5d821 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java @@ -63,5 +63,10 @@ public class Table { * 是否置顶 */ private boolean pinned; + + /** + * ddl + */ + private String ddl; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java index 5df92a714..472ef555a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Trigger.java @@ -1,9 +1,29 @@ package ai.chat2db.spi.model; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + /** * @author jipengfei * @version : Trigger.java */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor public class Trigger { + + private String databaseName; + + private String schemaName; + + private String triggerName; + + private String eventManipulation; + + private String triggerBody; + } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java index 4831bfa2a..6cf380759 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java @@ -43,6 +43,13 @@ public class ConnectInfo { */ private String databaseName; + + + /** + * schema + */ + private String schemaName; + /** * 控制台ID */ @@ -73,6 +80,9 @@ public class ConnectInfo { */ private String dbType; + /** + * 端口 + */ private Integer port; /** @@ -486,4 +496,11 @@ public void setGmtModified(LocalDateTime gmtModified) { this.gmtModified = gmtModified; } + public String getSchemaName() { + return schemaName; + } + + public void setSchemaName(String schemaName) { + this.schemaName = schemaName; + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java index 3dae5f80e..f26a446d0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java @@ -44,6 +44,7 @@ public static Procedure buildProcedure(ResultSet resultSet) { return procedure; } + public static TableIndexColumn buildTableIndexColumn(ResultSet resultSet) throws SQLException { TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(getString(resultSet, "COLUMN_NAME")); From 4b8071926ccac80c2fd877fd44f99c9936e57796 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 12 Aug 2023 16:34:48 +0800 Subject: [PATCH 0591/1069] =?UTF-8?q?fix:=E6=9C=AC=E5=9C=B0=E5=AD=98?= =?UTF-8?q?=E5=82=A8=E7=9A=84=E4=B8=BB=E9=A2=98=E8=89=B2=E4=B8=8E=E8=83=8C?= =?UTF-8?q?=E6=99=AF=E8=89=B2=E4=B8=8E=E4=BB=A3=E7=A0=81=E4=B8=8D=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E6=97=B6=E5=AF=BC=E8=87=B4=E9=A1=B5=E9=9D=A2=E5=B4=A9?= =?UTF-8?q?=E6=BA=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/hooks/useTheme.ts | 16 ++++++++++++++-- chat2db-client/src/layouts/index.tsx | 14 -------------- chat2db-client/src/utils/localStorage.ts | 6 +++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/chat2db-client/src/hooks/useTheme.ts b/chat2db-client/src/hooks/useTheme.ts index 50e441a1a..9993a7575 100644 --- a/chat2db-client/src/hooks/useTheme.ts +++ b/chat2db-client/src/hooks/useTheme.ts @@ -6,13 +6,25 @@ import { ThemeType, PrimaryColorType } from '@/constants'; import { getPrimaryColor, getTheme, setPrimaryColor, setTheme } from '@/utils/localStorage'; const initialTheme = () => { - let backgroundColor = getTheme() || ThemeType.Dark; + const localStorageTheme = getTheme(); + const localStoragePrimaryColor = getPrimaryColor(); - let primaryColor = getPrimaryColor() || PrimaryColorType.Golden_Purple; + // 判断localStorage的theme在不在ThemeType中, 如果存在就用localStorageTheme + let backgroundColor = ThemeType.Light + if (Object.values(ThemeType).includes(localStorageTheme)) { + backgroundColor = localStorageTheme; + } + + let primaryColor = PrimaryColorType.Golden_Purple + if (Object.values(PrimaryColorType).includes(localStoragePrimaryColor)) { + primaryColor = localStoragePrimaryColor; + } if (backgroundColor === ThemeType.FollowOs) { backgroundColor = getOsTheme(); } + document.documentElement.setAttribute('theme', backgroundColor); + document.documentElement.setAttribute('primary-color', primaryColor); return { backgroundColor, primaryColor, diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index a22f00978..ea8ea78af 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -98,7 +98,6 @@ function AppContainer() { // 初始化app function collectInitApp() { monitorOsTheme(); - initTheme(); initLang(); setInitEnd(true); } @@ -119,19 +118,6 @@ function AppContainer() { }; } - // 初始化主题 - function initTheme() { - let theme = getTheme(); - if (theme === ThemeType.FollowOs) { - theme = - (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches - ? ThemeType.Dark - : ThemeType.Light) || ThemeType.Dark; - } - document.documentElement.setAttribute('theme', theme); - document.documentElement.setAttribute('primary-color', getPrimaryColor()); - } - // 初始化语言 function initLang() { if (!getLang()) { diff --git a/chat2db-client/src/utils/localStorage.ts b/chat2db-client/src/utils/localStorage.ts index 27979e75c..3f4499707 100644 --- a/chat2db-client/src/utils/localStorage.ts +++ b/chat2db-client/src/utils/localStorage.ts @@ -10,8 +10,8 @@ export function setLang(lang: LangType) { } export function getTheme(): ThemeType { - const themeColor:any = localStorage.getItem('theme') as ThemeType - if(themeColor){ + const themeColor: any = localStorage.getItem('theme') as ThemeType + if (themeColor) { return themeColor } localStorage.setItem('theme', ThemeType.Light) @@ -25,7 +25,7 @@ export function setTheme(theme: ThemeType) { export function getPrimaryColor(): PrimaryColorType { const primaryColor = localStorage.getItem('primary-color') as PrimaryColorType - if(primaryColor){ + if (primaryColor) { return primaryColor } localStorage.setItem('primary-color', PrimaryColorType.Golden_Purple) From 89e1c72d80d07a9986e3b69c139fa19dfbafb4c4 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 12 Aug 2023 16:49:20 +0800 Subject: [PATCH 0592/1069] style --- chat2db-client/src/components/SearchResult/TableBox/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index f893eec38..7109f55ed 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -234,7 +234,7 @@ export default function TableBox(props: ITableProps) { return ( <> -
{bottomStatus}
+
{bottomStatus}
); } else { From 5c180cc3b911e20e1dc53eba65978fa8235a0f8b Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 12 Aug 2023 17:52:26 +0800 Subject: [PATCH 0593/1069] claude ai support --- .../domain/api/enums/AiSqlSourceEnum.java | 5 + .../web/api/controller/ai/ChatController.java | 31 +++ .../ai/claude/client/ClaudeAIClient.java | 87 ++++++++ .../claude/client/ClaudeAiStreamClient.java | 186 ++++++++++++++++++ .../ClaudeHeaderAuthorizationInterceptor.java | 40 ++++ .../model/ClaudeChatCompletionsOptions.java | 38 ++++ .../ai/claude/model/ClaudeChatMessage.java | 15 ++ .../model/ClaudeCompletionResponse.java | 25 +++ .../ai/claude/model/ClaudeMessageLimit.java | 9 + .../listener/ClaudeAIEventSourceListener.java | 112 +++++++++++ 10 files changed, 548 insertions(+) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java index d475f83e7..522d57646 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java @@ -32,6 +32,11 @@ public enum AiSqlSourceEnum implements BaseEnum { */ CHAT2DBAI("CHAT2DB OPENAI"), + /** + * CLAUDE AI + */ + CLAUDEAI("CLAUDE AI"), + ; final String description; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 012f561fa..634ac7039 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -24,10 +24,14 @@ import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage; import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatRole; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; +import ai.chat2db.server.web.api.controller.ai.claude.client.ClaudeAIClient; +import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatCompletionsOptions; +import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage; import ai.chat2db.server.web.api.controller.ai.config.LocalCache; import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.listener.AzureOpenAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.listener.ClaudeAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.listener.OpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.listener.RestAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; @@ -212,6 +216,8 @@ private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sse return chatWithRestAi(queryRequest, sseEmitter); case AZUREAI : return chatWithAzureAi(queryRequest, sseEmitter, uid); + case CLAUDEAI: + return chatWithClaudeAi(queryRequest, sseEmitter, uid); } return chatWithOpenAi(queryRequest, sseEmitter, uid); } @@ -326,6 +332,31 @@ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sse return sseEmitter; } + + /** + * chat with claude ai + * + * @param queryRequest + * @param sseEmitter + * @param uid + * @return + * @throws IOException + */ + private SseEmitter chatWithClaudeAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { + String prompt = buildPrompt(queryRequest); + ClaudeChatMessage claudeChatMessage = new ClaudeChatMessage(); + claudeChatMessage.setText(prompt); + ClaudeChatCompletionsOptions chatCompletionsOptions = new ClaudeChatCompletionsOptions(); + chatCompletionsOptions.setPrompt(prompt); + claudeChatMessage.setCompletion(chatCompletionsOptions); + + buildSseEmitter(sseEmitter, uid); + + ClaudeAIEventSourceListener sourceListener = new ClaudeAIEventSourceListener(sseEmitter); + ClaudeAIClient.getInstance().streamCompletions(claudeChatMessage, sourceListener); + return sseEmitter; + } + /** * construct sseEmitter * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java new file mode 100644 index 000000000..77dd97f22 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAIClient.java @@ -0,0 +1,87 @@ + +package ai.chat2db.server.web.api.controller.ai.claude.client; + +import ai.chat2db.server.domain.api.model.Config; +import ai.chat2db.server.domain.api.service.ConfigService; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * @author jipengfei + * @version : OpenAIClient.java + */ +@Slf4j +public class ClaudeAIClient { + + public static final String CLAUDE_SESSION_KEY = "claude.sessionKey"; + + public static final String CLAUDE_API_HOST = "claude.apiHost"; + + public static final String CLAUDE_ORG_ID = "claude.orgId"; + + public static final String CLAUDE_USER_ID = "claude.userId"; + + + private static ClaudeAiStreamClient CLAUDE_AI_STREAM_CLIENT; + private static String apiKey; + + public static ClaudeAiStreamClient getInstance() { + if (CLAUDE_AI_STREAM_CLIENT != null) { + return CLAUDE_AI_STREAM_CLIENT; + } else { + return singleton(); + } + } + + private static ClaudeAiStreamClient singleton() { + if (CLAUDE_AI_STREAM_CLIENT == null) { + synchronized (ClaudeAIClient.class) { + if (CLAUDE_AI_STREAM_CLIENT == null) { + refresh(); + } + } + } + return CLAUDE_AI_STREAM_CLIENT; + } + + public static void refresh() { + String apikey = ""; + String orgId = ""; + String userId = ""; + String apiHost = "https://claude.ai/api/append_message"; + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config apiHostConfig = configService.find(CLAUDE_API_HOST).getData(); + if (apiHostConfig != null) { + apiHost = apiHostConfig.getContent(); + } + Config config = configService.find(CLAUDE_SESSION_KEY).getData(); + if (config != null) { + apikey = config.getContent(); + } + Config orgConfig = configService.find(CLAUDE_ORG_ID).getData(); + if (orgConfig != null) { + orgId = orgConfig.getContent(); + } + Config userConfig = configService.find(CLAUDE_USER_ID).getData(); + if (userConfig != null) { + userId = userConfig.getContent(); + } + log.info("refresh claude sessionKey:{}", maskApiKey(apikey)); + CLAUDE_AI_STREAM_CLIENT = ClaudeAiStreamClient.builder().apiHost(apiHost) + .sessionKey(apikey).orgId(orgId).userId(userId).build(); + apiKey = apikey; + } + + private static String maskApiKey(String input) { + if (StringUtils.isBlank(input)) { + return input; + } + + StringBuilder maskedString = new StringBuilder(input); + for (int i = input.length() / 4; i < input.length() / 2; i++) { + maskedString.setCharAt(i, '*'); + } + return maskedString.toString(); + } +} 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 new file mode 100644 index 000000000..d36079025 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/client/ClaudeAiStreamClient.java @@ -0,0 +1,186 @@ +package ai.chat2db.server.web.api.controller.ai.claude.client; + +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +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.ObjectMapper; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import okhttp3.sse.EventSources; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * 自定义AI接口client + * + * @author moji + */ +@Slf4j +public class ClaudeAiStreamClient { + + /** + * apikey + */ + @Getter + @NotNull + private String sessionKey; + + /** + * endpoint + */ + @Getter + @NotNull + private String orgId; + + /** + * deployId + */ + @Getter + private String apiHost; + + @Getter + private String userId; + + /** + * okHttpClient + */ + @Getter + private OkHttpClient okHttpClient; + + + /** + * @param builder + */ + private ClaudeAiStreamClient(Builder builder) { + this.sessionKey = builder.sessionKey; + this.orgId = builder.orgId; + this.apiHost = builder.apiHost; + this.userId = builder.userId; + if (Objects.isNull(builder.okHttpClient)) { + builder.okHttpClient = this.okHttpClient(); + } + okHttpClient = builder.okHttpClient; + } + + /** + * okhttpclient + */ + private OkHttpClient okHttpClient() { + OkHttpClient okHttpClient = new OkHttpClient + .Builder() + .addInterceptor(new ClaudeHeaderAuthorizationInterceptor(this.sessionKey, this.orgId)) + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(50, TimeUnit.SECONDS) + .readTimeout(50, TimeUnit.SECONDS) + .build(); + return okHttpClient; + } + + /** + * 构造 + * + * @return + */ + public static ClaudeAiStreamClient.Builder builder() { + return new ClaudeAiStreamClient.Builder(); + } + + public static final class Builder { + private String sessionKey; + + private String orgId; + + private String apiHost; + + private String userId; + + /** + * 自定义OkhttpClient + */ + private OkHttpClient okHttpClient; + + public Builder() { + } + + public ClaudeAiStreamClient.Builder sessionKey(String sessionKey) { + this.sessionKey = sessionKey; + return this; + } + + /** + * @param apiHost + * @return + */ + public ClaudeAiStreamClient.Builder apiHost(String apiHost) { + this.apiHost = apiHost; + return this; + } + + /** + * @param orgId + * @return + */ + public ClaudeAiStreamClient.Builder orgId(String orgId) { + this.orgId = orgId; + return this; + } + + public ClaudeAiStreamClient.Builder userId(String userId) { + this.userId = userId; + return this; + } + + public ClaudeAiStreamClient.Builder okHttpClient(OkHttpClient val) { + this.okHttpClient = val; + return this; + } + + public ClaudeAiStreamClient build() { + return new ClaudeAiStreamClient(this); + } + + } + + /** + * chat + * + * @param claudeChatMessage + * @param eventSourceListener + */ + public void streamCompletions(ClaudeChatMessage claudeChatMessage, EventSourceListener eventSourceListener) { + if (Objects.isNull(eventSourceListener)) { + log.error("param error:AzureEventSourceListener cannot be empty"); + throw new ParamBusinessException(); + } + log.info("Claude AI, prompt:{}", claudeChatMessage.getText()); + try { + claudeChatMessage.setOrganization_uuid(this.orgId); + claudeChatMessage.setConversation_uuid(this.userId); + EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); + ObjectMapper mapper = new ObjectMapper(); + String requestBody = mapper.writeValueAsString(claudeChatMessage); + + Request request = new Request.Builder() + .url(this.apiHost) + .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) + .build(); + //创建事件 + EventSource eventSource = factory.newEventSource(request, eventSourceListener); + log.info("finish invoking claude ai"); + } catch (Exception e) { + log.error("claude ai error", e); + eventSourceListener.onFailure(null, e, null); + throw new ParamBusinessException(); + } + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java new file mode 100644 index 000000000..351bb6c54 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/interceptor/ClaudeHeaderAuthorizationInterceptor.java @@ -0,0 +1,40 @@ +package ai.chat2db.server.web.api.controller.ai.claude.interceptor; + +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import lombok.Getter; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + +/** + * 描述:请求增加header apikey + * + * @author grt + * @since 2023-03-23 + */ +@Getter +public class ClaudeHeaderAuthorizationInterceptor implements Interceptor { + + private String sessionKey; + + private String orgId; + + public ClaudeHeaderAuthorizationInterceptor(String sessionKey, String orgId) { + this.orgId = orgId; + this.sessionKey = sessionKey; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request original = chain.request(); + Request request = original.newBuilder() + .header("Cookie", "sessionKey=" + sessionKey) + .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) + .method(original.method(), original.body()) + .build(); + return chain.proceed(request); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java new file mode 100644 index 000000000..cc7961247 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatCompletionsOptions.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. +package ai.chat2db.server.web.api.controller.ai.claude.model; + +import lombok.Data; + +@Data +public final class ClaudeChatCompletionsOptions { + + private Boolean incremental = true; + + private String model = "claude-2"; + + private String prompt; + + private String timezone = "Asia/Shanghai"; + + private Boolean stream = true; + + public Boolean isStream() { + return this.stream; + } + + public ClaudeChatCompletionsOptions setStream(Boolean stream) { + this.stream = stream; + return this; + } + + public String getModel() { + return this.model; + } + + public ClaudeChatCompletionsOptions setModel(String model) { + this.model = model; + return this; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java new file mode 100644 index 000000000..5b9fe07bf --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeChatMessage.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.web.api.controller.ai.claude.model; + +import lombok.Data; + +@Data +public class ClaudeChatMessage { + + private String conversation_uuid; + + private String organization_uuid; + + private String text; + + private ClaudeChatCompletionsOptions completion; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java new file mode 100644 index 000000000..627078c9b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeCompletionResponse.java @@ -0,0 +1,25 @@ + +package ai.chat2db.server.web.api.controller.ai.claude.model; + +import com.unfbx.chatgpt.entity.common.Usage; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * @author moji + * @version : ClaudeCompletionResponse.java + */ +@Data +public class ClaudeCompletionResponse implements Serializable { + @Serial + private static final long serialVersionUID = 4968922211204353592L; + private String log_id; + private String stop_reason; + private String stop; + private String model; + private String completion; + private Usage usage; + private ClaudeMessageLimit messageLimit; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java new file mode 100644 index 000000000..f6b0c4e8a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/model/ClaudeMessageLimit.java @@ -0,0 +1,9 @@ +package ai.chat2db.server.web.api.controller.ai.claude.model; + +import lombok.Data; + +@Data +public class ClaudeMessageLimit { + + private String type; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java new file mode 100644 index 000000000..c6401867b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java @@ -0,0 +1,112 @@ +package ai.chat2db.server.web.api.controller.ai.listener; + +import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeCompletionResponse; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.unfbx.chatgpt.entity.chat.Message; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Objects; + +/** + * ClaudeAIEventSourceListener + */ +@Slf4j +public class ClaudeAIEventSourceListener extends EventSourceListener { + + private SseEmitter sseEmitter; + + private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + public ClaudeAIEventSourceListener(SseEmitter sseEmitter) { + this.sseEmitter = sseEmitter; + } + + /** + * {@inheritDoc} + */ + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("ClaudeAIEventSourceListener..."); + } + + /** + * {@inheritDoc} + */ + @SneakyThrows + @Override + public void onEvent(EventSource eventSource, String id, String type, String data) { + log.info("Claude AI data:{}", data); + if (data.equals("[DONE]")) { + log.info("Claude AI end"); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]") + .reconnectTime(3000)); + sseEmitter.complete(); + return; + } + // 读取Json + ClaudeCompletionResponse completionResponse = mapper.readValue(data, ClaudeCompletionResponse.class); + String text = completionResponse.getCompletion(); + Message message = new Message(); + if (text != null) { + message.setContent(text); + sseEmitter.send(SseEmitter.event() + .id(null) + .data(message) + .reconnectTime(3000)); + } + } + + @Override + public void onClosed(EventSource eventSource) { + sseEmitter.complete(); + log.info("Claude AI closed..."); + } + + @Override + public void onFailure(EventSource eventSource, Throwable t, Response response) { + try { + if (Objects.isNull(response)) { + String message = t.getMessage(); + Message sseMessage = new Message(); + sseMessage.setContent(message); + sseEmitter.send(SseEmitter.event() + .id("[ERROR]") + .data(sseMessage)); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + sseEmitter.complete(); + return; + } + ResponseBody body = response.body(); + String bodyString = null; + if (Objects.nonNull(body)) { + bodyString = body.string(); + log.error("Claude sse error:{}", bodyString, t); + } else { + log.error("Claude sse body error:{}", response, t); + } + eventSource.cancel(); + Message message = new Message(); + message.setContent("Claude sse error:" + bodyString); + sseEmitter.send(SseEmitter.event() + .id("[ERROR]") + .data(message)); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + sseEmitter.complete(); + } catch (Exception exception) { + log.error("发送数据异常:", exception); + } + } +} From 2503dd6eb563def9bf71b0e9017afeb37b9d00f9 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sat, 12 Aug 2023 18:06:29 +0800 Subject: [PATCH 0594/1069] support view trigger producer function --- .../chat2db/plugin/mysql/MysqlMetaData.java | 24 +++ .../chat2db/plugin/oracle/OracleMetaData.java | 25 +++ .../plugin/postgresql/PostgreSQLMetaData.java | 131 ++++++++++++++++ .../plugin/sqlserver/SqlServerMetaData.java | 142 +++++++++++++++--- .../domain/api/service/FunctionService.java | 10 +- .../domain/api/service/ProcedureService.java | 10 ++ .../domain/api/service/TriggerService.java | 9 ++ .../core/impl/DlTemplateServiceImpl.java | 54 +++---- .../domain/core/impl/FunctionServiceImpl.java | 6 + .../core/impl/ProcedureServiceImpl.java | 6 + .../domain/core/impl/TriggerServiceImpl.java | 6 + .../domain/core/impl/ViewServiceImpl.java | 7 +- .../controller/rdb/FunctionController.java | 10 +- .../controller/rdb/ProcedureController.java | 11 +- .../api/controller/rdb/TriggerController.java | 11 +- .../rdb/request/FunctionDetailRequest.java | 34 +++++ .../rdb/request/FunctionPageRequest.java | 44 ++++++ .../rdb/request/ProcedureDetailRequest.java | 38 +++++ .../rdb/request/ProcedurePageRequest.java | 44 ++++++ .../rdb/request/TriggerDetailRequest.java | 38 +++++ .../rdb/request/TriggerPageRequest.java | 43 ++++++ .../main/java/ai/chat2db/spi/MetaData.java | 12 ++ .../chat2db/spi/jdbc/DefaultMetaService.java | 5 + 23 files changed, 662 insertions(+), 58 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionDetailRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionPageRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedureDetailRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedurePageRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerDetailRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerPageRequest.java 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 bbccea5ea..3a5a73061 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 @@ -9,6 +9,7 @@ import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; @@ -136,4 +137,27 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, return null; }); } + + private static String VIEW_SQL + = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; + + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, databaseName, viewName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + table.setDdl(resultSet.getString("definition")); + return table; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } } 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 9346b4092..e4993392c 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 @@ -9,6 +9,7 @@ import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; @@ -129,4 +130,28 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, } }); } + + + private static String VIEW_SQL + = "SELECT VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER = '%s' AND VIEW_NAME = '%s';"; + + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, schemaName, viewName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + table.setDdl(resultSet.getString("TEXT")); + return table; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } } 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 9c2962078..830af8cf5 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 @@ -8,8 +8,13 @@ import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; +import jakarta.validation.constraints.NotEmpty; import static ai.chat2db.plugin.postgresql.consts.SQLConst.FUNCTION_SQL; @@ -74,4 +79,130 @@ public List schemas(Connection connection, String databaseName) { return databases; }); } + + + private static String ROUTINES_SQL + = " SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p " + + "where p.prokind = '%s' and p.proname='%s';"; + + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL,"f", functionName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(functionName); + function.setFunctionBody(resultSet.getString("code")); + return function; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + + } + + private static String TRIGGER_SQL + = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " + + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" + + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s' AND t.tgname ='%s';"; + + private static String TRIGGER_SQL_LIST + = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " + + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" + + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s';"; + + + @Override + public List triggers(Connection connection,String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + String sql = String.format(TRIGGER_SQL_LIST, schemaName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("trigger_name")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return triggers; + }); + } + + + @Override + public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, + String triggerName) { + + String sql = String.format(TRIGGER_SQL, schemaName, triggerName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setTriggerName(triggerName); + trigger.setTriggerBody(resultSet.getString("trigger_body")); + return trigger; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } + + @Override + public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, + String procedureName) { + String sql = String.format(ROUTINES_SQL,"p", procedureName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setProcedureName(procedureName); + procedure.setProcedureBody(resultSet.getString("code")); + return procedure; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } + + private static String VIEW_SQL + = "SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname = '%s' AND viewname = '%s';"; + + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, schemaName, viewName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + table.setDdl(resultSet.getString("definition")); + return table; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } } 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 d40d353ee..50f76edd0 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 @@ -2,13 +2,17 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; -import com.google.common.collect.Lists; +import jakarta.validation.constraints.NotEmpty; public class SqlServerMetaData extends DefaultMetaService implements MetaData { private String functionSQL @@ -62,24 +66,124 @@ public String tableDDL(Connection connection, String databaseName, String schema return null; }); } + + private static String ROUTINES_SQL + = "SELECT type_desc, OBJECT_NAME(object_id) AS FunctionName, OBJECT_DEFINITION(object_id) AS functionDefinition FROM sys.objects WHERE type_desc IN(%s) and name = '%s' ;"; + + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL,"'SQL_SCALAR_FUNCTION', 'SQL_TABLE_VALUED_FUNCTION'", functionName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(functionName); + function.setFunctionBody(resultSet.getString("functionDefinition")); + return function; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + + } + + private static String TRIGGER_SQL + = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS TriggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects WHERE xtype = 'TR' and name = '%s';"; + + private static String TRIGGER_SQL_LIST + = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS TriggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects WHERE xtype = 'TR' "; + + + @Override + public List triggers(Connection connection,String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + return SQLExecutor.getInstance().executeSql(connection, TRIGGER_SQL_LIST, resultSet -> { + try { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("triggerName")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return triggers; + }); + } + + + @Override + public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, + String triggerName) { + + String sql = String.format(TRIGGER_SQL, triggerName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setTriggerName(triggerName); + trigger.setTriggerBody(resultSet.getString("triggerName")); + return trigger; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } + + @Override + public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, + String procedureName) { + String sql = String.format(ROUTINES_SQL,"'SQL_STORED_PROCEDURE'", procedureName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setProcedureName(procedureName); + procedure.setProcedureBody(resultSet.getString("procedureDefinition")); + return procedure; + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); + } + + + private static String VIEW_SQL + = "SELECT TABLE_SCHEMA, TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; + @Override - public List databases(Connection connection) { - List databases = Lists.newArrayList(); - SQLExecutor.getInstance().executeSql(connection, "SELECT name " - + "FROM sys.databases", - resultSet -> { - try { - while (resultSet.next()) { - Database database = new Database(); - String databaseName = resultSet.getString("name"); - database.setName(databaseName); - databases.add(database); - } - } catch (SQLException e) { - throw new RuntimeException(e); + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, schemaName, viewName); + return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { + try { + if (resultSet.next()) { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + table.setDdl(resultSet.getString("VIEW_DEFINITION")); + return table; } - return null; - }); - return databases; + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java index 779cc4a56..ccce55e9f 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/FunctionService.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.api.service; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Function; import jakarta.validation.constraints.NotEmpty; @@ -18,5 +19,12 @@ public interface FunctionService { */ ListResult functions(@NotEmpty String databaseName, String schemaName); - + /** + * Querying function information. + * @param databaseName + * @param schemaName + * @param functionName + * @return + */ + DataResult detail(String databaseName, String schemaName, String functionName); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java index 3e7004e3c..3df6301bf 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ProcedureService.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.api.service; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Procedure; import jakarta.validation.constraints.NotEmpty; @@ -13,4 +14,13 @@ public interface ProcedureService { * @return */ ListResult procedures(@NotEmpty String databaseName, String schemaName); + + /** + * Querying procedure information. + * @param databaseName + * @param schemaName + * @param procedureName + * @return + */ + DataResult detail(String databaseName, String schemaName, String procedureName); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java index e051b93b5..6309b5b0b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TriggerService.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.api.service; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Trigger; import jakarta.validation.constraints.NotEmpty; @@ -14,4 +15,12 @@ public interface TriggerService { */ ListResult triggers(@NotEmpty String databaseName, String schemaName); + /** + * Querying trigger information. + * @param databaseName + * @param schemaName + * @param triggerName + * @return + */ + DataResult detail(String databaseName, String schemaName, String triggerName); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index da46024b7..d98510588 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -74,36 +74,38 @@ public ListResult execute(DlExecuteParam param) { // 解析sql分页 SQLStatement sqlStatement; + boolean autoLimit = false; try { sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); - } catch (ParserException e) { - log.warn("解析sql失败:{}", sql, e); - ExecuteResult executeResult = ExecuteResult.builder() - .success(Boolean.FALSE) - .originalSql(originalSql) - .sql(sql) - .message(e.getMessage()) - .build(); - result.add(executeResult); - continue; - } - // 是否需要代码帮忙分页 - boolean autoLimit = false; - if (sqlStatement instanceof SQLSelectStatement) { - // 不是查询全部数据 而且 用户自己没有传分页 - autoLimit = BooleanUtils.isNotTrue(param.getPageSizeAll()) && SQLUtils.getLimit(sqlStatement, dbType) - == null; - if (autoLimit) { - pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); - pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); - int offset = (pageNo - 1) * pageSize; - try { - sql = PagerUtils.limit(sql, dbType, offset, pageSize); - } catch (Exception e) { - autoLimit = false; + // 是否需要代码帮忙分页 + + if (sqlStatement instanceof SQLSelectStatement) { + // 不是查询全部数据 而且 用户自己没有传分页 + autoLimit = BooleanUtils.isNotTrue(param.getPageSizeAll()) && SQLUtils.getLimit(sqlStatement, + dbType) + == null; + if (autoLimit) { + pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); + pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); + int offset = (pageNo - 1) * pageSize; + try { + sql = PagerUtils.limit(sql, dbType, offset, pageSize); + } catch (Exception e) { + autoLimit = false; + } } + sqlType = SqlTypeEnum.SELECT.getCode(); } - sqlType = SqlTypeEnum.SELECT.getCode(); + } catch (ParserException e) { + log.warn("解析sql失败:{}", sql, e); + //ExecuteResult executeResult = ExecuteResult.builder() + // .success(Boolean.FALSE) + // .originalSql(originalSql) + // .sql(sql) + // .message(e.getMessage()) + // .build(); + //result.add(executeResult); + //continue; } ExecuteResult executeResult = execute(sql); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java index 58f961a1e..040bca7d0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/FunctionServiceImpl.java @@ -1,6 +1,7 @@ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.FunctionService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.sql.Chat2DBContext; @@ -12,4 +13,9 @@ public class FunctionServiceImpl implements FunctionService { public ListResult functions(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().functions(Chat2DBContext.getConnection(),databaseName, schemaName)); } + + @Override + public DataResult detail(String databaseName, String schemaName, String functionName) { + return DataResult.of(Chat2DBContext.getMetaData().function(Chat2DBContext.getConnection(), databaseName, schemaName, functionName)); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java index 34f2bb015..c6bafe71e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ProcedureServiceImpl.java @@ -1,6 +1,7 @@ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.ProcedureService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Procedure; import ai.chat2db.spi.sql.Chat2DBContext; @@ -13,4 +14,9 @@ public class ProcedureServiceImpl implements ProcedureService { public ListResult procedures(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().procedures(Chat2DBContext.getConnection(),databaseName, schemaName)); } + + @Override + public DataResult detail(String databaseName, String schemaName, String procedureName) { + return DataResult.of(Chat2DBContext.getMetaData().procedure(Chat2DBContext.getConnection(), databaseName, schemaName, procedureName)); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java index 90b167ff6..d6c3fae9d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TriggerServiceImpl.java @@ -1,6 +1,7 @@ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.service.TriggerService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.Chat2DBContext; @@ -12,4 +13,9 @@ public class TriggerServiceImpl implements TriggerService { public ListResult triggers(String databaseName, String schemaName) { return ListResult.of(Chat2DBContext.getMetaData().triggers(Chat2DBContext.getConnection(),databaseName, schemaName)); } + + @Override + public DataResult detail(String databaseName, String schemaName, String triggerName) { + return DataResult.of(Chat2DBContext.getMetaData().trigger(Chat2DBContext.getConnection(), databaseName, schemaName, triggerName)); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java index b9ec48462..57442e8b4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ViewServiceImpl.java @@ -19,12 +19,7 @@ public ListResult
views(String databaseName, String schemaName) { @Override public DataResult
detail(String databaseName, String schemaName, String tableName) { MetaData metaSchema = Chat2DBContext.getMetaData(); - String ddl = metaSchema.tableDDL(Chat2DBContext.getConnection(), databaseName, schemaName, tableName); - Table table = new Table(); - table.setDdl(ddl); - table.setName(tableName); - table.setSchemaName(schemaName); - table.setDatabaseName(databaseName); + Table table = metaSchema.view(Chat2DBContext.getConnection(), databaseName, schemaName, tableName); return DataResult.of(table); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java index 97797d2b7..8a43baaa0 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/FunctionController.java @@ -1,10 +1,12 @@ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.service.FunctionService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.FunctionDetailRequest; +import ai.chat2db.server.web.api.controller.rdb.request.FunctionPageRequest; import ai.chat2db.spi.model.Function; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -21,11 +23,15 @@ public class FunctionController { private FunctionService functionService; @GetMapping("/list") - public WebPageResult list(@Valid TableBriefQueryRequest request) { + public WebPageResult list(@Valid FunctionPageRequest request) { ListResult functionListResult = functionService.functions(request.getDatabaseName(), request.getSchemaName()); return WebPageResult.of(functionListResult.getData(), Long.valueOf(functionListResult.getData().size()), 1, functionListResult.getData().size()); } + @GetMapping("/detail") + public DataResult detail(@Valid FunctionDetailRequest request) { + return functionService.detail(request.getDatabaseName(), request.getSchemaName(), request.getFunctionName()); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java index 39b63fd60..375dbbd2a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/ProcedureController.java @@ -1,10 +1,12 @@ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.service.ProcedureService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.ProcedureDetailRequest; +import ai.chat2db.server.web.api.controller.rdb.request.ProcedurePageRequest; import ai.chat2db.spi.model.Procedure; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; @@ -19,10 +21,15 @@ public class ProcedureController { private ProcedureService procedureService; @GetMapping("/list") - public WebPageResult list(@Valid TableBriefQueryRequest request) { + public WebPageResult list(@Valid ProcedurePageRequest request) { ListResult procedureListResult = procedureService.procedures(request.getDatabaseName(), request.getSchemaName()); return WebPageResult.of(procedureListResult.getData(), Long.valueOf(procedureListResult.getData().size()), 1, procedureListResult.getData().size()); } + + @GetMapping("/detail") + public DataResult detail(@Valid ProcedureDetailRequest request) { + return procedureService.detail(request.getDatabaseName(), request.getSchemaName(), request.getProcedureName()); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java index b01a10fab..3ee67f6ef 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TriggerController.java @@ -1,10 +1,12 @@ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.service.TriggerService; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; -import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TriggerDetailRequest; +import ai.chat2db.server.web.api.controller.rdb.request.TriggerPageRequest; import ai.chat2db.spi.model.Trigger; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -21,9 +23,14 @@ public class TriggerController { private TriggerService triggerService; @GetMapping("/list") - public WebPageResult list(@Valid TableBriefQueryRequest request) { + public WebPageResult list(@Valid TriggerPageRequest request) { ListResult listResult = triggerService.triggers(request.getDatabaseName(), request.getSchemaName()); return WebPageResult.of(listResult.getData(), Long.valueOf(listResult.getData().size()), 1, listResult.getData().size()); } + + @GetMapping("/detail") + public DataResult detail(@Valid TriggerDetailRequest request) { + return triggerService.detail(request.getDatabaseName(), request.getSchemaName(), request.getTriggerName()); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionDetailRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionDetailRequest.java new file mode 100644 index 000000000..344b093f2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionDetailRequest.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class FunctionDetailRequest implements DataSourceBaseRequestInfo { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间,pg,oracle需要,mysql不需要 + */ + private String schemaName; + + /** + * function name + */ + private String functionName; + + /** + * if true, refresh the cache + */ + private boolean refresh; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionPageRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionPageRequest.java new file mode 100644 index 000000000..8f625c5b2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/FunctionPageRequest.java @@ -0,0 +1,44 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.io.Serial; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class FunctionPageRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { + + @Serial + private static final long serialVersionUID = -364547173428396332L; + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间,pg,oracle需要,mysql不需要 + */ + private String schemaName; + + /** + * 模糊搜索词 + */ + private String searchKey; + + /** + * function name + */ + private String functionName; + + /** + * if true, refresh the cache + */ + private boolean refresh; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedureDetailRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedureDetailRequest.java new file mode 100644 index 000000000..4e080230a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedureDetailRequest.java @@ -0,0 +1,38 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.io.Serial; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class ProcedureDetailRequest implements DataSourceBaseRequestInfo { + + @Serial + private static final long serialVersionUID = -364547173428396332L; + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间,pg,oracle需要,mysql不需要 + */ + private String schemaName; + + /** + * procedure name + */ + private String procedureName; + + /** + * if true, refresh the cache + */ + private boolean refresh; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedurePageRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedurePageRequest.java new file mode 100644 index 000000000..f0116c1e3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ProcedurePageRequest.java @@ -0,0 +1,44 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.io.Serial; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class ProcedurePageRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { + + @Serial + private static final long serialVersionUID = -364547173428396332L; + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间,pg,oracle需要,mysql不需要 + */ + private String schemaName; + + /** + * 模糊搜索词 + */ + private String searchKey; + + /** + * procedure name + */ + private String procedureName; + + /** + * if true, refresh the cache + */ + private boolean refresh; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerDetailRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerDetailRequest.java new file mode 100644 index 000000000..7dbe71ec8 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerDetailRequest.java @@ -0,0 +1,38 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.io.Serial; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class TriggerDetailRequest implements DataSourceBaseRequestInfo { + @Serial + private static final long serialVersionUID = -364547173428396332L; + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间,pg,oracle需要,mysql不需要 + */ + private String schemaName; + + /** + * trigger name + */ + private String triggerName; + + /** + * if true, refresh the cache + */ + private boolean refresh; +} + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerPageRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerPageRequest.java new file mode 100644 index 000000000..9b4b103a3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TriggerPageRequest.java @@ -0,0 +1,43 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import java.io.Serial; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class TriggerPageRequest extends PageQueryRequest implements DataSourceBaseRequestInfo { + @Serial + private static final long serialVersionUID = -364547173428396332L; + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; + + /** + * 表所在空间,pg,oracle需要,mysql不需要 + */ + private String schemaName; + + /** + * 模糊搜索词 + */ + private String searchKey; + + /** + * trigger name + */ + private String triggerName; + + /** + * if true, refresh the cache + */ + private boolean refresh; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index f1a0472a4..6abcc0204 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -52,6 +52,18 @@ String tableDDL(Connection connection, @NotEmpty String databaseName, String sch */ List
tables(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName); + /** + * Querying view information. + * + * @param connection + * @param databaseName + * @param schemaName + * @param viewName + * @return + */ + Table view(Connection connection, @NotEmpty String databaseName, String schemaName, String viewName); + + /** * Querying all views under a schema. * diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 117123130..e37b91894 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -55,6 +55,11 @@ public List
tables(Connection connection,String databaseName, String sche return SQLExecutor.getInstance().tables(connection,databaseName, schemaName, tableName, new String[]{"TABLE"}); } + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + return null; + } + @Override public List
views(Connection connection,String databaseName, String schemaName) { return SQLExecutor.getInstance().tables(connection,databaseName, schemaName, null, new String[]{"VIEW"}); From aefe29366726ebc3894e311f8808944014274339 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 12 Aug 2023 19:25:18 +0800 Subject: [PATCH 0595/1069] add operation type --- .../java/ai/chat2db/server/domain/api/model/Operation.java | 5 +++++ .../server/domain/api/param/OperationPageQueryParam.java | 5 +++++ .../chat2db/server/domain/api/param/OperationSavedParam.java | 5 +++++ .../server/domain/api/param/OperationUpdateParam.java | 5 +++++ .../server/domain/repository/entity/OperationSavedDO.java | 5 +++++ ...\234\344\277\235\345\255\230\347\261\273\345\236\213.sql" | 2 ++ .../operation/saved/request/OperationCreateRequest.java | 5 +++++ .../operation/saved/request/OperationUpdateRequest.java | 5 +++++ .../web/api/controller/operation/saved/vo/OperationVO.java | 5 +++++ 9 files changed, 42 insertions(+) create mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_8__\346\223\215\344\275\234\344\277\235\345\255\230\347\261\273\345\236\213.sql" diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java index fcd58d257..c141932b4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java @@ -74,4 +74,9 @@ public class Operation { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * operation type + */ + private String operationType; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java index 470ac7d71..01e882161 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java @@ -41,4 +41,9 @@ public class OperationPageQueryParam extends PageQueryParam { * orderBy modify time desc */ private boolean orderByDesc; + + /** + * operation type + */ + private String operationType; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java index 6571daa8d..910048700 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java @@ -49,4 +49,9 @@ public class OperationSavedParam { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * operation type + */ + private String operationType; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java index b6c8c3619..01b4cf2e4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java @@ -57,4 +57,9 @@ public class OperationUpdateParam { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * operation type + */ + private String operationType; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/OperationSavedDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/OperationSavedDO.java index e2611d22f..902a13f76 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/OperationSavedDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/OperationSavedDO.java @@ -83,4 +83,9 @@ public class OperationSavedDO implements Serializable { * schema名称 */ private String dbSchemaName; + + /** + * operation type + */ + private String operationType; } diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_8__\346\223\215\344\275\234\344\277\235\345\255\230\347\261\273\345\236\213.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_8__\346\223\215\344\275\234\344\277\235\345\255\230\347\261\273\345\236\213.sql" new file mode 100644 index 000000000..2fb4363b2 --- /dev/null +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V1_0_8__\346\223\215\344\275\234\344\277\235\345\255\230\347\261\273\345\236\213.sql" @@ -0,0 +1,2 @@ +ALTER TABLE `operation_saved` ADD COLUMN `operation_type` varchar(1024) NULL COMMENT '操作类型'; + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationCreateRequest.java index 51cbc9534..187f093f1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/OperationCreateRequest.java @@ -43,4 +43,9 @@ public class OperationCreateRequest extends DataSourceBaseRequest { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * operation type + */ + private String operationType; } 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 1272ebe5c..c53a61d52 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 @@ -38,4 +38,9 @@ public class OperationUpdateRequest { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * operation type + */ + private String operationType; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/vo/OperationVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/vo/OperationVO.java index 2351a8c30..d6f002fda 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/vo/OperationVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/vo/OperationVO.java @@ -65,4 +65,9 @@ public class OperationVO { * 是否在tab中被打开,y表示打开,n表示未打开 */ private String tabOpened; + + /** + * operation type + */ + private String operationType; } From 7d6cd4d5ec30e8b39dfcb22663c34b5e5eba7d5a Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sat, 12 Aug 2023 19:32:29 +0800 Subject: [PATCH 0596/1069] add operation type --- .../server/domain/core/impl/OperationServiceImpl.java | 3 +++ .../operation/saved/request/OperationQueryRequest.java | 5 +++++ 2 files changed, 8 insertions(+) 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 8252afbdf..e5a3d7f65 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 @@ -101,6 +101,9 @@ public PageResult queryPage(OperationPageQueryParam param) { if (StringUtils.isNotBlank(param.getTabOpened())) { queryWrapper.eq("tab_opened", param.getTabOpened()); } + if (StringUtils.isNotBlank(param.getOperationType())) { + queryWrapper.eq("operation_type", param.getOperationType()); + } Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); 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 3f993ed74..31dd7702c 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 @@ -41,4 +41,9 @@ public class OperationQueryRequest extends PageQueryRequest { * orderBy modify time desc */ private Boolean orderByDesc; + + /** + * operation type + */ + private String operationType; } From 91cf84caecbe4c152b8ae81d4fa552aa6e31f332 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 12 Aug 2023 20:07:45 +0800 Subject: [PATCH 0597/1069] =?UTF-8?q?feat:=E5=87=BD=E6=95=B0=E8=A7=A6?= =?UTF-8?q?=E5=8F=91=E5=99=A8=E8=BF=87=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.vscode/settings.json | 12 + .../src/components/Iconfont/index.tsx | 6 +- chat2db-client/src/components/Tabs/index.less | 9 + chat2db-client/src/components/Tabs/index.tsx | 2 + chat2db-client/src/constants/common.ts | 31 ++ chat2db-client/src/constants/tree.ts | 11 + chat2db-client/src/i18n/en-us/workspace.ts | 4 + chat2db-client/src/i18n/zh-cn/workspace.ts | 5 + chat2db-client/src/models/workspace.ts | 32 ++ .../workspace/components/SaveList/index.less | 72 ++++ .../workspace/components/SaveList/index.tsx | 231 +++++++++++++ .../workspace/components/TableList/index.less | 65 ++++ .../workspace/components/TableList/index.tsx | 157 +++++++++ .../main/workspace/components/Tree/index.tsx | 8 +- .../workspace/components/Tree/treeConfig.tsx | 186 ++++++++++- .../components/WorkspaceLeft/index.less | 138 -------- .../components/WorkspaceLeft/index.tsx | 310 +----------------- .../components/WorkspaceRight/index.less | 5 + .../components/WorkspaceRight/index.tsx | 108 ++++-- chat2db-client/src/service/sql.ts | 60 +++- chat2db-client/src/typings/common.ts | 3 +- chat2db-client/src/typings/tree.ts | 12 + 22 files changed, 994 insertions(+), 473 deletions(-) create mode 100644 chat2db-client/src/pages/main/workspace/components/SaveList/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx create mode 100644 chat2db-client/src/pages/main/workspace/components/TableList/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/TableList/index.tsx diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 10c4199ae..04e59cc0d 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -23,17 +23,24 @@ "Appstore", "CLICKHOUSE", "Cascader", + "Consolas", "DBAI", "Dmaven", "Iconfont", "JDBC", "KEYPAIR", + "KINGBASE", + "MARIADB", "Mddhhmmss", + "Menlo", + "OCEANBASE", "OPENAI", + "POSTGRESQL", "RESTAI", "SQLSERVER", "Sercurity", "USERANDPASSWORD", + "VIEWCOLUMN", "ahooks", "antd", "bgcolor", @@ -45,12 +52,17 @@ "favicons", "findstr", "gtag", + "indexs", "lsof", "netstat", "pgsql", "pnpm", "remaininguses", + "scrollbar", "sortablejs", + "ueabe", + "ueabf", + "ueac", "umijs", "uuidv", "wireframe" diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 4670dc97e..8ac998310 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_71qdvj9b14y.woff2?t=1689947297468') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_71qdvj9b14y.woff?t=1689947297468') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_71qdvj9b14y.ttf?t=1689947297468') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_26r1r56bfkj.woff2?t=1691821503781') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_26r1r56bfkj.woff?t=1691821503781') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_26r1r56bfkj.ttf?t=1691821503781') format('truetype'); } `; let style = document.createElement('style'); diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 6ec2157a2..dae9857f1 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -31,6 +31,10 @@ margin: 0px 2px; cursor: pointer; user-select: none; + .text{ + display: flex; + align-items: center; + } &:hover { .tab-focus(); .text { @@ -131,3 +135,8 @@ outline: none; } } + +.prefixIcon{ + flex-shrink: 0; + margin-right: 4px; +} diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index e96ea7429..7b06098f0 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -5,6 +5,7 @@ import lodash from 'lodash'; import styles from './index.less'; export interface IOption { + prefixIcon?: string; label: string | React.ReactNode; value: number | string; } @@ -90,6 +91,7 @@ export default memo(function Tab(props) { { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> :
+ {t.prefixIcon && } {t.label}
} diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index c18077bda..e88a2d80d 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -32,3 +32,34 @@ export enum OSType { MAC = 'Mac', RESTS = 'rests', } + +export enum OperationType { + CONSOLE = 'console', + FUNCTION = 'function', + PROCEDURE = 'procedure', + VIEW = 'view', + TRIGGER = 'trigger', +} + +export const operationTypeConfig: { + [key in OperationType]: { + icon: string + }; +} = { + [OperationType.CONSOLE]: { + icon: '\ue619' + }, + [OperationType.VIEW]: { + icon: '\ue647' + }, + [OperationType.FUNCTION]: { + icon: '\ue6fd' + }, + [OperationType.PROCEDURE]: { + icon: '\ue647' + }, + + [OperationType.TRIGGER]: { + icon: '\ue647' + } +} \ No newline at end of file diff --git a/chat2db-client/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts index f382628fd..648f32d5b 100644 --- a/chat2db-client/src/constants/tree.ts +++ b/chat2db-client/src/constants/tree.ts @@ -11,4 +11,15 @@ export enum TreeNodeType { KEY = 'key', INDEXES = 'indexes', INDEX = 'index', + VIEWS = 'views', // 视图组 + VIEW = 'view', // 视图 + VIEWCOLUMN = 'viewColumn', + VIEWCOLUMNS = 'viewColumns', + FUNCTIONS = 'functions', // 函数组 + FUNCTION = 'function', // 函数 + PROCEDURES = 'procedures', // procedure组 + PROCEDURE = 'procedure', // procedure + TRIGGERS = 'triggers', // trigger组 + TRIGGER = 'trigger', // trigger + } \ No newline at end of file diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 4fea04001..cee30ecc3 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -15,4 +15,8 @@ export default { 'workspace.table.export.cur.csv': 'Export result of current page set csv', 'workspace.table.export.all.insert': 'Export result set insert sql', 'workspace.table.export.cur.insert': 'Export result of current page set insert sql', + 'workspace.tree.view': 'View', + 'workspace.tree.trigger': 'Trigger', + 'workspace.tree.function': 'Function', + 'workspace.tree.procedure': 'Procedure', }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 4025b522f..0bca87de9 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -14,4 +14,9 @@ export default { 'workspace.table.export.cur.csv': '导出当前页结果集 csv', 'workspace.table.export.all.insert': '导出结果集 insert sql', 'workspace.table.export.cur.insert': '导出当前页结果集 insert sql', + 'workspace.tree.view': '视图', + 'workspace.tree.trigger': '触发器', + 'workspace.tree.function': '函数', + 'workspace.tree.procedure': '过程', + }; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index cd099b9c3..306c59b56 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -25,6 +25,7 @@ export interface IWorkspaceModelState { curConsoleId: number | null; openConsoleList: IConsole[]; curTableList: ITreeNode[]; + curViewList: ITreeNode[]; } export interface IWorkspaceModelType { @@ -39,6 +40,7 @@ export interface IWorkspaceModelType { setOpenConsoleList: Reducer; setCurConsoleId: Reducer; setCurTableList: Reducer; + setCurViewList: Reducer; }; effects: { fetchDatabaseAndSchema: Effect; @@ -46,6 +48,7 @@ export interface IWorkspaceModelType { fetchGetSavedConsole: Effect; fetchGetCurTableList: Effect; fetchGetSavedConsoleLoading: Effect; + fetchGetCurViewList: Effect; }; } @@ -59,6 +62,7 @@ const WorkspaceModel: IWorkspaceModelType = { consoleList: [], openConsoleList: [], curTableList: [], + curViewList: [], curConsoleId: null }, @@ -115,6 +119,14 @@ const WorkspaceModel: IWorkspaceModelType = { curTableList: payload, }; }, + + // 视图列表 + setCurViewList(state, { payload }) { + return { + ...state, + curViewList: payload, + }; + }, }, effects: { @@ -191,6 +203,26 @@ const WorkspaceModel: IWorkspaceModelType = { } catch { + } + }, + *fetchGetCurViewList({ payload, callback }, { put, call }) { + try { + const res = (yield treeConfig[TreeNodeType.VIEWS].getChildren!({ + pageNo: 1, + pageSize: 999, + ...payload, + })) as ITreeNode[]; + // 异步操作完成后调用回调函数 + if (callback && typeof callback === 'function') { + callback(res); + } + yield put({ + type: 'setCurViewList', + payload: res, + }); + } + catch { + } }, }, diff --git a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less new file mode 100644 index 000000000..0dbfaadf2 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -0,0 +1,72 @@ +@import '../../../../../styles/var.less'; + +.saveModule { + flex-shrink: 0; +} + +.leftModuleTitle { + flex-shrink: 0; + margin-bottom: 10px; + .leftModuleTitleText { + display: flex; + justify-content: space-between; + align-items: center; + height: 26px; + .modelName { + font-weight: bold; + } + .iconBox { + display: flex; + align-items: center; + } + .refreshIcon { + margin-right: 10px; + } + .refreshIcon, + .searchIcon { + cursor: pointer; + &:hover { + color: var(--color-primary); + } + } + } + .leftModuleTitleSearch { + height: 26px; + } +} + +.saveBoxList { + margin: 0px -10px; + height: calc(26px * 3 + 6px); + overflow-y: auto; +} + +.saveItem { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0px 10px; + margin-bottom: 2px; + line-height: 26px; + border-radius: 2px; + user-select: none; + cursor: pointer; + .saveItemText{ + width: 0px; + flex: 1; + .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 new file mode 100644 index 000000000..d24357809 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -0,0 +1,231 @@ +import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react'; +import classnames from 'classnames'; +import i18n from '@/i18n'; +import { connect } from 'umi'; +import { Cascader, Divider, Input, Dropdown, Button, Spin } from 'antd'; +import Iconfont from '@/components/Iconfont'; +import LoadingContent from '@/components/Loading/LoadingContent'; +import { IConnectionModelType } from '@/models/connection'; +import { IWorkspaceModelType } from '@/models/workspace'; +import historyServer from '@/service/history'; +import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; +import { IConsole, ITreeNode } from '@/typings'; +import styles from './index.less'; +import { approximateList } from '@/utils'; + +interface IProps { + className?: string; + workspaceModel: IWorkspaceModelType['state'], + dispatch: any; + tableLoading: boolean; + databaseLoading: boolean; +} + +const dvaModel = connect( + ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ + connectionModel: connection, + workspaceModel: workspace, + tableLoading: loading.effects['workspace/fetchGetCurTableList'], + databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'], + }), +); + +const SaveList = dvaModel(function (props: any) { + const { workspaceModel, dispatch } = props; + const { curWorkspaceParams, consoleList } = workspaceModel; + const [searching, setSearching] = useState(false); + const inputRef = useRef(); + const [searchedList, setSearchedList] = useState(); + + 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({ + cursor: 'start', + }); + } + }, [searching]) + + + function openSearch() { + setSearching(true); + } + + function onBlur() { + if (!inputRef.current.input.value) { + setSearching(false); + setSearchedList(undefined); + } + } + + function onChange(value: string) { + setSearchedList(approximateList(consoleList, value,)) + } + + function openConsole(data: IConsole) { + + let p: any = { + id: data.id, + tabOpened: ConsoleOpenedStatus.IS_OPEN + }; + historyServer.updateSavedConsole(p).then((res) => { + + dispatch({ + type: 'workspace/setCurConsoleId', + payload: data.id, + }); + + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + orderByDesc: false, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + ...curWorkspaceParams + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: res.data, + }) + } + }) + }); + } + + function deleteSaved(data: IConsole) { + let p: any = { + id: data.id, + }; + historyServer.deleteSavedConsole(p).then((res) => { + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + orderByDesc: true, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + ...curWorkspaceParams + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: res.data, + }) + } + }) + dispatch({ + type: 'workspace/fetchGetSavedConsole', + payload: { + orderByDesc: true, + status: ConsoleStatus.RELEASE, + ...curWorkspaceParams + }, + callback: (res: any) => { + dispatch({ + type: 'workspace/setConsoleList', + payload: res.data, + }) + } + }) + }); + } + + return ( +
+
+ { + searching ? +
+ } + onBlur={onBlur} + onChange={(e) => onChange(e.target.value)} + allowClear + /> +
+ : +
+
{i18n('workspace.title.saved')}
+
+ {/*
refreshTableList()}> + +
*/} +
openSearch()}> + +
+
+
+ } +
+
+ + {(searchedList || consoleList)?.map((t: IConsole) => { + return ( +
{ + openConsole(t) + }} + key={t.id} + className={styles.saveItem} + > +
+ +
+ { + openConsole(t) + } + }, + { + key: 'delete', + label: i18n('common.button.delete'), + onClick: () => { + deleteSaved(t) + }, + }, + ], + }} + > +
+ +
+
+ +
+ ); + })} +
+
+
+ ); +}); + +export default dvaModel(SaveList); diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less new file mode 100644 index 000000000..35d173645 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -0,0 +1,65 @@ +@import '../../../../../styles/var.less'; + +.tableModule { + display: flex; + flex-direction: column; + flex: 1; + height: 0; +} + +.leftModuleTitle { + flex-shrink: 0; + margin-bottom: 10px; + .leftModuleTitleText { + display: flex; + justify-content: space-between; + align-items: center; + height: 26px; + cursor: pointer; + user-select: none; + .modelName { + display: flex; + align-items: center; + font-weight: bold; + } + .iconBox { + display: flex; + align-items: center; + } + .refreshIcon { + margin-right: 10px; + } + .refreshIcon, + .searchIcon { + cursor: pointer; + &:hover { + color: var(--color-primary); + } + } + } + .leftModuleTitleSearch { + height: 26px; + } +} + +.treeBox { + flex: 1; + height: 0; + overflow-y: auto; + margin: 0px -10px; +} + +.refreshIconBox{ + cursor: pointer; + &:hover{ + color: var(--color-primary) + } +} + +.cascaderPopup{ + :global{ + .ant-cascader-menu{ + height: auto; + } + } +} diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx new file mode 100644 index 000000000..76fe39ab7 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -0,0 +1,157 @@ +import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react'; +import classnames from 'classnames'; +import i18n from '@/i18n'; +import { connect } from 'umi'; +import { Input, Cascader } from 'antd'; +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 { ITreeNode } from '@/typings'; +import { TreeNodeType } from '@/constants'; +import styles from './index.less'; +import { approximateTreeNode } from '@/utils'; + +interface IOption { + value: TreeNodeType; + label: string; +} + +const optionsList: IOption[] = [ + { + value: TreeNodeType.TABLES, + label: i18n('common.text.table') + }, + { value: TreeNodeType.VIEWS, label: i18n('workspace.tree.view') }, + { value: TreeNodeType.FUNCTIONS, label: i18n('workspace.tree.function') }, + { value: TreeNodeType.PROCEDURES, label: i18n('workspace.tree.procedure') }, + { value: TreeNodeType.TRIGGERS, label: i18n('workspace.tree.trigger') }, +] + +const dvaModel = connect( + ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ + connectionModel: connection, + workspaceModel: workspace, + tableLoading: loading.effects['workspace/fetchGetCurTableList'], + databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'], + }), +); + +const TableList = dvaModel(function (props: any) { + const { workspaceModel, dispatch, tableLoading } = props; + const { curWorkspaceParams, curTableList, curViewList } = workspaceModel; + const [searching, setSearching] = useState(false); + const inputRef = useRef(); + const [searchedTableList, setSearchedTableList] = useState(); + const isReady = curWorkspaceParams?.dataSourceId && ((curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName) || (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null)) + const [curType, setCurType] = useState(optionsList[0]); + const [curList, setCurList] = useState([]); + + useEffect(() => { + if (isReady) { + treeConfig[curType.value].getChildren!({ + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + }).then(res => { + setCurList(res); + console.log(res) + if (curType.value === TreeNodeType.TABLES) { + dispatch({ + type: 'workspace/setCurTableList', + payload: res, + }) + } + }) + } + }, [curWorkspaceParams, curType]); + + useEffect(() => { + if (searching) { + inputRef.current!.focus({ + cursor: 'start', + }); + } + }, [searching]) + + function openSearch() { + setSearching(true); + } + + function onBlur() { + if (!inputRef.current.input.value) { + setSearching(false); + setSearchedTableList(undefined); + } + } + + function onChange(value: string) { + setSearchedTableList(approximateTreeNode(curList, value)) + } + + function refreshTableList() { + if (isReady) { + dispatch({ + type: 'workspace/fetchGetCurTableList', + payload: { + ...curWorkspaceParams, + refresh: true, + extraParams: curWorkspaceParams, + } + }) + } + } + + function cascaderChange(value: string[], selectedOptions: IOption[]) { + setCurType(selectedOptions[0]); + } + + return ( +
+
+ { + searching ? +
+ } + onBlur={onBlur} + onChange={(e) => onChange(e.target.value)} + allowClear + /> +
+ : +
+ +
+ {curType.label} + +
+
+
+
refreshTableList()}> + +
+
openSearch()}> + +
+
+
+ } +
+ + + +
+ ); +}); + +export default dvaModel(TableList); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index c54f6cd1c..6e42d3827 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -139,7 +139,13 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { } function nodeDoubleClick() { - if (data.treeNodeType === TreeNodeType.TABLE) { + 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 diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index d105bd7f6..ff48015ed 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -40,6 +40,18 @@ export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string, unfold [TreeNodeType.INDEX]: { icon: '\ue65b' }, + [TreeNodeType.VIEW]: { + icon: '\ue647' + }, + [TreeNodeType.FUNCTION]: { + icon: '\ue6fd' + }, + [TreeNodeType.PROCEDURE]: { + icon: '\ue647' + }, + [TreeNodeType.TRIGGER]: { + icon: '\ue647' + }, } export enum OperationColumn { @@ -92,7 +104,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { connectionService.getDBList(params).then(res => { - const data: ITreeNode[] = res.map(t => { + const data: ITreeNode[] = res.map((t:any)=> { return { key: t.name, name: t.name, @@ -167,6 +179,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh ], }, + [TreeNodeType.TABLES]: { icon: '\ueac5', getChildren: (params) => { @@ -195,11 +208,12 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh ], }, + [TreeNodeType.TABLE]: { icon: '\ue63e', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - const tableList = [ + const list = [ { name: 'columns', treeNodeType: TreeNodeType.COLUMNS, @@ -220,13 +234,179 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, ] - r(tableList); + r(list); }) }, operationColumn: [ OperationColumn.ExportDDL, OperationColumn.DeleteTable, OperationColumn.Top ], }, + + [TreeNodeType.VIEWS]: { + icon: '\ue647', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void, j) => { + mysqlServer.getViewList(params).then(res => { + const viewList: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.name, + treeNodeType: TreeNodeType.VIEW, + key: t.name, + pinned: t.pinned, + comment: t.comment, + extraParams: { + ...params.extraParams, + tableName: t.name + } + } + }) + r(viewList); + }).catch(error => { + j(error) + }) + }) + }, + }, + + [TreeNodeType.FUNCTIONS]: { + icon: '\ue647', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void, j) => { + mysqlServer.getFunctionList(params).then(res => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.functionName, + treeNodeType: TreeNodeType.FUNCTION, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + functionName: t.functionName + } + } + }) + r(list); + }).catch(error => { + j(error) + }) + }) + }, + }, + + [TreeNodeType.FUNCTION]: { + icon: '\ue6fd', + }, + + [TreeNodeType.PROCEDURES]: { + icon: '\ue647', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void, j) => { + mysqlServer.getProcedureList(params).then(res => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.procedureName, + treeNodeType: TreeNodeType.PROCEDURE, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + procedureName: t.procedureName + } + } + }) + r(list); + }).catch(error => { + j(error) + }) + }) + }, + }, + + [TreeNodeType.PROCEDURE]: { + icon: '\ue611' + }, + + [TreeNodeType.TRIGGERS]: { + icon: '\ue647', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void, j) => { + mysqlServer.getTriggerList(params).then(res => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.triggerName, + treeNodeType: TreeNodeType.TRIGGER, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + triggerName: t.triggerName + } + } + }) + r(list); + }).catch(error => { + j(error) + }) + }) + }, + }, + + [TreeNodeType.TRIGGER]: { + icon: '\ue611' + }, + + [TreeNodeType.VIEW]: { + icon: '\ue647', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void, j) => { + const list = [ + { + name: 'columns', + treeNodeType: TreeNodeType.COLUMNS, + key: 'columns', + extraParams: params.extraParams + }, + ] + r(list); + }) + }, + }, + + [TreeNodeType.VIEWCOLUMNS]: { + icon: '\ue647', + getChildren: (params) => { + return new Promise((r: (value: ITreeNode[]) => void, j) => { + mysqlServer.getViewColumnList(params).then(res => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.name, + treeNodeType: TreeNodeType.VIEWCOLUMN, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + } + } + }) + r(list); + }).catch(error => { + j(error) + }) + }) + }, + }, + [TreeNodeType.VIEWCOLUMN]: { + icon: '\ue647', + }, + [TreeNodeType.COLUMNS]: { icon: '\ueac5', getChildren: (params: ITableParams) => { 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 2a9d82e8e..7e52af89d 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -18,59 +18,6 @@ flex-shrink: 0; } -.tableModule { - display: flex; - flex-direction: column; - flex: 1; - height: 0; -} - -.saveModule { - flex-shrink: 0; -} - -.boxLeftSave { -} - -.boxLeftConnection { -} - -.selectDatabaseBox { - display: flex; - align-items: center; - width: 100%; - user-select: none; - .currentDatabase { - flex: 1; - width: 0px; - display: flex; - align-items: center; - cursor: pointer; - i { - flex-shrink: 0; - margin-left: 10px; - } - .name { - font-size: 16px; - font-weight: bold; - max-width: 90%; - .f-single-line(); - } - } - - .otherOperations { - flex-shrink: 0; - } - - .iconBox { - width: 30px; - height: 30px; - display: flex; - justify-content: center; - align-items: center; - } -} - .cascaderPopup { :global { .ant-cascader-menu-item-content { @@ -79,84 +26,6 @@ } } -.saveBoxList { - margin: 0px -10px; - height: calc(26px * 3 + 6px); - overflow-y: auto; -} - -.saveBoxList { - margin: 0px -10px; -} - -.treeBox { - flex: 1; - height: 0; - overflow-y: auto; - margin: 0px -10px; -} - -.saveItem { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0px 10px; - margin-bottom: 2px; - line-height: 26px; - border-radius: 2px; - user-select: none; - cursor: pointer; - .saveItemText{ - width: 0px; - flex: 1; - .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; - } - } -} - -.leftModuleTitle { - flex-shrink: 0; - margin-bottom: 10px; - .leftModuleTitleText { - display: flex; - justify-content: space-between; - align-items: center; - height: 26px; - .modelName { - font-weight: bold; - } - .iconBox { - display: flex; - align-items: center; - } - .refreshIcon { - margin-right: 10px; - } - .refreshIcon, - .searchIcon { - cursor: pointer; - &:hover { - color: var(--color-primary); - } - } - } - .leftModuleTitleSearch { - height: 26px; - } -} - .createButtonBox { padding: 10px 0px 10px; } @@ -169,10 +38,3 @@ margin-right: 10px; } } - -.refreshIconBox{ - cursor: pointer; - &:hover{ - color: var(--color-primary) - } -} 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 e95de2c60..27acdc05e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -14,6 +14,8 @@ import { IConsole, ITreeNode, ICreateConsole } from '@/typings'; import styles from './index.less'; import { approximateTreeNode, approximateList } from '@/utils'; import historyService from '@/service/history'; +import TableList from '../TableList'; +import SaveList from '../SaveList'; interface IProps { className?: string; @@ -84,7 +86,6 @@ const WorkspaceLeft = memo(function (props) { useEffect(() => { document.addEventListener('keydown', (e) => { - console.log(e.metaKey, e.key === 't') if ((e.ctrlKey || e.metaKey) && e.key === 't') { e.preventDefault(); addConsole(); @@ -94,9 +95,9 @@ const WorkspaceLeft = memo(function (props) { return (
- + - +
tables(Connection connection, String databaseName, String sch while (resultSet.next()) { n++; tables.add(buildTable(resultSet)); - if (n >= 5000) {// 最多只取5000条 + if (n >= 1000) {// 最多只取1000条 break; } } From 51415a1d3331da310ee56082698f20ae43190da7 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 12:48:20 +0800 Subject: [PATCH 0603/1069] support view trigger producer function --- .../core/impl/DlTemplateServiceImpl.java | 4 +- chat2db-server/chat2db-server-test/pom.xml | 12 ++++++ .../server/test/temp/SQLParseTest.java | 42 +++++++++++++++++++ .../web/api/controller/sql/SqlController.java | 28 +++++++++++++ .../sql/request/SqlFormatRequest.java | 17 ++++++++ chat2db-server/chat2db-spi/pom.xml | 5 +++ .../java/ai/chat2db/spi/util/SqlUtils.java | 18 ++++++++ 7 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SQLParseTest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFormatRequest.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index d98510588..03f8273b6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -12,7 +12,6 @@ import com.alibaba.druid.sql.ast.SQLStatement; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.parser.ParserException; -import com.alibaba.druid.sql.parser.SQLParserUtils; import ai.chat2db.server.domain.api.param.DlCountParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; @@ -31,6 +30,7 @@ import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; +import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; @@ -58,7 +58,7 @@ public ListResult execute(DlExecuteParam param) { sqlAnalyseParam.setSql(param.getSql()); DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); - List sqlList = SQLParserUtils.splitAndRemoveComment(param.getSql(), dbType); + List sqlList = SqlUtils.parse(param.getSql(), dbType); if (CollectionUtils.isEmpty(sqlList)) { throw new BusinessException("dataSource.sqlAnalysisError"); } diff --git a/chat2db-server/chat2db-server-test/pom.xml b/chat2db-server/chat2db-server-test/pom.xml index 7b21ac40d..175517935 100644 --- a/chat2db-server/chat2db-server-test/pom.xml +++ b/chat2db-server/chat2db-server-test/pom.xml @@ -23,6 +23,18 @@ spring-boot-starter-test test + + com.github.jsqlparser + jsqlparser + 4.6 + test + + + com.github.vertical-blank + sql-formatter + 2.0.4 + test + diff --git a/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SQLParseTest.java b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SQLParseTest.java new file mode 100644 index 000000000..590fcdc80 --- /dev/null +++ b/chat2db-server/chat2db-server-test/src/test/java/ai/chat2db/server/test/temp/SQLParseTest.java @@ -0,0 +1,42 @@ +package ai.chat2db.server.test.temp; + +import com.github.vertical_blank.sqlformatter.SqlFormatter; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.Statements; + +public class SQLParseTest { + + public static void main(String[] args) throws JSQLParserException { + String sql = "CREATE OR REPLACE PROCEDURE public.raise_salary(emp_id integer, percentage numeric)\n" + + " LANGUAGE plpgsql\n" + + "AS $procedure$\n" + + "BEGIN\n" + + " UPDATE employees\n" + + " SET salary = salary + (salary * percentage / 100)\n" + + " WHERE id = emp_id;\n" + + "COMMIT;\n" + + "END; -- sdsd\n" + + "$procedure$"; + + Statements statements = CCJSqlParserUtil.parseStatements(sql); + + // 如果是多条语句,解析后的实际类型是StatementList + + + // 遍历每个语句 + for (Statement stmt : statements.getStatements()) { + // 如果是单条语句,实际类型是Statement + System.out.println(stmt.toString()); + + System.out.println(" dddd:"+SqlFormatter.format(stmt.toString())); + System.out.println(" hu:"+ cn.hutool.db.sql.SqlFormatter.format(stmt.toString())); + } + String s = SqlFormatter.format("SELECT * FROM table1"); + System.out.println(s); + + + + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java new file mode 100644 index 000000000..fec1cf2fe --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java @@ -0,0 +1,28 @@ +package ai.chat2db.server.web.api.controller.sql; + +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.sql.request.SqlFormatRequest; +import cn.hutool.db.sql.SqlFormatter; +import jakarta.validation.Valid; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@ConnectionInfoAspect +@RequestMapping("/api/sql") +@RestController +public class SqlController { + + @GetMapping("/format") + public DataResult list(@Valid SqlFormatRequest sqlFormatRequest) { + + String sql = sqlFormatRequest.getSql(); + try { + sql = SqlFormatter.format(sql); + } catch (Exception e) { + // ignore + } + return DataResult.of(sql); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFormatRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFormatRequest.java new file mode 100644 index 000000000..65d67b736 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/request/SqlFormatRequest.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.web.api.controller.sql.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SqlFormatRequest { + + private String sql; + + private String dbType; +} diff --git a/chat2db-server/chat2db-spi/pom.xml b/chat2db-server/chat2db-spi/pom.xml index 2824e27c1..baa916ff3 100644 --- a/chat2db-server/chat2db-spi/pom.xml +++ b/chat2db-server/chat2db-spi/pom.xml @@ -69,5 +69,10 @@ commons-beanutils commons-beanutils + + com.github.jsqlparser + jsqlparser + 4.6 + \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index cea57cafe..bf58ca346 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -40,6 +40,7 @@ import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex; +import com.alibaba.druid.sql.parser.SQLParserUtils; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.common.util.EasyBooleanUtils; @@ -52,6 +53,9 @@ import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.Statement; +import net.sf.jsqlparser.statement.Statements; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -461,4 +465,18 @@ private static SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSourc return null; } + public static List parse(String sql,DbType dbType) { + List list = new ArrayList<>(); + try { + Statements statements = CCJSqlParserUtil.parseStatements(sql); + // 遍历每个语句 + for (Statement stmt : statements.getStatements()) { + list.add(stmt.toString()); + } + } catch (Exception e) { + list = SQLParserUtils.splitAndRemoveComment(sql, dbType); + } + return list; + } + } \ No newline at end of file From ff784c8e12c668d0e178bbff3d34c134c5979d26 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 12:49:02 +0800 Subject: [PATCH 0604/1069] support view trigger producer function --- .../server/web/api/controller/sql/SqlController.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java index fec1cf2fe..17bcfa614 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/sql/SqlController.java @@ -9,11 +9,19 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +/** + * SQL Controller + */ @ConnectionInfoAspect @RequestMapping("/api/sql") @RestController public class SqlController { + /** + * SQL Format + * @param sqlFormatRequest + * @return + */ @GetMapping("/format") public DataResult list(@Valid SqlFormatRequest sqlFormatRequest) { From d347ec0222b895e3d48b1a6c2f6c9beec91077c8 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 14:01:19 +0800 Subject: [PATCH 0605/1069] support view trigger producer function --- .../core/impl/DlTemplateServiceImpl.java | 154 +++++++++--------- 1 file changed, 76 insertions(+), 78 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 03f8273b6..0ed82aa40 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -67,84 +67,7 @@ public ListResult execute(DlExecuteParam param) { ListResult listResult = ListResult.of(result); // 执行sql for (String originalSql : sqlList) { - String sql = originalSql; - int pageNo = 0; - int pageSize = 0; - String sqlType = SqlTypeEnum.UNKNOWN.getCode(); - - // 解析sql分页 - SQLStatement sqlStatement; - boolean autoLimit = false; - try { - sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); - // 是否需要代码帮忙分页 - - if (sqlStatement instanceof SQLSelectStatement) { - // 不是查询全部数据 而且 用户自己没有传分页 - autoLimit = BooleanUtils.isNotTrue(param.getPageSizeAll()) && SQLUtils.getLimit(sqlStatement, - dbType) - == null; - if (autoLimit) { - pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); - pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); - int offset = (pageNo - 1) * pageSize; - try { - sql = PagerUtils.limit(sql, dbType, offset, pageSize); - } catch (Exception e) { - autoLimit = false; - } - } - sqlType = SqlTypeEnum.SELECT.getCode(); - } - } catch (ParserException e) { - log.warn("解析sql失败:{}", sql, e); - //ExecuteResult executeResult = ExecuteResult.builder() - // .success(Boolean.FALSE) - // .originalSql(originalSql) - // .sql(sql) - // .message(e.getMessage()) - // .build(); - //result.add(executeResult); - //continue; - } - - ExecuteResult executeResult = execute(sql); - executeResult.setSqlType(sqlType); - executeResult.setOriginalSql(originalSql); - // 自动分页 - if (autoLimit) { - executeResult.setPageNo(pageNo); - executeResult.setPageSize(pageSize); - executeResult.setHasNextPage( - CollectionUtils.size(executeResult.getDataList()) >= executeResult.getPageSize()); - } else { - executeResult.setPageNo(1); - executeResult.setPageSize(CollectionUtils.size(executeResult.getDataList())); - executeResult.setHasNextPage(Boolean.FALSE); - } - // Splice row numbers - List
newHeaderList = new ArrayList<>(); - newHeaderList.add(Header.builder() - .name(I18nUtils.getMessage("sqlResult.rowNumber")) - .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER - .getCode()).build()); - if (executeResult.getHeaderList() != null) { - newHeaderList.addAll(executeResult.getHeaderList()); - } - executeResult.setHeaderList(newHeaderList); - if (executeResult.getDataList() != null) { - int rowNumberIncrement = 1 + Math.max(pageNo - 1, 0) * pageSize; - for (int i = 0; i < executeResult.getDataList().size(); i++) { - List row = executeResult.getDataList().get(i); - List newRow = Lists.newArrayListWithExpectedSize(row.size() + 1); - newRow.add(Integer.toString(i + rowNumberIncrement)); - newRow.addAll(row); - executeResult.getDataList().set(i, newRow); - } - } - // Total number of fuzzy rows - executeResult.setFuzzyTotal(calculateFuzzyTotal(pageNo, pageSize, executeResult)); - + ExecuteResult executeResult = executeSQL(originalSql,dbType,param); result.add(executeResult); if (!executeResult.getSuccess()) { listResult.setSuccess(false); @@ -155,6 +78,81 @@ public ListResult execute(DlExecuteParam param) { return listResult; } + private ExecuteResult executeSQL(String originalSql,DbType dbType,DlExecuteParam param) { + String sql = originalSql; + int pageNo = 0; + int pageSize = 0; + String sqlType = SqlTypeEnum.UNKNOWN.getCode(); + + // 解析sql分页 + SQLStatement sqlStatement; + boolean autoLimit = false; + try { + sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + // 是否需要代码帮忙分页 + if (sqlStatement instanceof SQLSelectStatement) { + // 不是查询全部数据 而且 用户自己没有传分页 + autoLimit = BooleanUtils.isNotTrue(param.getPageSizeAll()) && SQLUtils.getLimit(sqlStatement, + dbType) + == null; + if (autoLimit) { + pageNo = Optional.ofNullable(param.getPageNo()).orElse(1); + pageSize = Optional.ofNullable(param.getPageSize()).orElse(EasyToolsConstant.MAX_PAGE_SIZE); + int offset = (pageNo - 1) * pageSize; + try { + sql = PagerUtils.limit(sql, dbType, offset, pageSize); + } catch (Exception e) { + autoLimit = false; + } + } + sqlType = SqlTypeEnum.SELECT.getCode(); + } + } catch (ParserException e) { + log.warn("解析sql失败:{}", sql, e); + } + + ExecuteResult executeResult = execute(sql); + executeResult.setSqlType(sqlType); + executeResult.setOriginalSql(originalSql); + // 自动分页 + if (autoLimit) { + executeResult.setPageNo(pageNo); + executeResult.setPageSize(pageSize); + executeResult.setHasNextPage( + CollectionUtils.size(executeResult.getDataList()) >= executeResult.getPageSize()); + } else { + executeResult.setPageNo(1); + executeResult.setPageSize(CollectionUtils.size(executeResult.getDataList())); + executeResult.setHasNextPage(Boolean.FALSE); + } + // Splice row numbers + List
newHeaderList = new ArrayList<>(); + newHeaderList.add(Header.builder() + .name(I18nUtils.getMessage("sqlResult.rowNumber")) + .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER + .getCode()).build()); + if (executeResult.getHeaderList() != null) { + newHeaderList.addAll(executeResult.getHeaderList()); + } + executeResult.setHeaderList(newHeaderList); + if (executeResult.getDataList() != null) { + int rowNumberIncrement = 1 + Math.max(pageNo - 1, 0) * pageSize; + for (int i = 0; i < executeResult.getDataList().size(); i++) { + List row = executeResult.getDataList().get(i); + List newRow = Lists.newArrayListWithExpectedSize(row.size() + 1); + newRow.add(Integer.toString(i + rowNumberIncrement)); + newRow.addAll(row); + executeResult.getDataList().set(i, newRow); + } + } + // Total number of fuzzy rows + executeResult.setFuzzyTotal(calculateFuzzyTotal(pageNo, pageSize, executeResult)); + return executeResult; + } + + + + private String calculateFuzzyTotal(int pageNo, int pageSize, ExecuteResult executeResult) { int dataSize = CollectionUtils.size(executeResult.getDataList()); if (pageSize <= 0) { From 27c26d8503d4fc0fd14132ab47aa4e52d8c75eee Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 13 Aug 2023 14:01:20 +0800 Subject: [PATCH 0606/1069] fix: Remove the quotation marks from the table name --- chat2db-client/src/components/Console/MonacoEditor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index a412cd070..9f923e00c 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -206,7 +206,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { return Object.keys(hintData).map((key) => ({ label: key, kind: monaco.languages.CompletionItemKind.Method, - insertText: `\`${key}\``, + insertText: `${key}`, detail: '', })); }; From fd1e24b63e558e35ea0d8de23506c7fb02ef3de4 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 14:01:52 +0800 Subject: [PATCH 0607/1069] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E4=B8=BB=E9=A2=98=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/blocks/Setting/BaseSetting/index.tsx | 16 +++-- .../components/Console/MonacoEditor/index.tsx | 23 ++++--- chat2db-client/src/constants/theme.ts | 13 ++-- chat2db-client/src/i18n/en-us/setting.ts | 1 + chat2db-client/src/i18n/zh-cn/setting.ts | 1 + .../src/theme/{ => background}/dark.ts | 4 +- .../src/theme/background/darkDimmed.ts | 61 +++++++++++++++++++ .../src/theme/{ => background}/light.ts | 4 +- chat2db-client/src/theme/custom/dark.less | 2 +- .../src/theme/custom/darkDimmed.less | 3 + chat2db-client/src/theme/index.ts | 6 +- 11 files changed, 108 insertions(+), 26 deletions(-) rename chat2db-client/src/theme/{ => background}/dark.ts (93%) create mode 100644 chat2db-client/src/theme/background/darkDimmed.ts rename chat2db-client/src/theme/{ => background}/light.ts (93%) create mode 100644 chat2db-client/src/theme/custom/darkDimmed.less diff --git a/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx b/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx index 4b0b04ba8..aa4ff38e6 100644 --- a/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/BaseSetting/index.tsx @@ -25,6 +25,11 @@ const themeList = [ name: i18n('setting.text.dark'), img: themeDarkImg, }, + { + code: ThemeType.DarkDimmed, + name: i18n('setting.text.dark2'), + img: themeDarkImg + }, { code: ThemeType.FollowOs, name: i18n('setting.text.followOS'), @@ -83,9 +88,9 @@ const colorList = [ // baseBody 基础设置 export default function BaseSetting() { const [lang, setLang] = useState(currentLang); - const [currentTheme, setCurrentTheme] = useState(localStorage.getItem('theme')); - const [currentPrimaryColor, setCurrentPrimaryColor] = useState(localStorage.getItem('primary-color')); const [appTheme, setAppTheme] = useTheme(); + const [currentTheme, setCurrentTheme] = useState(appTheme.backgroundColor); + const [currentPrimaryColor, setCurrentPrimaryColor] = useState(localStorage.getItem('primary-color')); const changePrimaryColor = (item: any) => { const html = document.documentElement; @@ -103,12 +108,13 @@ export default function BaseSetting() { location.reload(); } - function handleChangeTheme(theme: ThemeType) { + // TODO: 这里写 ThemeType 为什么报错呢 + function handleChangeTheme(backgroundColor: any) { setAppTheme({ ...appTheme, - backgroundColor: theme, + backgroundColor, }); - setCurrentTheme(theme); + setCurrentTheme(backgroundColor); } return ( diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index a412cd070..4b4cc80c0 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -67,12 +67,12 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { ...options, value: defaultValue || '', language: language, - theme: appTheme.backgroundColor === ThemeType.Light ? EditorThemeType.Default : EditorThemeType.BlackTheme, + theme: appTheme.backgroundColor, }); editorRef.current = editorIns; didMount && didMount(editorIns); - monaco.editor.defineTheme(EditorThemeType.Default, { + monaco.editor.defineTheme(ThemeType.Light, { base: 'vs', inherit: true, rules: [{ background: '#15161a' }] as any, @@ -82,7 +82,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }, }); - monaco.editor.defineTheme(EditorThemeType.BlackTheme, { + monaco.editor.defineTheme(ThemeType.Dark, { base: 'vs-dark', inherit: true, rules: [{ background: '#15161a' }] as any, @@ -92,6 +92,16 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { 'editor.background': '#0A0B0C', //背景色 }, }); + monaco.editor.defineTheme(ThemeType.DarkDimmed, { + base: 'vs-dark', + inherit: true, + rules: [{ background: '#15161a' }] as any, + colors: { + // 相关颜色属性配置 + 'editor.foreground': '#ffffff', + 'editor.background': '#1c2128', //背景色 + }, + }); monaco.editor.defineTheme(EditorThemeType.DashboardLightTheme, { base: 'vs', @@ -136,12 +146,11 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // 监听主题色变化切换编辑器主题色 useEffect(() => { - const isDark = appTheme.backgroundColor === ThemeType.Dark; if (options?.theme) { monaco.editor.setTheme(options.theme); - } else { - monaco.editor.setTheme(isDark ? 'BlackTheme' : 'Default'); - } + return + } + monaco.editor.setTheme(appTheme.backgroundColor); }, [appTheme.backgroundColor, options?.theme]); // useEffect(() => { diff --git a/chat2db-client/src/constants/theme.ts b/chat2db-client/src/constants/theme.ts index b553afec2..637df2ba7 100644 --- a/chat2db-client/src/constants/theme.ts +++ b/chat2db-client/src/constants/theme.ts @@ -1,9 +1,15 @@ export enum ThemeType { Light = 'light', Dark = 'dark', + DarkDimmed = 'darkDimmed', FollowOs = 'followOs', } +export enum EditorThemeType { + DashboardLightTheme = 'DashboardLightTheme', + DashboardBlackTheme = 'DashboardBlackTheme', +} + export enum PrimaryColorType { Polar_Green = 'polar-green', Golden_Purple = 'golden-purple', @@ -19,10 +25,3 @@ export enum LangType { EN_US = 'en-us', ZH_CN = 'zh-cn', } - -export enum EditorThemeType { - Default = 'Default', - BlackTheme = 'BlackTheme', - DashboardLightTheme = 'DashboardLightTheme', - DashboardBlackTheme = 'DashboardBlackTheme', -} diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 96ae965d1..aeced01d4 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -10,6 +10,7 @@ export default { 'setting.label.green': 'Green', 'setting.label.violet': 'Violet', 'setting.text.dark': 'Dark', + 'setting.text.dark2': 'Dark-2', 'setting.text.light': 'Light', 'setting.text.followOS': 'FollowOS', 'setting.title.language': 'Language', diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index fece84929..22c879163 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -10,6 +10,7 @@ export default { 'setting.label.green': '极光绿', 'setting.label.violet': '酱紫', 'setting.text.dark': '暗色', + 'setting.text.dark2': '暗色2', 'setting.text.light': '亮色', 'setting.text.followOS': '自动', 'setting.title.language': '语言', diff --git a/chat2db-client/src/theme/dark.ts b/chat2db-client/src/theme/background/dark.ts similarity index 93% rename from chat2db-client/src/theme/dark.ts rename to chat2db-client/src/theme/background/dark.ts index f0083f60c..707e1b176 100644 --- a/chat2db-client/src/theme/dark.ts +++ b/chat2db-client/src/theme/background/dark.ts @@ -1,6 +1,6 @@ import { theme } from 'antd'; import { PrimaryColorType } from '@/constants'; -import { commonToken } from './common'; +import { commonToken } from '../common'; type IAntdPrimaryColor = { [key in PrimaryColorType]: any; @@ -40,7 +40,7 @@ const antDarkTheme = { antdPrimaryColor, token: { ...commonToken, - colorText: "rgb(241, 241, 244)", + colorTextBase: 'rgb(241, 241, 244)', colorBgBase: '#0a0b0c', colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#0a0b0c', diff --git a/chat2db-client/src/theme/background/darkDimmed.ts b/chat2db-client/src/theme/background/darkDimmed.ts new file mode 100644 index 000000000..574e4929c --- /dev/null +++ b/chat2db-client/src/theme/background/darkDimmed.ts @@ -0,0 +1,61 @@ +import { theme } from 'antd'; +import { PrimaryColorType } from '@/constants'; +import { commonToken } from '../common'; + +type IAntdPrimaryColor = { + [key in PrimaryColorType]: any; +}; + +// 主题色 +const antdPrimaryColor: IAntdPrimaryColor = { + [PrimaryColorType.Polar_Green]: { + colorPrimary: '#3c8618', + }, + [PrimaryColorType.Golden_Purple]: { + colorPrimary: '#7688c9', + }, + [PrimaryColorType.Polar_Blue]: { + colorPrimary: '#1677ff', + }, + [PrimaryColorType.Silver]: { + colorPrimary: '#c3b7a4', + }, + [PrimaryColorType.Red]: { + colorPrimary: '#fd6874', + }, + [PrimaryColorType.Orange]: { + colorPrimary: '#ffa940', + }, + [PrimaryColorType.Blue2]: { + colorPrimary: '#009cc7', + }, + [PrimaryColorType.Gold]: { + colorPrimary: '#b59a6d', + }, +}; + +const antdLightTheme = { + algorithm: [theme.defaultAlgorithm, theme.compactAlgorithm], + customName: 'dark-dimmed', + antdPrimaryColor, + token: { + ...commonToken, + colorTextBase: 'rgb(241, 241, 244)', + colorBgBase: 'rgb(28, 33, 40)', + colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', + colorBgContainer: 'rgb(28, 33, 40)', + colorBgElevated: 'rgb(34, 39, 46)', + colorBorder: 'rgb(68, 76, 86)', + colorBorderSecondary: 'rgba(55, 62, 71, 0.4)', + // ...commonToken, + // colorText: "rgb(241, 241, 244)", + // colorBgBase: '#191a23', + // colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', + // colorBgContainer: '#191a23', + // colorBgElevated: '#202123', + // colorBorder: 'rgba(231, 235, 254, 0.075)', + // colorBorderSecondary: 'rgba(231, 235, 254, 0.075)', + }, +}; + +export default antdLightTheme; diff --git a/chat2db-client/src/theme/light.ts b/chat2db-client/src/theme/background/light.ts similarity index 93% rename from chat2db-client/src/theme/light.ts rename to chat2db-client/src/theme/background/light.ts index 0d112f068..97d9e3138 100644 --- a/chat2db-client/src/theme/light.ts +++ b/chat2db-client/src/theme/background/light.ts @@ -1,6 +1,6 @@ import { theme } from 'antd'; import { PrimaryColorType } from '@/constants'; -import { commonToken } from './common'; +import { commonToken } from '../common'; type IAntdPrimaryColor = { [key in PrimaryColorType]: any; @@ -44,7 +44,7 @@ const antdLightTheme = { colorBgBase: '#fff', colorHoverBg: '#eee', colorBgContainer: '#fff', - colorBgElevated: '#F8F9FA', + colorBgElevated: '#f6f8fa', colorBorder: '#d3d3d4', }, }; diff --git a/chat2db-client/src/theme/custom/dark.less b/chat2db-client/src/theme/custom/dark.less index b5dac155c..f25d625f5 100644 --- a/chat2db-client/src/theme/custom/dark.less +++ b/chat2db-client/src/theme/custom/dark.less @@ -1,3 +1,3 @@ -:root { +:root [theme='dark'] { --custom-color-icon: #5C5D5E; } diff --git a/chat2db-client/src/theme/custom/darkDimmed.less b/chat2db-client/src/theme/custom/darkDimmed.less new file mode 100644 index 000000000..8c890a907 --- /dev/null +++ b/chat2db-client/src/theme/custom/darkDimmed.less @@ -0,0 +1,3 @@ +:root [theme='darkDimmed'] { + --custom-color-icon: #5C5D5E; +} diff --git a/chat2db-client/src/theme/index.ts b/chat2db-client/src/theme/index.ts index affaf40ce..dd6f288a5 100644 --- a/chat2db-client/src/theme/index.ts +++ b/chat2db-client/src/theme/index.ts @@ -1,5 +1,6 @@ -import antdDarkTheme from './dark'; -import antdLightTheme from './light'; +import antdDarkTheme from './background/dark'; +import antdDarkDimmedTheme from './background/darkDimmed'; +import antdLightTheme from './background/light'; import { ThemeType, PrimaryColorType } from '@/constants'; import { ITheme } from '@/typings/theme'; import lodash from 'lodash'; @@ -7,6 +8,7 @@ import lodash from 'lodash'; const antdThemeConfigs = { [ThemeType.Dark]: antdDarkTheme, [ThemeType.Light]: antdLightTheme, + [ThemeType.DarkDimmed]: antdDarkDimmedTheme, }; export function getAntdThemeConfig(theme: ITheme) { From ac38fb83561ae4cc7d24ac8b3c98b1a9c23499fd Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 14:02:06 +0800 Subject: [PATCH 0608/1069] =?UTF-8?q?feat:=E6=96=B0=E7=9A=84=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 2b9345447..5338caf1b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,15 +1,5 @@ { "cSpell.words": [ - "AZUREAI", - "DBAI", - "DBHUB", - "Dmaven", - "Dserver", - "Dspring", - "Mddhhmmss", - "OPENAI", - "RESTAI", - "Sercurity", "aarch", "ahooks", "alicdn", @@ -17,12 +7,18 @@ "altool", "antd", "asar", + "AZUREAI", "bgcolor", "blockmap", "cascader", "chmod", "datasource", + "DBAI", + "DBHUB", "dingtalk", + "Dmaven", + "Dserver", + "Dspring", "echart", "echarts", "favicons", @@ -30,20 +26,27 @@ "iconfont", "jdbc", "macos", + "Mddhhmmss", "mkdir", "monaco", "msgtype", "nsis", + "OPENAI", "ossutil", "pgsql", "pnpm", "remaininguses", + "RESTAI", "samuelmeuli", + "Sercurity", "sortablejs", "temurin", + "Tigger", "togglefullscreen", "umijs", "umirc", + "VIEWCOLUMN", + "VIEWCOLUMNS", "wechat", "wireframe", "xcrun", From 23f5c17461dce3ea35f3de8a698d811fe3f838e1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 14:21:05 +0800 Subject: [PATCH 0609/1069] =?UTF-8?q?style=EF=BC=9A=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=B8=BB=E9=A2=98=E9=A2=9C=E8=89=B2=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 1 + chat2db-client/src/theme/background/darkDimmed.ts | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 5338caf1b..55d8d3970 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -45,6 +45,7 @@ "togglefullscreen", "umijs", "umirc", + "uuidv", "VIEWCOLUMN", "VIEWCOLUMNS", "wechat", diff --git a/chat2db-client/src/theme/background/darkDimmed.ts b/chat2db-client/src/theme/background/darkDimmed.ts index 574e4929c..e56f1e9d0 100644 --- a/chat2db-client/src/theme/background/darkDimmed.ts +++ b/chat2db-client/src/theme/background/darkDimmed.ts @@ -35,7 +35,7 @@ const antdPrimaryColor: IAntdPrimaryColor = { }; const antdLightTheme = { - algorithm: [theme.defaultAlgorithm, theme.compactAlgorithm], + algorithm: [theme.darkAlgorithm, theme.compactAlgorithm], customName: 'dark-dimmed', antdPrimaryColor, token: { @@ -47,6 +47,7 @@ const antdLightTheme = { colorBgElevated: 'rgb(34, 39, 46)', colorBorder: 'rgb(68, 76, 86)', colorBorderSecondary: 'rgba(55, 62, 71, 0.4)', + controlItemBgActive: 'rgba(241, 241, 244, 0.08);', // ...commonToken, // colorText: "rgb(241, 241, 244)", // colorBgBase: '#191a23', From 1ba2d63679bd2d5eb281276ca651d2b390c7166e Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 14:57:24 +0800 Subject: [PATCH 0610/1069] Complete team user code --- .../domain/api/model/DataSourceAccess.java | 6 + ...urceAccessComprehensivePageQueryParam.java | 9 +- .../access/DataSourceAccessCreatParam.java | 39 ++++++ .../access/DataSourceAccessSelector.java | 6 + .../api/param/team/TeamCreateParam.java | 37 +++++ .../api/param/team/TeamPageQueryParam.java | 19 +++ .../domain/api/param/team/TeamSelector.java | 23 +++ .../api/param/team/TeamUpdateParam.java | 35 +++++ .../TeamUserComprehensivePageQueryParam.java | 10 ++ .../param/team/user/TeamUserCreatParam.java | 29 ++++ .../api/param/user/UserCreateParam.java | 45 ++++++ .../UserPageQueryParam.java} | 16 +-- .../domain/api/param/user/UserSelector.java | 23 +++ .../api/param/user/UserUpdateParam.java | 51 +++++++ .../api/service/DataSourceAccessService.java | 7 +- .../domain/api/service/TeamService.java | 41 ++++++ .../domain/api/service/TeamUserService.java | 19 +++ .../domain/api/service/UserService.java | 14 +- .../converter/DataSourceAccessConverter.java | 14 ++ .../domain/core/converter/TeamConverter.java | 18 +++ .../core/converter/TeamUserConverter.java | 15 ++ .../domain/core/converter/UserConverter.java | 16 +++ .../impl/DataSourceAccessServiceImpl.java | 38 ++--- .../domain/core/impl/TeamServiceImpl.java | 54 +++++++ .../domain/core/impl/TeamUserServiceImpl.java | 27 +++- .../domain/core/impl/UserServiceImpl.java | 64 ++++++--- .../mapper/DataSourceAccessCustomMapper.java | 5 +- .../mapper/TeamUserCustomMapper.java | 3 +- .../mapper/DataSourceAccessCustomMapper.xml | 14 +- .../resources/mapper/TeamUserCustomMapper.xml | 13 +- .../main/resources/i18n/messages.properties | 4 +- .../resources/i18n/messages_en_US.properties | 4 +- .../resources/i18n/messages_zh_CN.properties | 4 +- .../tools/base/wrapper/result/PageResult.java | 19 +++ .../common/CommonAdminController.java | 46 ++++-- .../converter/CommonAdminConverter.java | 87 ++++++++++++ .../DataSourceAccessAdminController.java | 13 +- .../DataSourceAccessAdminConverter.java | 5 + .../controller/team/TeamAdminController.java | 19 ++- .../team/TeamDataSourceAdminController.java | 34 ++++- .../team/TeamUserAdminController.java | 31 +++- .../team/converter/TeamAdminConverter.java | 57 ++++++++ .../TeamDataSourcesAdminConverter.java | 49 +++++++ .../converter/TeamUserAdminConverter.java | 48 +++++++ .../TeamDataSourceBatchCreateRequest.java | 1 + .../team/request/TeamUpdateRequest.java | 6 - .../request/TeamUserBatchCreateRequest.java | 1 + .../team/vo/TeamDataSourcePageQueryVO.java | 7 + .../controller/user/UserAdminController.java | 22 +-- .../user/UserDataSourceAdminController.java | 36 +++-- .../user/UserTeamAdminController.java | 30 +++- .../user/converter/UserAdminConverter.java | 87 ++++++------ .../UserDataSourcesAdminConverter.java | 49 +++++++ .../converter/UserTeamAdminConverter.java | 37 +++++ .../request/CommonQueryRequest.java | 18 +++ .../api/controller/user/UserController.java | 132 ++++++++---------- 56 files changed, 1289 insertions(+), 267 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessCreatParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamCreateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamUpdateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserCreatParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserCreateParam.java rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{UserQueryParam.java => user/UserPageQueryParam.java} (53%) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserUpdateParam.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java index e5c68cd34..91c094be5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/DataSourceAccess.java @@ -61,6 +61,12 @@ public class DataSourceAccess implements Serializable { @NotNull private Long dataSourceId; + /** + * 数据源 + */ + @NotNull + private DataSource dataSource; + /** * 授权类型 * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java index cbc9c92b1..78c5cd4f9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java @@ -18,7 +18,12 @@ public class DataSourceAccessComprehensivePageQueryParam extends PageQueryParam private Long dataSourceId; /** - * searchKey + * Query keywords for users or teams */ - private String searchKey; + private String userOrTeamSearchKey; + + /** + * Query keywords for data source + */ + private String dataSourceSearchKey; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessCreatParam.java new file mode 100644 index 000000000..133989782 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessCreatParam.java @@ -0,0 +1,39 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessCreatParam { + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java index 639ed62ab..de59b6def 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java @@ -20,4 +20,10 @@ public class DataSourceAccessSelector { * 授权对象 */ private Boolean accessObject; + + + /** + * 数据源 + */ + private Boolean dataSource; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamCreateParam.java new file mode 100644 index 000000000..346e598ea --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamCreateParam.java @@ -0,0 +1,37 @@ +package ai.chat2db.server.domain.api.param.team; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class TeamCreateParam { + /** + * 团队编码 + */ + @NotNull + private String code; + + /** + * 团队名称 + */ + @NotNull + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + @NotNull + private String status; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java new file mode 100644 index 000000000..6cef62c44 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.domain.api.param.team; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.Data; + +/** + * page query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamPageQueryParam extends PageQueryParam { + + /** + * searchKey + */ + private String searchKey; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java new file mode 100644 index 000000000..a8252fe3f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.team; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * select + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamSelector { + /** + * empty + */ + private Boolean empty; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamUpdateParam.java new file mode 100644 index 000000000..eac5dddde --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamUpdateParam.java @@ -0,0 +1,35 @@ +package ai.chat2db.server.domain.api.param.team; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * update + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUpdateParam { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 团队名称 + */ + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java index 214c61561..26f47e4e4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java @@ -27,4 +27,14 @@ public class TeamUserComprehensivePageQueryParam extends PageQueryParam { * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum */ private String teamRoleCode; + + /** + * Query keywords for team + */ + private String teamSearchKey; + + /** + * Query keywords for user + */ + private String userSearchKey; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserCreatParam.java new file mode 100644 index 000000000..619fae389 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserCreatParam.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.domain.api.param.team.user; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Team User + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserCreatParam { + /** + * team id + */ + @NotNull + private Long teamId; + + /** + * user id + */ + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserCreateParam.java new file mode 100644 index 000000000..4556de813 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserCreateParam.java @@ -0,0 +1,45 @@ +package ai.chat2db.server.domain.api.param.user; + +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class UserCreateParam { + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UserQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java similarity index 53% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UserQueryParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java index ae471f57f..bfe3ffb28 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UserQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java @@ -1,16 +1,13 @@ -package ai.chat2db.server.domain.api.param; - -import java.io.Serial; +package ai.chat2db.server.domain.api.param.user; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; - import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; /** - * 用户查询参数 + * * page query * * @author Jiaju Zhuang */ @@ -18,11 +15,10 @@ @SuperBuilder @NoArgsConstructor @AllArgsConstructor -public class UserQueryParam extends PageQueryParam { - @Serial - private static final long serialVersionUID = 7341467383637825621L; +public class UserPageQueryParam extends PageQueryParam { + /** - * 用户名 + * searchKey */ - private String keyWord; + private String searchKey; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java new file mode 100644 index 000000000..babf2efb4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * select + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UserSelector { + /** + * empty + */ + private Boolean empty; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserUpdateParam.java new file mode 100644 index 000000000..bac4bdf7d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserUpdateParam.java @@ -0,0 +1,51 @@ +package ai.chat2db.server.domain.api.param.user; + +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class UserUpdateParam { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java index 5791845ee..5c974dec2 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java @@ -1,10 +1,11 @@ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.model.DataSourceAccess; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import jakarta.validation.constraints.NotNull; @@ -25,14 +26,14 @@ public interface DataSourceAccessService { PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector); + /** * Batch Create * * @param param * @return */ - ActionResult batchCreate(DataSourceAccessBatchCreatParam param); - + DataResult create(DataSourceAccessCreatParam param); /** * delete * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java index 57c82a9ff..cfd558533 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamService.java @@ -3,7 +3,15 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.param.team.TeamCreateParam; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.team.TeamSelector; +import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import jakarta.validation.constraints.NotNull; /** * team @@ -12,6 +20,15 @@ */ public interface TeamService { + /** + * Pagination query + * + * @param param + * @param selector + * @return + */ + PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector); + /** * List Query Data * @@ -20,4 +37,28 @@ public interface TeamService { */ ListResult listQuery(List idList); + /** + * Create + * + * @param param + * @return + */ + DataResult create(TeamCreateParam param); + + /** + * update + * + * @param param + * @return + */ + DataResult update(TeamUpdateParam param); + + /** + * delete + * + * @param id + * @return + */ + ActionResult delete(@NotNull Long id); + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java index 95636620b..bc6d24a2d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java @@ -2,8 +2,12 @@ import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import jakarta.validation.constraints.NotNull; /** * team user @@ -21,4 +25,19 @@ public interface TeamUserService { */ PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector); + /** + * Create + * + * @param param + * @return + */ + DataResult create(TeamUserCreatParam param); + + /** + * delete + * + * @param id + * @return + */ + ActionResult delete(@NotNull Long id); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java index f54323d73..1c98ca4f2 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/UserService.java @@ -3,7 +3,11 @@ import java.util.List; import ai.chat2db.server.domain.api.model.User; -import ai.chat2db.server.domain.api.param.UserQueryParam; +import ai.chat2db.server.domain.api.param.user.UserCreateParam; +import ai.chat2db.server.domain.api.param.user.UserSelector; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserUpdateParam; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; @@ -44,26 +48,26 @@ public interface UserService { * @param param * @return */ - PageResult queryPage(UserQueryParam param); + PageResult pageQuery(UserPageQueryParam param, UserSelector selector); /** * 更新用户信息 * @param user * @return */ - DataResult update(User user); + DataResult update(UserUpdateParam user); /** * 删除用户 * @param id * @return */ - DataResult delete(Long id); + ActionResult delete(Long id); /** * 创建一个用户 * @param user * @return */ - DataResult create(User user); + DataResult create(UserCreateParam user); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java index 1a330ecd7..65bae69f2 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java @@ -3,6 +3,7 @@ import java.util.List; import ai.chat2db.server.domain.api.model.DataSourceAccess; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import lombok.extern.slf4j.Slf4j; import org.mapstruct.Mapper; @@ -46,6 +47,19 @@ public abstract class DataSourceAccessConverter { public abstract DataSourceAccessDO param2do(Long dataSourceId, Long accessObjectId, String accessObjectType, Long userId); + /** + * convert + * + * @param param + + * @return + */ + @Mappings({ + @Mapping(target = "createUserId", source = "userId"), + @Mapping(target = "modifiedUserId", source = "userId"), + }) + public abstract DataSourceAccessDO param2do(DataSourceAccessCreatParam param, Long userId); + /** * convert * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java index a8804da17..4fe82c878 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java @@ -4,6 +4,8 @@ import java.util.Map; import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.param.team.TeamCreateParam; +import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.repository.entity.TeamDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; @@ -37,6 +39,22 @@ public abstract class TeamConverter { */ public abstract List do2dto(List list); + /** + * convert + * + * @param param + * @return + */ + public abstract TeamDO param2do(TeamCreateParam param); + + /** + * convert + * + * @param param + * @return + */ + public abstract TeamDO param2do(TeamUpdateParam param); + /** * Fill in detailed information * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java index 6bc5223ff..b4666d454 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java @@ -5,6 +5,7 @@ import ai.chat2db.server.domain.api.model.Environment; import ai.chat2db.server.domain.api.model.TeamUser; +import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.service.EnvironmentService; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; @@ -38,6 +39,20 @@ public abstract class TeamUserConverter { */ public abstract List do2dto(List list); + /** + * convert + * + * @param param + + * @return + */ + @Mappings({ + @Mapping(target = "createUserId", source = "userId"), + @Mapping(target = "modifiedUserId", source = "userId"), + }) + public abstract TeamUserDO param2do(TeamUserCreatParam param, Long userId); + + /** * Fill in detailed information * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java index 945f9cd37..8cc7ac0a7 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java @@ -4,6 +4,8 @@ import java.util.Map; import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.param.user.UserCreateParam; +import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.repository.entity.DbhubUserDO; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; @@ -50,6 +52,20 @@ public abstract class UserConverter { */ public abstract DbhubUserDO dto2do(User user); + /** + * + * @param user + * @return + */ + public abstract DbhubUserDO param2do(UserCreateParam user); + + /** + * + * @param user + * @return + */ + public abstract DbhubUserDO param2do(UserUpdateParam user); + /** * Fill in detailed information * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java index 81a81463c..e947595ff 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java @@ -3,16 +3,13 @@ import java.util.List; import java.util.Map; -import com.alibaba.fastjson2.JSON; - import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.model.DataSourceAccessObject; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.model.User; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessObjectParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.domain.api.service.TeamService; @@ -22,10 +19,10 @@ import ai.chat2db.server.domain.repository.mapper.DataSourceAccessCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; @@ -58,8 +55,11 @@ public class DataSourceAccessServiceImpl implements DataSourceAccessService { @Override public PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector) { - IPage iPage = dataSourceAccessCustomMapper.comprehensivePageQuery( - new Page<>(param.getPageNo(), param.getPageSize()), param.getDataSourceId(), param.getSearchKey()); + Page page = new Page<>(param.getPageNo(), param.getPageSize()); + page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = dataSourceAccessCustomMapper.comprehensivePageQuery(page, + param.getDataSourceId(), param.getUserOrTeamSearchKey(), + param.getDataSourceSearchKey()); List list = dataSourceAccessConverter.do2dto(iPage.getRecords()); @@ -69,25 +69,11 @@ public PageResult comprehensivePageQuery(DataSourceAccessCompr } @Override - public ActionResult batchCreate(DataSourceAccessBatchCreatParam param) { - if (CollectionUtils.isEmpty(param.getAccessObjectList())) { - return ActionResult.isSuccess(); - } - for (DataSourceAccessObjectParam dataSourceAccessObjectParam : param.getAccessObjectList()) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(DataSourceAccessDO::getAccessObjectType, dataSourceAccessObjectParam.getType()); - queryWrapper.eq(DataSourceAccessDO::getDataSourceId, dataSourceAccessObjectParam.getId()); - DataSourceAccessDO query = dataSourceAccessMapper.selectOne(queryWrapper); - if (query != null) { - log.info("The data source already exists, no need to add it again,{}", - JSON.toJSONString(dataSourceAccessObjectParam)); - continue; - } - dataSourceAccessMapper.insert( - dataSourceAccessConverter.param2do(param.getDataSourceId(), dataSourceAccessObjectParam.getId(), - dataSourceAccessObjectParam.getType(), ContextUtils.getUserId())); - } - return ActionResult.isSuccess(); + public DataResult create(DataSourceAccessCreatParam param) { + DataSourceAccessDO data = dataSourceAccessConverter.param2do(param, ContextUtils.getUserId()); + + dataSourceAccessMapper.insert(data); + return DataResult.of(data.getId()); } @Override diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java index d5b8a2fc7..355bee32c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java @@ -3,15 +3,25 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.param.team.TeamCreateParam; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.team.TeamSelector; +import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.core.converter.TeamConverter; import ai.chat2db.server.domain.repository.entity.TeamDO; import ai.chat2db.server.domain.repository.mapper.TeamMapper; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.base.wrapper.result.PageResult; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** @@ -39,4 +49,48 @@ public ListResult listQuery(List idList) { List list = teamConverter.do2dto(dataList); return ListResult.of(list); } + + @Override + public PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.isNotBlank(param.getSearchKey())) { + queryWrapper.and(wrapper -> wrapper.like(TeamDO::getCode, "%" + param.getSearchKey() + "%") + .or() + .like(TeamDO::getName, "%" + param.getSearchKey() + "%")); + } + Page page = new Page<>(param.getPageNo(), param.getPageSize()); + page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = teamMapper.selectPage(page, queryWrapper); + List list = teamConverter.do2dto(iPage.getRecords()); + + fillData(list, selector); + + return PageResult.of(list, iPage.getTotal(), param); + } + + @Override + public DataResult create(TeamCreateParam param) { + TeamDO data = teamConverter.param2do(param); + teamMapper.insert(data); + return DataResult.of(data.getId()); + } + + @Override + public DataResult update(TeamUpdateParam param) { + TeamDO data = teamConverter.param2do(param); + teamMapper.updateById(data); + return DataResult.of(data.getId()); + } + + @Override + public ActionResult delete(Long id) { + teamMapper.deleteById(id); + return ActionResult.isSuccess(); + } + + private void fillData(List list, TeamSelector selector) { + if (CollectionUtils.isEmpty(list) || selector == null) { + return; + } + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java index ee3227781..a8ae4c2b1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java @@ -4,6 +4,7 @@ import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.domain.core.converter.TeamConverter; @@ -11,7 +12,11 @@ import ai.chat2db.server.domain.core.converter.UserConverter; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.domain.repository.mapper.TeamUserCustomMapper; +import ai.chat2db.server.domain.repository.mapper.TeamUserMapper; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -35,6 +40,8 @@ public class TeamUserServiceImpl implements TeamUserService { @Resource private TeamUserCustomMapper teamUserCustomMapper; @Resource + private TeamUserMapper teamUserMapper; + @Resource private UserConverter userConverter; @Resource private TeamConverter teamConverter; @@ -42,8 +49,10 @@ public class TeamUserServiceImpl implements TeamUserService { @Override public PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector) { - IPage iPage = teamUserCustomMapper.comprehensivePageQuery( - new Page<>(param.getPageNo(), param.getPageSize()), param.getTeamId(), param.getUserId(), + Page page = new Page<>(param.getPageNo(), param.getPageSize()); + page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = teamUserCustomMapper.comprehensivePageQuery(page, param.getTeamId(), + param.getUserId(), param.getTeamRoleCode()); List list = teamUserConverter.do2dto(iPage.getRecords()); @@ -53,6 +62,20 @@ public PageResult comprehensivePageQuery(TeamUserComprehensivePageQuer return PageResult.of(list, iPage.getTotal(), param); } + @Override + public DataResult create(TeamUserCreatParam param) { + TeamUserDO data = teamUserConverter.param2do(param, ContextUtils.getUserId()); + + teamUserMapper.insert(data); + return DataResult.of(data.getId()); + } + + @Override + public ActionResult delete(Long id) { + teamUserMapper.deleteById(id); + return ActionResult.isSuccess(); + } + private void fillData(List list, TeamUserSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java index 1d28cf327..8659c73fd 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java @@ -3,12 +3,18 @@ import java.util.List; import java.util.Objects; +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.User; -import ai.chat2db.server.domain.api.param.UserQueryParam; +import ai.chat2db.server.domain.api.param.user.UserCreateParam; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserSelector; +import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.converter.UserConverter; import ai.chat2db.server.domain.repository.entity.DbhubUserDO; import ai.chat2db.server.domain.repository.mapper.DbhubUserMapper; +import ai.chat2db.server.tools.base.excption.BusinessException; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; @@ -18,6 +24,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** @@ -61,37 +68,56 @@ public ListResult listQuery(List idList) { } @Override - public PageResult queryPage(UserQueryParam param) { - LambdaQueryWrapper query = new LambdaQueryWrapper<>(); - if (Objects.nonNull(param.getKeyWord())) { - query.like(DbhubUserDO::getUserName, param.getKeyWord()); + public PageResult pageQuery(UserPageQueryParam param, UserSelector selector) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + if (StringUtils.isNotBlank(param.getSearchKey())) { + queryWrapper.and(wrapper -> wrapper.like(DbhubUserDO::getUserName, "%" + param.getSearchKey() + "%") + .or() + .like(DbhubUserDO::getNickName, "%" + param.getSearchKey() + "%") + .or() + .like(DbhubUserDO::getEmail, "%" + param.getSearchKey() + "%")); } + // Default not to query desktop accounts + queryWrapper.ne(DbhubUserDO::getId, RoleCodeEnum.DESKTOP.getDefaultUserId()); Page page = new Page<>(param.getPageNo(), param.getPageSize()); - page.setOptimizeCountSql(false); - IPage iPage = dbhubUserMapper.selectPage(page, query); + page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = dbhubUserMapper.selectPage(page, queryWrapper); return PageResult.of(userConverter.do2dto(iPage.getRecords()), iPage.getTotal(), param); } @Override - public DataResult update(User user) { - DbhubUserDO dbhubUserDO = userConverter.dto2do(user); - if (Objects.nonNull(dbhubUserDO.getPassword())) { - String bcryptPassword = DigestUtil.bcrypt(dbhubUserDO.getPassword()); - dbhubUserDO.setPassword(bcryptPassword); + public DataResult update(UserUpdateParam user) { + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(user.getId())) { + throw new BusinessException("user.canNotOperateSystemAccount"); } - int n = dbhubUserMapper.updateById(dbhubUserDO); - return DataResult.of(n == 1); + DbhubUserDO data = userConverter.param2do(user); + if (Objects.nonNull(data.getPassword())) { + String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); + data.setPassword(bcryptPassword); + } + + if (RoleCodeEnum.ADMIN.getDefaultUserId().equals(user.getId())) { + data.setStatus(null); + data.setEmail(null); + data.setUserName(null); + data.setRoleCode(null); + } + dbhubUserMapper.updateById(data); + return DataResult.of(data.getId()); } @Override - public DataResult delete(Long id) { - int n = dbhubUserMapper.deleteById(id); - return DataResult.of(n == 1); + public ActionResult delete(Long id) { + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(id) || RoleCodeEnum.ADMIN.getDefaultUserId().equals(id)) { + throw new BusinessException("user.canNotOperateSystemAccount"); + } + dbhubUserMapper.deleteById(id); + return ActionResult.isSuccess(); } @Override - public DataResult create(User user) { - DbhubUserDO data = userConverter.dto2do(user); + public DataResult create(UserCreateParam user) { + DbhubUserDO data = userConverter.param2do(user); String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); data.setPassword(bcryptPassword); dbhubUserMapper.insert(data); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java index fd4e30c26..42b821c93 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java @@ -1,7 +1,6 @@ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; -import ai.chat2db.server.domain.repository.entity.DataSourceDO; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.IPage; import org.apache.ibatis.annotations.Param; @@ -13,6 +12,8 @@ */ public interface DataSourceAccessCustomMapper extends Mapper { - IPage comprehensivePageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, @Param("searchKey") String searchKey); + IPage comprehensivePageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, + @Param("userOrTeamSearchKey") String userOrTeamSearchKey, + @Param("dataSourceSearchKey") String dataSourceSearchKey); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java index 0803f4394..db758e397 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java @@ -1,6 +1,5 @@ package ai.chat2db.server.domain.repository.mapper; -import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.domain.repository.entity.TeamUserDO; import com.baomidou.mybatisplus.core.mapper.Mapper; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -13,6 +12,6 @@ */ public interface TeamUserCustomMapper extends Mapper { - IPage comprehensivePageQuery(IPage page, @Param("teamId") Long teamId, + IPage comprehensivePageQuery(IPage page, @Param("teamId") Long teamId, @Param("userId") Long userId, @Param("teamRoleCode") String teamRoleCode); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml index 94e73c2d8..22c8c62f6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml @@ -4,18 +4,24 @@ diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml index fdc691673..797964f7d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml @@ -4,9 +4,12 @@ diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties index ac3d4314e..a1871dcaf 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties @@ -23,4 +23,6 @@ connection.ssh.error=SSH connection failed, please check the connection informat connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number -sqlResult.success=Execution successful \ No newline at end of file +sqlResult.success=Execution successful + +user.canNotOperateSystemAccount=System accounts cannot be operated \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index 7e049f759..ed17ed945 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -21,4 +21,6 @@ connection.ssh.error=SSH connection failed, please check the connection informat connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number -sqlResult.success=Execution successful \ No newline at end of file +sqlResult.success=Execution successful + +user.canNotOperateSystemAccount=System accounts cannot be operated \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index b3de56bc7..8a08545c1 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -20,4 +20,6 @@ connection.ssh.error=SSH 链接异常,请检查SSH配置 connection.driver.load.error=数据库驱动加载异常,请检查驱动配置 # sqlResult sqlResult.rowNumber=行号 -sqlResult.success=执行成功 \ No newline at end of file +sqlResult.success=执行成功 + +user.canNotOperateSystemAccount=不能操作系统账号 diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java index f707a6ac4..5f690e149 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java @@ -272,6 +272,25 @@ public PageResult map(Function mapper) { return pageResult; } + /** + * 将当前的类型转换成另外一个类型 + * + * @param mapper 转换的方法 + * @param 返回的类型 + * @return 分页返回对象 + */ + public ListResult mapToList(Function mapper) { + List returnData = hasData(this) ? getData().stream().map(mapper).collect(Collectors.toList()) + : Collections.emptyList(); + ListResult result = new ListResult<>(); + result.setSuccess(getSuccess()); + result.setErrorCode(getErrorCode()); + result.setErrorMessage(getErrorMessage()); + result.setTraceId(getTraceId()); + result.setData(returnData); + return result; + } + /** * 将当前的类型转换成另外一个类型 * 并且转换成web的类型 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java index dcc7ddc2e..d58596405 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java @@ -1,11 +1,19 @@ package ai.chat2db.server.admin.api.controller.common; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import java.util.List; + +import ai.chat2db.server.admin.api.controller.common.converter.CommonAdminConverter; import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; -import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; -import ai.chat2db.server.domain.api.service.DataSourceService; -import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; +import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; +import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.service.TeamService; +import ai.chat2db.server.domain.api.service.UserService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import com.google.common.collect.Lists; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.GetMapping; @@ -22,9 +30,11 @@ public class CommonAdminController { @Resource - private DataSourceService dataSourceService; + private UserService userService; + @Resource + private TeamService teamService; @Resource - private DataSourceAdminConverter dataSourceAdminConverter; + private CommonAdminConverter commonAdminConverter; /** * Fuzzy query of users or teams @@ -34,8 +44,18 @@ public class CommonAdminController { * @version 2.1.0 */ @GetMapping("/team_user/list") - public WebPageResult teamUserList(@Valid CommonPageQueryRequest request) { - return null; + public ListResult teamUserList(@Valid CommonQueryRequest request) { + UserPageQueryParam userPageQueryParam = commonAdminConverter.request2paramUser(request); + List result = Lists.newArrayList(); + result.addAll(userService.pageQuery(userPageQueryParam, null) + .mapToList(commonAdminConverter::dto2voTeamUser) + .getData()); + + TeamPageQueryParam teamPageQueryParam = commonAdminConverter.request2paramTeam(request); + result.addAll(teamService.pageQuery(teamPageQueryParam, null) + .mapToList(commonAdminConverter::dto2voTeamUser) + .getData()); + return ListResult.of(result); } /** @@ -46,8 +66,9 @@ public WebPageResult teamUserList(@Valid CommonPageQueryRequest * @version 2.1.0 */ @GetMapping("/user/list") - public WebPageResult userList(@Valid CommonPageQueryRequest request) { - return null; + public ListResult userList(@Valid CommonQueryRequest request) { + return userService.pageQuery(commonAdminConverter.request2paramUser(request), null) + .mapToList(commonAdminConverter::dto2voUser); } /** @@ -58,8 +79,9 @@ public WebPageResult userList(@Valid CommonPageQueryRequest requ * @version 2.1.0 */ @GetMapping("/team/list") - public WebPageResult teamList(@Valid CommonPageQueryRequest request) { - return null; + public ListResult teamList(@Valid CommonQueryRequest request) { + return teamService.pageQuery(commonAdminConverter.request2paramTeam(request), null) + .mapToList(commonAdminConverter::dto2voTeam); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java new file mode 100644 index 000000000..41b287954 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java @@ -0,0 +1,87 @@ +package ai.chat2db.server.admin.api.controller.common.converter; + +import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; +import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; +import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; +import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring", imports = {AccessObjectTypeEnum.class}) +public abstract class CommonAdminConverter { + + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "pageSize", expression = "java(10)"), + }) + public abstract TeamPageQueryParam request2paramTeam(CommonQueryRequest request); + + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "pageSize", expression = "java(10)"), + }) + public abstract UserPageQueryParam request2paramUser(CommonQueryRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract SimpleTeamVO dto2voTeam(Team dto); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract SimpleUserVO dto2voUser(User dto); + + /** + * conversion + * + * @param dto + * @return + */ + @Mappings({ + @Mapping(target = "type", expression = "java(AccessObjectTypeEnum.TEAM.getCode())"), + @Mapping(target = "code", source = "code"), + @Mapping(target = "name", source = "name"), + }) + public abstract TeamUserListVO dto2voTeamUser(Team dto); + + /** + * conversion + * + * @param dto + * @return + */ + @Mappings({ + @Mapping(target = "type", expression = "java(AccessObjectTypeEnum.USER.getCode())"), + @Mapping(target = "code", source = "userName"), + @Mapping(target = "name", source = "nickName"), + }) + public abstract TeamUserListVO dto2voTeamUser(User dto); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java index 2160e56d8..f23df0c2c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java @@ -5,6 +5,7 @@ import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessPageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourceAccessPageQueryVO; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -59,8 +60,14 @@ public WebPageResult page(@Valid DataSourceAccessPa * @version 2.1.0 */ @PostMapping("/batch_create") - public ActionResult batchCreate(@RequestBody DataSourceAccessBatchCreateRequest request) { - return dataSourceAccessService.batchCreate(dataSourceAccessAdminConverter.request2param(request)); + public ActionResult batchCreate(@Valid @RequestBody DataSourceAccessBatchCreateRequest request) { + request.getAccessObjectList() + .forEach(accessObject -> dataSourceAccessService.create(DataSourceAccessCreatParam.builder() + .dataSourceId(request.getDataSourceId()) + .accessObjectId(accessObject.getId()) + .accessObjectType(accessObject.getType()) + .build())); + return ActionResult.isSuccess(); } /** @@ -71,7 +78,7 @@ public ActionResult batchCreate(@RequestBody DataSourceAccessBatchCreateRequest */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return dataSourceAccessService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java index bcb1bce6d..19839a212 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java @@ -8,6 +8,8 @@ import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; /** * converter @@ -23,6 +25,9 @@ public abstract class DataSourceAccessAdminConverter { * @param request * @return */ + @Mappings({ + @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), + }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(DataSourceAccessPageQueryRequest request); /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java index c51e3d1b2..1e1ef7e60 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java @@ -1,13 +1,16 @@ package ai.chat2db.server.admin.api.controller.team; +import ai.chat2db.server.admin.api.controller.team.converter.TeamAdminConverter; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; +import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -25,6 +28,10 @@ @RequestMapping("/api/admin/team") @RestController public class TeamAdminController { + @Resource + private TeamService teamService; + @Resource + private TeamAdminConverter teamAdminConverter; /** * Pagination query @@ -35,7 +42,8 @@ public class TeamAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return null; + return teamService.pageQuery(teamAdminConverter.request2param(request), null) + .mapToWeb(teamAdminConverter::dto2vo); } /** @@ -47,8 +55,7 @@ public WebPageResult page(@Valid CommonPageQueryRequest request */ @PostMapping("/create") public DataResult create(@RequestBody TeamCreateRequest request) { - return null; - + return teamService.create(teamAdminConverter.request2param(request)); } /** @@ -59,8 +66,8 @@ public DataResult create(@RequestBody TeamCreateRequest request) { * @version 2.1.0 */ @PostMapping("/update") - public ActionResult update(@RequestBody TeamUpdateRequest request) { - return null; + public DataResult update(@RequestBody TeamUpdateRequest request) { + return teamService.update(teamAdminConverter.request2param(request)); } /** @@ -71,6 +78,6 @@ public ActionResult update(@RequestBody TeamUpdateRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return teamService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java index 8ffd260cd..149ec7d81 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java @@ -1,12 +1,17 @@ package ai.chat2db.server.admin.api.controller.team; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.team.converter.TeamDataSourcesAdminConverter; import ai.chat2db.server.admin.api.controller.team.request.TeamDataSourceBatchCreateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; +import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -24,6 +29,14 @@ @RequestMapping("/api/admin/team/data_source") @RestController public class TeamDataSourceAdminController { + private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() + .accessObject(Boolean.TRUE) + .build(); + + @Resource + private DataSourceAccessService dataSourceAccessService; + @Resource + private TeamDataSourcesAdminConverter teamDataSourcesAdminConverter; /** * Pagination query @@ -34,7 +47,9 @@ public class TeamDataSourceAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return null; + return dataSourceAccessService.comprehensivePageQuery(teamDataSourcesAdminConverter.request2param(request), + DATA_SOURCE_ACCESS_SELECTOR) + .mapToWeb(teamDataSourcesAdminConverter::dto2vo); } /** @@ -45,9 +60,14 @@ public WebPageResult page(@Valid CommonPageQueryReque * @version 2.1.0 */ @PostMapping("/batch_create") - public DataResult create(@RequestBody TeamDataSourceBatchCreateRequest request) { - return null; - + public ActionResult create(@Valid @RequestBody TeamDataSourceBatchCreateRequest request) { + request.getDataSourceIdList() + .forEach(dataSourceId -> dataSourceAccessService.create(DataSourceAccessCreatParam.builder() + .dataSourceId(dataSourceId) + .accessObjectId(request.getTeamId()) + .accessObjectType(AccessObjectTypeEnum.TEAM.getCode()) + .build())); + return ActionResult.isSuccess(); } /** @@ -58,6 +78,6 @@ public DataResult create(@RequestBody TeamDataSourceBatchCreateRequest req */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return dataSourceAccessService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java index 9073bd5b6..bb788b03a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java @@ -1,12 +1,16 @@ package ai.chat2db.server.admin.api.controller.team; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.team.converter.TeamUserAdminConverter; import ai.chat2db.server.admin.api.controller.team.request.TeamUserBatchCreateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; +import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -24,6 +28,14 @@ @RequestMapping("/api/admin/team/user") @RestController public class TeamUserAdminController { + private static final TeamUserSelector TEAM_USER_SELECTOR = TeamUserSelector.builder() + .user(Boolean.TRUE) + .build(); + + @Resource + private TeamUserService teamUserService; + @Resource + private TeamUserAdminConverter teamUserAdminConverter; /** * Pagination query @@ -34,7 +46,8 @@ public class TeamUserAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return null; + return teamUserService.comprehensivePageQuery(teamUserAdminConverter.request2param(request), TEAM_USER_SELECTOR) + .mapToWeb(teamUserAdminConverter::dto2vo); } /** @@ -45,9 +58,13 @@ public WebPageResult page(@Valid CommonPageQueryRequest req * @version 2.1.0 */ @PostMapping("/batch_create") - public DataResult create(@RequestBody TeamUserBatchCreateRequest request) { - return null; - + public ActionResult create(@Valid @RequestBody TeamUserBatchCreateRequest request) { + request.getUserIdList() + .forEach(userId -> teamUserService.create(TeamUserCreatParam.builder() + .teamId(request.getTeamId()) + .userId(userId) + .build())); + return ActionResult.isSuccess(); } /** @@ -58,6 +75,6 @@ public DataResult create(@RequestBody TeamUserBatchCreateRequest request) */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return teamUserService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java new file mode 100644 index 000000000..9cbd9a16f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java @@ -0,0 +1,57 @@ +package ai.chat2db.server.admin.api.controller.team.converter; + +import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.param.team.TeamCreateParam; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; +import org.mapstruct.Mapper; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) +public abstract class TeamAdminConverter { + + + /** + * conversion + * + * @param request + * @return + */ + public abstract TeamPageQueryParam request2param(CommonPageQueryRequest request); + + + /** + * conversion + * + * @param dto + * @return + */ + public abstract TeamPageQueryVO dto2vo(Team dto); + + + /** + * conversion + * + * @param request + * @return + */ + public abstract TeamCreateParam request2param(TeamCreateRequest request); + + + /** + * conversion + * + * @param request + * @return + */ + public abstract TeamUpdateParam request2param(TeamUpdateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java new file mode 100644 index 000000000..451bf7e0a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java @@ -0,0 +1,49 @@ +package ai.chat2db.server.admin.api.controller.team.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSourceAccess; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) +public abstract class TeamDataSourcesAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "dataSourceSearchKey"), + }) + public abstract DataSourceAccessComprehensivePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract TeamDataSourcePageQueryVO dto2vo(DataSourceAccess dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java new file mode 100644 index 000000000..4327e2e4b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java @@ -0,0 +1,48 @@ +package ai.chat2db.server.admin.api.controller.team.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.model.TeamUser; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class TeamUserAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "userSearchKey"), + }) + public abstract TeamUserComprehensivePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract TeamUserPageQueryVO dto2vo(TeamUser dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java index 6c3f20f74..57eeaab98 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java @@ -23,6 +23,7 @@ public class TeamDataSourceBatchCreateRequest { /** * team id */ + @NotNull private Long teamId; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java index 584fd05ef..2a1a2b8ab 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java @@ -16,12 +16,6 @@ public class TeamUpdateRequest { @NotNull private Long id; - /** - * 团队编码 - */ - @NotNull - private String code; - /** * 团队名称 */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java index 64a29215e..42f1a75fd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java @@ -23,6 +23,7 @@ public class TeamUserBatchCreateRequest { /** * team id */ + @NotNull private Long teamId; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java index 6a8a652a2..7caa47de1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java @@ -2,6 +2,7 @@ package ai.chat2db.server.admin.api.controller.team.vo; import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; +import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -12,6 +13,12 @@ @Data public class TeamDataSourcePageQueryVO { + /** + * 主键 + */ + @NotNull + private Long id; + /** * team id */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java index 0c5bd94d8..f94f70bf8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java @@ -1,12 +1,12 @@ package ai.chat2db.server.admin.api.controller.user; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.user.converter.UserAdminConverter; import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; -import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; @@ -30,9 +30,9 @@ public class UserAdminController { @Resource - private DataSourceService dataSourceService; + private UserService userService; @Resource - private UserAdminConverter dataSourceAdminConverter; + private UserAdminConverter userAdminConverter; /** * Pagination query @@ -43,7 +43,8 @@ public class UserAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return null; + return userService.pageQuery(userAdminConverter.request2param(request), null) + .mapToWeb(userAdminConverter::dto2vo); } /** @@ -54,9 +55,8 @@ public WebPageResult page(@Valid CommonPageQueryRequest request * @version 2.1.0 */ @PostMapping("/create") - public DataResult create(@RequestBody UserCreateRequest request) { - return null; - + public DataResult create(@Valid @RequestBody UserCreateRequest request) { + return userService.create(userAdminConverter.request2param(request)); } /** @@ -67,8 +67,8 @@ public DataResult create(@RequestBody UserCreateRequest request) { * @version 2.1.0 */ @PostMapping("/update") - public ActionResult update(@RequestBody UserUpdateRequest request) { - return null; + public DataResult update(@RequestBody UserUpdateRequest request) { + return userService.update(userAdminConverter.request2param(request)); } /** @@ -79,6 +79,6 @@ public ActionResult update(@RequestBody UserUpdateRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return userService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java index 3b676f171..473f60b96 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java @@ -1,12 +1,17 @@ package ai.chat2db.server.admin.api.controller.user; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; -import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.converter.UserDataSourcesAdminConverter; +import ai.chat2db.server.admin.api.controller.user.request.UserDataSourceBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; +import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -24,6 +29,14 @@ @RequestMapping("/api/admin/user/data_source") @RestController public class UserDataSourceAdminController { + private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() + .accessObject(Boolean.TRUE) + .build(); + + @Resource + private DataSourceAccessService dataSourceAccessService; + @Resource + private UserDataSourcesAdminConverter userDataSourcesAdminConverter; /** * Pagination query @@ -34,7 +47,9 @@ public class UserDataSourceAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return null; + return dataSourceAccessService.comprehensivePageQuery(userDataSourcesAdminConverter.request2param(request), + DATA_SOURCE_ACCESS_SELECTOR) + .mapToWeb(userDataSourcesAdminConverter::dto2vo); } /** @@ -45,9 +60,14 @@ public WebPageResult page(@Valid CommonPageQueryReque * @version 2.1.0 */ @PostMapping("/batch_create") - public DataResult create(@RequestBody UserTeamBatchCreateRequest request) { - return null; - + public ActionResult create(@RequestBody UserDataSourceBatchCreateRequest request) { + request.getDataSourceIdList() + .forEach(dataSourceId -> dataSourceAccessService.create(DataSourceAccessCreatParam.builder() + .dataSourceId(dataSourceId) + .accessObjectId(request.getUserId()) + .accessObjectType(AccessObjectTypeEnum.USER.getCode()) + .build())); + return ActionResult.isSuccess(); } /** @@ -58,6 +78,6 @@ public DataResult create(@RequestBody UserTeamBatchCreateRequest request) */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return dataSourceAccessService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java index 20a62d970..9888f3cfa 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java @@ -1,12 +1,16 @@ package ai.chat2db.server.admin.api.controller.user; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.admin.api.controller.user.converter.UserTeamAdminConverter; import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; +import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -24,6 +28,13 @@ @RequestMapping("/api/admin/user/team") @RestController public class UserTeamAdminController { + private static final TeamUserSelector TEAM_USER_SELECTOR = TeamUserSelector.builder() + .team(Boolean.TRUE) + .build(); + @Resource + private TeamUserService teamUserService; + @Resource + private UserTeamAdminConverter userTeamAdminConverter; /** * Pagination query @@ -34,7 +45,8 @@ public class UserTeamAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return null; + return teamUserService.comprehensivePageQuery(userTeamAdminConverter.request2param(request), TEAM_USER_SELECTOR) + .mapToWeb(userTeamAdminConverter::dto2vo); } /** @@ -45,9 +57,13 @@ public WebPageResult page(@Valid CommonPageQueryRequest req * @version 2.1.0 */ @PostMapping("/batch_create") - public DataResult create(@RequestBody UserTeamBatchCreateRequest request) { - return null; - + public ActionResult bacthCreate(@Valid @RequestBody UserTeamBatchCreateRequest request) { + request.getTeamIdList() + .forEach(teamId -> teamUserService.create(TeamUserCreatParam.builder() + .teamId(teamId) + .userId(request.getUserId()) + .build())); + return ActionResult.isSuccess(); } /** @@ -58,6 +74,6 @@ public DataResult create(@RequestBody UserTeamBatchCreateRequest request) */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable Long id) { - return null; + return teamUserService.delete(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java index 409e0d599..c3c750c5d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java @@ -1,5 +1,13 @@ package ai.chat2db.server.admin.api.controller.user.converter; +import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.param.user.UserCreateParam; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import org.mapstruct.Mapper; /** @@ -10,49 +18,38 @@ @Mapper(componentModel = "spring") public abstract class UserAdminConverter { - ///** - // * conversion - // * - // * @param request - // * @return - // */ - //public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); - // - ///** - // * conversion - // * - // * @param request - // * @return - // */ - //public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); - - ///** - // * conversion - // * - // * @param dto - // * @return - // */ - //public abstract DataSourcePageQueryVO dto2vo(DataSource dto); - // - ///** - // * 参数转换 - // * - // * @param request - // * @return - // */ - //@Mappings({ - // @Mapping(source = "user", target = "userName") - //}) - //public abstract DataSourceCreateParam createReq2param(UserCreateRequest request); - // - ///** - // * 参数转换 - // * - // * @param request - // * @return - // */ - //@Mappings({ - // @Mapping(source = "user", target = "userName") - //}) - //public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); + /** + * conversion + * + * @param request + * @return + */ + public abstract UserPageQueryParam request2param(CommonPageQueryRequest request); + + + /** + * conversion + * + * @param dto + * @return + */ + public abstract UserPageQueryVO dto2vo(User dto); + + + /** + * conversion + * + * @param request + * @return + */ + public abstract UserCreateParam request2param(UserCreateRequest request); + + + /** + * conversion + * + * @param request + * @return + */ + public abstract UserUpdateParam request2param(UserUpdateRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java new file mode 100644 index 000000000..e9de9d509 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java @@ -0,0 +1,49 @@ +package ai.chat2db.server.admin.api.controller.user.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSourceAccess; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) +public abstract class UserDataSourcesAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), + }) + public abstract DataSourceAccessComprehensivePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract UserDataSourcePageQueryVO dto2vo(DataSourceAccess dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java new file mode 100644 index 000000000..850e718a2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java @@ -0,0 +1,37 @@ +package ai.chat2db.server.admin.api.controller.user.converter; + +import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.model.TeamUser; +import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class UserTeamAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "teamSearchKey"), + }) + public abstract TeamUserComprehensivePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract UserTeamPageQueryVO dto2vo(TeamUser dto); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java new file mode 100644 index 000000000..048b6c23e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java @@ -0,0 +1,18 @@ + +package ai.chat2db.server.common.api.controller.request; + +import lombok.Data; + +/** + * Common query + * + * @author Jiaju Zhuang + */ +@Data +public class CommonQueryRequest { + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java index 8ba60057e..adfcd5885 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/user/UserController.java @@ -1,27 +1,7 @@ package ai.chat2db.server.web.api.controller.user; -import ai.chat2db.server.domain.api.model.User; -import ai.chat2db.server.domain.api.param.UserQueryParam; -import ai.chat2db.server.domain.api.service.UserService; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; -import ai.chat2db.server.tools.base.wrapper.result.DataResult; -import ai.chat2db.server.tools.base.wrapper.result.PageResult; -import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; -import ai.chat2db.server.web.api.controller.user.converter.UserWebConverter; -import ai.chat2db.server.web.api.controller.user.request.UserCreateRequest; -import ai.chat2db.server.web.api.controller.user.request.UserQueryRequest; -import ai.chat2db.server.web.api.controller.user.request.UserUpdateRequest; -import ai.chat2db.server.web.api.controller.user.vo.UserVO; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; /** @@ -31,60 +11,60 @@ @RequestMapping("/api/user") @RestController public class UserController { - - @Autowired - private UserService userService; - - @Autowired - private UserWebConverter userWebConverter; - - @GetMapping("/{id}") - public DataResult query(@PathVariable("id") Long id) { - return DataResult.of(userWebConverter.dto2vo(userService.query(id).getData())); - } - - @GetMapping("/list") - public WebPageResult list(UserQueryRequest request) { - UserQueryParam userQueryParam = new UserQueryParam(); - userQueryParam.setKeyWord(request.getKeyWord()); - userQueryParam.setPageNo(request.getPageNo()); - userQueryParam.setPageSize(request.getPageSize()); - PageResult pageResult = userService.queryPage(userQueryParam); - return WebPageResult.of(userWebConverter.dto2vo(pageResult.getData()), pageResult.getTotal(), request); - } - - /** - * 新增Key - * - * @param request - * @return - */ - @PostMapping("/create") - public DataResult create(@RequestBody UserCreateRequest request) { - return userService.create(userWebConverter.createRequest2dto(request)); - } - - /** - * 更新我的保存 - * - * @param request - * @return - */ - @RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) - public ActionResult update(@RequestBody UserUpdateRequest request) { - DataResult result = userService.update(userWebConverter.updateRequest2dto(request)); - return ActionResult.isSuccess(); - } - - /** - * 删除我的保存 - * - * @param id - * @return - */ - @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable("id") Long id) { - userService.delete(id); - return ActionResult.isSuccess(); - } + // + //@Autowired + //private UserService userService; + // + //@Autowired + //private UserWebConverter userWebConverter; + // + //@GetMapping("/{id}") + //public DataResult query(@PathVariable("id") Long id) { + // return DataResult.of(userWebConverter.dto2vo(userService.query(id).getData())); + //} + // + //@GetMapping("/list") + //public WebPageResult list(UserQueryRequest request) { + // UserQueryParam userQueryParam = new UserQueryParam(); + // userQueryParam.setKeyWord(request.getKeyWord()); + // userQueryParam.setPageNo(request.getPageNo()); + // userQueryParam.setPageSize(request.getPageSize()); + // PageResult pageResult = userService.queryPage(userQueryParam); + // return WebPageResult.of(userWebConverter.dto2vo(pageResult.getData()), pageResult.getTotal(), request); + //} + // + ///** + // * 新增Key + // * + // * @param request + // * @return + // */ + //@PostMapping("/create") + //public DataResult create(@RequestBody UserCreateRequest request) { + // return userService.create(userWebConverter.createRequest2dto(request)); + //} + // + ///** + // * 更新我的保存 + // * + // * @param request + // * @return + // */ + //@RequestMapping(value = "/update",method = {RequestMethod.POST, RequestMethod.PUT}) + //public ActionResult update(@RequestBody UserUpdateRequest request) { + // DataResult result = userService.update(userWebConverter.updateRequest2dto(request)); + // return ActionResult.isSuccess(); + //} + // + ///** + // * 删除我的保存 + // * + // * @param id + // * @return + // */ + //@DeleteMapping("/{id}") + //public ActionResult delete(@PathVariable("id") Long id) { + // userService.delete(id); + // return ActionResult.isSuccess(); + //} } \ No newline at end of file From 201d08a9eb7e76427dfc2e2b6a2876e632b5f0ad Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 13 Aug 2023 15:15:19 +0800 Subject: [PATCH 0611/1069] fix: Optimize table styles --- .../SearchResult/TableBox/index.less | 23 ++++++++++++------- .../SearchResult/TableBox/index.tsx | 4 ++-- chat2db-client/src/typings/database.ts | 1 + 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index f403fab44..81b462ca3 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -13,14 +13,21 @@ // border-bottom: 0; // // height: 400px !important; // } - .hGEOgo td.first { - border-left: 0; - } - .hGEOgo tr.first th { - border-top: 0; - } - .hGEOgo th.first { - border-left: 0; + .hGEOgo { + --header-bgcolor: #fafafa; + --header-color: #000; + th { + font-weight: bold; + } + th.first { + border-left: 0; + } + td.first { + border-left: 0; + } + tr.first th { + border-top: 0; + } } .art-table-cell { diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 7109f55ed..546f2f1a9 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -37,7 +37,7 @@ const DarkSupportBaseTable: any = styled(BaseTable)` &.dark { --bgcolor: var(--color-bg-base); --header-bgcolor: var(--color-bg-elevated); - --hover-bgcolor: #46484a; + --hover-bgcolor: #1a1b1c; --header-hover-bgcolor: #606164; --highlight-bgcolor: #191a1b; --header-highlight-bgcolor: #191a1b; @@ -230,7 +230,7 @@ export default function TableBox(props: ITableProps) { ); - if (!columns.length || sqlType !== 'SELECT') { + if (!columns.length) { return ( <> diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index cf4925157..2dcbedb0c 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -24,6 +24,7 @@ export interface IManageResultData { duration: number; fuzzyTotal: string; hasNextPage: boolean; + sqlType: 'SELECT' | 'UNKNOWN'; } /** 查询结果 配置属性 */ From a36f0b7d60bc38977feea986b8b4b9bb65d27b28 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 15:22:43 +0800 Subject: [PATCH 0612/1069] =?UTF-8?q?sql=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.vscode/settings.json | 45 ++++++++++--------- .../components/Console/MonacoEditor/index.tsx | 12 ++++- .../src/components/Console/index.tsx | 23 ++++------ chat2db-client/src/service/sql.ts | 9 +++- .../src/theme/background/darkDimmed.ts | 2 +- chat2db-client/src/utils/index.ts | 29 +++++++++++- 6 files changed, 79 insertions(+), 41 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 04e59cc0d..75c08e7a4 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -19,52 +19,53 @@ }, "git.mergeEditor": false, "cSpell.words": [ - "AZUREAI", - "Appstore", - "CLICKHOUSE", - "Cascader", - "Consolas", - "DBAI", - "Dmaven", - "Iconfont", - "JDBC", - "KEYPAIR", - "KINGBASE", - "MARIADB", - "Mddhhmmss", - "Menlo", - "OCEANBASE", - "OPENAI", - "POSTGRESQL", - "RESTAI", - "SQLSERVER", - "Sercurity", - "USERANDPASSWORD", - "VIEWCOLUMN", "ahooks", "antd", + "Appstore", + "AZUREAI", "bgcolor", + "Cascader", "chatgpt", + "CLICKHOUSE", + "Consolas", "datas", "datasource", + "DBAI", "dbhub", + "Dmaven", "echarts", "favicons", "findstr", "gtag", + "Iconfont", "indexs", + "JDBC", + "KEYPAIR", + "KINGBASE", "lsof", + "MARIADB", + "Mddhhmmss", + "Menlo", "netstat", + "OCEANBASE", + "OPENAI", "pgsql", "pnpm", + "POSTGRESQL", "remaininguses", + "RESTAI", "scrollbar", + "Sercurity", "sortablejs", + "SQLSERVER", + "Tigger", "ueabe", "ueabf", "ueac", "umijs", + "USERANDPASSWORD", "uuidv", + "VIEWCOLUMN", "wireframe" ], "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index b640eb6e7..d296f0bea 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -149,7 +149,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { if (options?.theme) { monaco.editor.setTheme(options.theme); return - } + } monaco.editor.setTheme(appTheme.backgroundColor); }, [appTheme.backgroundColor, options?.theme]); @@ -315,12 +315,22 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e return; } switch (range) { + // 覆盖所有内容 case 'cover': newRange = model.getFullModelRange(); break; + // 在开头添加内容 case 'front': newRange = new monaco.Range(1, 1, 1, 1); break; + // 格式化选中区域的sql + case 'select': + const selection = editor.getSelection(); + if (selection) { + newRange = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn); + } + break; + // 在末尾添加内容 case 'end': const lastLine = editor.getModel().getLineCount(); const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index cb24b431b..f7c5d2283 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -5,7 +5,6 @@ import connectToEventSource from '@/utils/eventSource'; import { Button, Spin, message, Drawer, Modal } from 'antd'; import ChatInput from './ChatInput'; import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; -import { format } from 'sql-formatter'; import historyServer from '@/service/history'; import aiServer from '@/service/ai'; import { v4 as uuidv4 } from 'uuid'; @@ -14,7 +13,7 @@ import Iconfont from '../Iconfont'; import { IAiConfig, ITreeNode } from '@/typings'; import { IAIState } from '@/models/ai'; import Popularize from '@/components/Popularize'; -import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText } from '@/utils'; +import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText, formatSql } from '@/utils'; import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; import { AiSqlSourceType } from '@/typings/ai'; import i18n from '@/i18n'; @@ -162,7 +161,7 @@ function Console(props: IProps) { function timingAutoSave() { timerRef.current = setInterval(() => { handleLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); - }, 5000); + }, 500); } const tableListName = useMemo(() => { @@ -213,7 +212,7 @@ function Console(props: IProps) { } }, 3000); } - }catch (e) { + } catch (e) { setIsLoading(false); } @@ -471,17 +470,11 @@ function Console(props: IProps) { -

{i18n('common.text.noData')}

}} isStickyHead stickyTop={31} @@ -279,7 +279,6 @@ export default function TableBox(props: ITableProps) { open={!!viewTableCellData?.name} onCancel={handleCancel} width="60vw" - height="70vh" maskClosable={false} footer={ <> diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 5805a3118..0692ee6b7 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -52,7 +52,7 @@ justify-content: center; align-items: center; align-items: center; - margin-bottom: 10px; + margin-bottom: 16px; width: 40px; height: 40px; border-radius: 8px; diff --git a/chat2db-client/src/theme/background/dark.ts b/chat2db-client/src/theme/background/dark.ts index 707e1b176..9f44314cc 100644 --- a/chat2db-client/src/theme/background/dark.ts +++ b/chat2db-client/src/theme/background/dark.ts @@ -45,7 +45,8 @@ const antDarkTheme = { colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#0a0b0c', colorBgElevated: '#131418', - colorBorder: '#36373a', + colorBorder: 'rgba(54, 55, 58,0.4)', + colorBorderSecondary: 'rgba(54, 55, 58,0.4)', }, }; diff --git a/chat2db-client/src/theme/background/darkDimmed.ts b/chat2db-client/src/theme/background/darkDimmed.ts index 192a04ad9..c637e55da 100644 --- a/chat2db-client/src/theme/background/darkDimmed.ts +++ b/chat2db-client/src/theme/background/darkDimmed.ts @@ -45,7 +45,7 @@ const antdLightTheme = { colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: 'rgb(28, 33, 40)', colorBgElevated: 'rgb(34, 39, 46)', - colorBorder: 'rgb(68, 76, 86)', + colorBorder: 'rgba(55, 62, 71, 0.4)', colorBorderSecondary: 'rgba(55, 62, 71, 0.4)', controlItemBgActive: 'rgba(241, 241, 244, 0.08);', // ...commonToken, diff --git a/chat2db-client/src/theme/background/light.ts b/chat2db-client/src/theme/background/light.ts index 97d9e3138..1c4b7d522 100644 --- a/chat2db-client/src/theme/background/light.ts +++ b/chat2db-client/src/theme/background/light.ts @@ -42,10 +42,11 @@ const antdLightTheme = { ...commonToken, colorText: "#232429", colorBgBase: '#fff', - colorHoverBg: '#eee', + colorHoverBg: 'rgba(0, 0, 0, 0.03)', colorBgContainer: '#fff', colorBgElevated: '#f6f8fa', - colorBorder: '#d3d3d4', + colorBorder: 'rgba(211, 211, 212, 0.4)', + colorBorderSecondary: 'rgba(211, 211, 212, 0.4)', }, }; From 126a01a7001c8ed23e7491122cd44a599b85a6bd Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 16:01:58 +0800 Subject: [PATCH 0616/1069] =?UTF-8?q?fix:=E5=88=B7=E6=96=B0=E8=A7=86?= =?UTF-8?q?=E5=9B=BEbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/SearchResult/index.tsx | 1 - .../workspace/components/TableList/index.tsx | 47 ++++++++++--------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 07bf9ebc2..9079a1f79 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -97,7 +97,6 @@ export default memo(function SearchResult(props) { }; const renderTableContent = (resultDataList || []).map((item, index: number) => { - console.log(item.sql, item.originalSql); if (item.success) { return ( (false); useEffect(() => { + setCurList([]); if (isReady) { - setTableLoading(true); - setCurList([]); - treeConfig[curType.value].getChildren!({ - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - }).then(res => { - setCurList(res); - setTableLoading(false); - if (curType.value === TreeNodeType.TABLES) { - dispatch({ - type: 'workspace/setCurTableList', - payload: res, - }) - } - }) + getList(); } }, [curWorkspaceParams, curType]); + + useEffect(() => { if (searching) { inputRef.current!.focus({ @@ -78,6 +67,24 @@ const TableList = dvaModel(function (props: any) { } }, [searching]) + function getList(refresh: boolean = false) { + setTableLoading(true); + treeConfig[curType.value].getChildren!({ + refresh, + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + }).then(res => { + setCurList(res); + setTableLoading(false); + if (curType.value === TreeNodeType.TABLES) { + dispatch({ + type: 'workspace/setCurTableList', + payload: res, + }) + } + }) + } + function openSearch() { setSearching(true); } @@ -95,14 +102,8 @@ const TableList = dvaModel(function (props: any) { function refreshTableList() { if (isReady) { - dispatch({ - type: 'workspace/fetchGetCurTableList', - payload: { - ...curWorkspaceParams, - refresh: true, - extraParams: curWorkspaceParams, - } - }) + setCurList([]); + getList(true); } } From 9a7517dd29999cee5b190f525231797c4e82737a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 16:14:07 +0800 Subject: [PATCH 0617/1069] Complete team user code --- .../common/CommonAdminController.java | 22 ++++++++++++++++++- .../converter/CommonAdminConverter.java | 22 +++++++++++++++++++ .../DataSourceAccessAdminConverter.java | 1 + .../converter/DataSourceAdminConverter.java | 6 +++++ .../team/converter/TeamAdminConverter.java | 5 +++++ .../TeamDataSourcesAdminConverter.java | 1 + .../converter/TeamUserAdminConverter.java | 1 + .../user/converter/UserAdminConverter.java | 8 ++++--- .../UserDataSourcesAdminConverter.java | 1 + .../converter/UserTeamAdminConverter.java | 1 + 10 files changed, 64 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java index d58596405..c0d5d521c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/CommonAdminController.java @@ -5,11 +5,14 @@ import ai.chat2db.server.admin.api.controller.common.converter.CommonAdminConverter; import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; +import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -28,12 +31,16 @@ @RequestMapping("/api/admin/common") @RestController public class CommonAdminController { - + private static final DataSourceSelector DATA_SOURCE_SELECTOR = DataSourceSelector.builder() + .environment(Boolean.TRUE) + .build(); @Resource private UserService userService; @Resource private TeamService teamService; @Resource + private DataSourceService dataSourceService; + @Resource private CommonAdminConverter commonAdminConverter; /** @@ -84,4 +91,17 @@ public ListResult teamList(@Valid CommonQueryRequest request) { .mapToList(commonAdminConverter::dto2voTeam); } + /** + * Fuzzy query of data source + * + * @param request + * @return + * @version 2.1.0 + */ + @GetMapping("/data_source/list") + public ListResult dataSourceList(@Valid CommonQueryRequest request) { + return dataSourceService.queryPageWithPermission(commonAdminConverter.request2paramDataSource(request), + DATA_SOURCE_SELECTOR) + .mapToList(commonAdminConverter::dto2voDataSource); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java index 41b287954..24034a69f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java @@ -1,12 +1,15 @@ package ai.chat2db.server.admin.api.controller.common.converter; import ai.chat2db.server.admin.api.controller.common.vo.TeamUserListVO; +import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import org.mapstruct.Mapper; @@ -43,6 +46,17 @@ public abstract class CommonAdminConverter { }) public abstract UserPageQueryParam request2paramUser(CommonQueryRequest request); + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "pageSize", expression = "java(10)"), + }) + public abstract DataSourcePageQueryParam request2paramDataSource(CommonQueryRequest request); + /** * conversion * @@ -51,6 +65,14 @@ public abstract class CommonAdminConverter { */ public abstract SimpleTeamVO dto2voTeam(Team dto); + /** + * conversion + * + * @param dto + * @return + */ + public abstract SimpleDataSourceVO dto2voDataSource(DataSource dto); + /** * conversion * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java index 19839a212..f97e2098a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java @@ -27,6 +27,7 @@ public abstract class DataSourceAccessAdminConverter { */ @Mappings({ @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(DataSourceAccessPageQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java index 9a1ebd6e2..5236de606 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java @@ -27,6 +27,9 @@ public abstract class DataSourceAdminConverter { * @param request * @return */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); /** @@ -35,6 +38,9 @@ public abstract class DataSourceAdminConverter { * @param request * @return */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java index 9cbd9a16f..a6eaf129e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java @@ -10,6 +10,8 @@ import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; /** * converter @@ -26,6 +28,9 @@ public abstract class TeamAdminConverter { * @param request * @return */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) public abstract TeamPageQueryParam request2param(CommonPageQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java index 451bf7e0a..d3dc0ad52 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java @@ -27,6 +27,7 @@ public abstract class TeamDataSourcesAdminConverter { */ @Mappings({ @Mapping(source = "searchKey", target = "dataSourceSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(CommonPageQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java index 4327e2e4b..e7ed55b1f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java @@ -26,6 +26,7 @@ public abstract class TeamUserAdminConverter { */ @Mappings({ @Mapping(source = "searchKey", target = "userSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract TeamUserComprehensivePageQueryParam request2param(CommonPageQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java index c3c750c5d..28bc43b69 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java @@ -9,6 +9,8 @@ import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; /** * converter @@ -24,9 +26,11 @@ public abstract class UserAdminConverter { * @param request * @return */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) public abstract UserPageQueryParam request2param(CommonPageQueryRequest request); - /** * conversion * @@ -35,7 +39,6 @@ public abstract class UserAdminConverter { */ public abstract UserPageQueryVO dto2vo(User dto); - /** * conversion * @@ -44,7 +47,6 @@ public abstract class UserAdminConverter { */ public abstract UserCreateParam request2param(UserCreateRequest request); - /** * conversion * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java index e9de9d509..6e2eaf36f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java @@ -27,6 +27,7 @@ public abstract class UserDataSourcesAdminConverter { */ @Mappings({ @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract DataSourceAccessComprehensivePageQueryParam request2param(CommonPageQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java index 850e718a2..41a0e1399 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java @@ -24,6 +24,7 @@ public abstract class UserTeamAdminConverter { */ @Mappings({ @Mapping(source = "searchKey", target = "teamSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), }) public abstract TeamUserComprehensivePageQueryParam request2param(CommonPageQueryRequest request); From 4be95d6f33fe87beec089379db4c1b63664e205b Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 16:49:46 +0800 Subject: [PATCH 0618/1069] Remove redundant interceptors --- .../server/start/log/MyInterceptor.java | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/MyInterceptor.java diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/MyInterceptor.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/MyInterceptor.java deleted file mode 100644 index 0d864d9db..000000000 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/MyInterceptor.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.chat2db.server.start.log; - -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -@Slf4j -@Component -public class MyInterceptor implements HandlerInterceptor { - - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - //// 处理逻辑 - //MyResult result = new MyResult(); - //result.setCode(200); - //result.setMessage("处理成功"); - //request.setAttribute("myResult", result); - return true; - } - - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { - //// 取出处理结果并封装成实体对象 - //MyResult result = (MyResult) request.getAttribute("myResult"); - //modelAndView.setView(new MappingJackson2JsonView()); - //modelAndView.addObject(result); - log.info("xx"); - } -} \ No newline at end of file From 9419c33d7977dc7c3146eb46c905aa56682e97a4 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 13 Aug 2023 17:00:30 +0800 Subject: [PATCH 0619/1069] mask log --- .../server/tools/common/util/LogUtils.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java index 14cd284a1..605c28446 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java @@ -5,6 +5,7 @@ import cn.hutool.core.lang.UUID; import cn.hutool.core.net.NetUtil; +import org.apache.commons.lang3.StringUtils; import org.zalando.logbook.HttpHeaders; import org.zalando.logbook.HttpRequest; @@ -36,6 +37,24 @@ public class LogUtils { */ private static final Pattern LINE_FEED_PATTERN = Pattern.compile("\r|\n"); + /** + * mask string + * + * @param input + * @return + */ + private static String maskString(String input) { + if (StringUtils.isBlank(input)) { + return input; + } + + StringBuilder maskedString = new StringBuilder(input); + for (int i = 0; i < input.length(); i += 2) { + maskedString.setCharAt(i, '*'); + } + return maskedString.toString(); + } + /** * 去除换行符 * @@ -59,7 +78,7 @@ public static String cutLog(Object log) { if (Objects.isNull(log)) { return null; } - return EasyStringUtils.limitString(removeCrlf(log.toString()), MAX_LOG_LENGTH); + return EasyStringUtils.limitString(maskString(removeCrlf(log.toString())), MAX_LOG_LENGTH); } /** From 798639060f2b7416c913f284010079bd01e9d542 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 13 Aug 2023 17:09:32 +0800 Subject: [PATCH 0620/1069] mask log --- .../main/java/ai/chat2db/server/start/log/EasyLogSink.java | 4 ++-- .../java/ai/chat2db/server/tools/common/util/LogUtils.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java index 268edb65b..f3f9857bd 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/log/EasyLogSink.java @@ -54,10 +54,10 @@ public void printLog(final Correlation correlation, final HttpRequest request, f webLog.setStartTime(LocalDateTime.ofInstant(correlation.getStart(), ZoneId.systemDefault())); webLog.setEndTime(LocalDateTime.ofInstant(correlation.getEnd(), ZoneId.systemDefault())); try { - webLog.setRequest(LogUtils.cutLog(new String(request.getBody(), StandardCharsets.UTF_8))); + webLog.setRequest(LogUtils.maskString(LogUtils.cutLog(new String(request.getBody(), StandardCharsets.UTF_8)))); if (ContentTypeUtils.isContentTypeJSON(response.getContentType()) || ContentTypeUtils.isContentTypeHTML( response.getContentType())) { - webLog.setResponse(LogUtils.cutLog(new String(response.getBody(), StandardCharsets.UTF_8))); + webLog.setResponse(LogUtils.maskString(LogUtils.cutLog(new String(response.getBody(), StandardCharsets.UTF_8)))); } else { webLog.setResponse(response.getContentType() + ":[" + response.getBody().length + "]"); } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java index 605c28446..432cbb459 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java @@ -43,7 +43,7 @@ public class LogUtils { * @param input * @return */ - private static String maskString(String input) { + public static String maskString(String input) { if (StringUtils.isBlank(input)) { return input; } @@ -78,7 +78,7 @@ public static String cutLog(Object log) { if (Objects.isNull(log)) { return null; } - return EasyStringUtils.limitString(maskString(removeCrlf(log.toString())), MAX_LOG_LENGTH); + return EasyStringUtils.limitString(removeCrlf(log.toString()), MAX_LOG_LENGTH); } /** From edd46baa9a26a29b9eec9bcde36624d76a5e7c75 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 13 Aug 2023 17:11:02 +0800 Subject: [PATCH 0621/1069] mask log --- .../main/java/ai/chat2db/server/tools/common/util/LogUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java index 432cbb459..73dbd5c2f 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/LogUtils.java @@ -49,7 +49,7 @@ public static String maskString(String input) { } StringBuilder maskedString = new StringBuilder(input); - for (int i = 0; i < input.length(); i += 2) { + for (int i = 0; i < input.length(); i += 4) { maskedString.setCharAt(i, '*'); } return maskedString.toString(); From 6346851dc1a10f1027e9df466a5d2f5d3c501252 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 17:15:55 +0800 Subject: [PATCH 0622/1069] style:console tab --- CHANGELOG.md | 27 ++++++++++++ chat2db-client/src/components/Tabs/index.less | 43 +++++++++++-------- chat2db-client/src/components/Tabs/index.tsx | 6 ++- .../workspace/components/TableList/index.tsx | 10 ++++- .../components/WorkspaceRight/index.less | 9 ++-- .../components/WorkspaceRightItem/index.tsx | 2 +- 6 files changed, 70 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfa94d374..666bfc96a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,30 @@ +# 2.0.12 + +## ⭐ New Features + +- Supports viewing views, functions, triggers, and procedures +- Support selected sql formatting +- Added new dark themes + +## 🐞 Bug Fixes + +- Fixed sql formatting failure issue +- Fixed an issue where locally stored theme colors and background colors are incompatible with the new version, causing page crashes +- Logs desensitize sensitive data + +## ⭐ 新特性 + +- 支持查看视图、函数、触发器、过程 +- 支持选中sql格式化 +- 增加新的暗色主题 + +## 🐞 问题修复 + +- 修复sql格式化会失败问题 +- 修复本地存储的主题色、背景色与新版本不兼容时会导致页面崩溃问题 +- 日志对敏感数据进行脱敏 + + # 2.0.11 ## 🐞 Bug Fixes diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index dae9857f1..d61844814 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -1,8 +1,13 @@ @import '../../styles/var.less'; .tab-focus() { - border-radius: 8px 8px 0 0; - background-color: var(--color-bg-base); + background-color: var(--color-bg-elevated) !important; + // 添加内阴影 + box-shadow: inset 0 -1px 0 0 var(--color-primary); + border-bottom: 1px solid var(--color-primary); + .icon{ + display: flex; + } } .tab-focus-line() { @@ -13,7 +18,9 @@ .tab { display: flex; overflow: auto; - &::-webkit-scrollbar {display:none} + &:hover{ + &::-webkit-scrollbar {display:block;} + } } .tabList { @@ -25,33 +32,28 @@ display: flex; align-items: center; padding: 0px 10px; - line-height: 30px; - height: 30px; - width: 100px; - margin: 0px 2px; + line-height: 32px; + height: 32px; + width: 120px; cursor: pointer; user-select: none; - .text{ + border-right: 1px solid var(--color-border); + .textBox{ + flex: 1; display: flex; align-items: center; } - &:hover { - .tab-focus(); - .text { - color: var(--color-primary); - } - } - .text { + .text{ flex: 1; width: 0; .f-single-line(); } .icon { - display: flex; + display: none; align-items: center; flex-shrink: 0; height: 20px; - margin-left: 2px; + margin-left: 4px; cursor: pointer; i { font-size: 12px; @@ -60,6 +62,13 @@ color: var(--color-primary); } } + &:hover { + .icon { + display: flex; + } + background-color: var(--color-bg-hover); + color: var(--color-primary); + } } .tabItemLine { diff --git a/chat2db-client/src/components/Tabs/index.tsx b/chat2db-client/src/components/Tabs/index.tsx index 7b06098f0..a3c2e6972 100644 --- a/chat2db-client/src/components/Tabs/index.tsx +++ b/chat2db-client/src/components/Tabs/index.tsx @@ -90,9 +90,11 @@ export default memo(function Tab(props) { t.value === editingTab ? { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> : -
+
{t.prefixIcon && } - {t.label} +
+ {t.label} +
}
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 1f304e74a..479d4ef0f 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -13,6 +13,7 @@ import { ITreeNode } from '@/typings'; import { TreeNodeType } from '@/constants'; import styles from './index.less'; import { approximateTreeNode } from '@/utils'; +import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IOption { value: TreeNodeType; @@ -50,12 +51,17 @@ const TableList = dvaModel(function (props: any) { const [curList, setCurList] = useState([]); const [tableLoading, setTableLoading] = useState(false); + useUpdateEffect(() => { + setCurList([]); + getList(); + }, [curType]); + useEffect(() => { setCurList([]); if (isReady) { - getList(); + setCurType({...optionsList[0]}); } - }, [curWorkspaceParams, curType]); + }, [curWorkspaceParams]); 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 04813eb9e..105ce9e52 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -15,13 +15,11 @@ } .tabs { - padding: 8px 4px 0px 4px; - margin: 0px 4px; } .consoleBox { position: absolute; - top: 38px; + top: 32px; left: 0; right: 0; bottom: 0; @@ -29,8 +27,9 @@ } .tabBox { - height: 38px; - background-color: var(--color-bg-elevated); + height: 32px; + background-color: var(--color-bg-base); + border-bottom: 1px solid var(--color-border); } .activeConsoleBox { diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 7ed6e3b92..1ceb9d53a 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -54,7 +54,7 @@ const WorkspaceRightItem = memo(function (props) { if (!doubleClickTreeNodeData) { return; } - if(doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE){ + if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { const { extraParams } = doubleClickTreeNodeData; const { tableName } = extraParams || {}; const ddl = `SELECT * FROM ${tableName};\n`; From 07bef9541840a94f7b9c6b64248f59a6560895ad Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 17:20:56 +0800 Subject: [PATCH 0623/1069] - Fix the issue of 'CLOB' not displaying specific content [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) --- CHANGELOG.md | 10 ++++++++++ .../src/main/java/ai/chat2db/spi/util/JdbcUtils.java | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfa94d374..2b0a5e58b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 2.0.12 + +## 🐞 Bug Fixes + +- Fix the issue of 'CLOB' not displaying specific content [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) + +## 🐞 问题修复 + +- 修复 `CLOB` 不展示具体内容的问题 [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) + # 2.0.11 ## 🐞 Bug Fixes 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 e157eb1b5..f7d132934 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 @@ -151,7 +151,7 @@ public static String getResultSetValue(ResultSet rs, int index) throws SQLExcept return "(BLOB " + blob.length() + ")"; } if (obj instanceof Clob clob) { - return "(CLOB " + clob.length() + ")"; + return clob.getSubString(1, Math.toIntExact(clob.length())); } if (obj instanceof Timestamp timestamp) { return Objects.toString(timestamp); From 3a467d7389d990fa599ea4a3aa8985a7c4d5f9c6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 17:32:39 +0800 Subject: [PATCH 0624/1069] style --- chat2db-client/src/components/Tabs/index.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index d61844814..09316058b 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -3,7 +3,7 @@ .tab-focus() { background-color: var(--color-bg-elevated) !important; // 添加内阴影 - box-shadow: inset 0 -1px 0 0 var(--color-primary); + // box-shadow: inset 0 -1px 0 0 var(--color-primary); border-bottom: 1px solid var(--color-primary); .icon{ display: flex; From 0f30b8937003eb227647a305b809042e4d187224 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 17:36:56 +0800 Subject: [PATCH 0625/1069] support view trigger producer function --- .../java/ai/chat2db/plugin/dm/DMMetaData.java | 114 +++++++++++++++++- .../java/ai/chat2db/plugin/h2/H2Meta.java | 12 +- .../plugin/mariadb/MariaDBMetaData.java | 114 ++++++++++++++++++ 3 files changed, 229 insertions(+), 11 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 cebcf5ffc..009d12e14 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 @@ -2,19 +2,26 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SqlUtils; +import jakarta.validation.constraints.NotEmpty; public class DMMetaData extends DefaultMetaService implements MetaData { public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String selectObjectDDLSQL = String.format( - "select dbms_metadata.get_ddl(%s, %s, %s) AS \"sql\" from dual", - SqlUtils.formatSQLString("TABLE"), SqlUtils.formatSQLString(tableName), - SqlUtils.formatSQLString(schemaName)); - return SQLExecutor.getInstance().executeSql(connection,selectObjectDDLSQL, resultSet -> { + "select dbms_metadata.get_ddl(%s, %s, %s) AS \"sql\" from dual", + SqlUtils.formatSQLString("TABLE"), SqlUtils.formatSQLString(tableName), + SqlUtils.formatSQLString(schemaName)); + return SQLExecutor.getInstance().executeSql(connection, selectObjectDDLSQL, resultSet -> { try { if (resultSet.next()) { return resultSet.getString("sql"); @@ -25,4 +32,103 @@ public String tableDDL(Connection connection, String databaseName, String schema return null; }); } + + private static String ROUTINES_SQL + = "SELECT OWNER, NAME, TEXT FROM ALL_SOURCE WHERE TYPE = '%s' AND NAME = '%s' AND NAME = '%s' ORDER BY LINE"; + + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL, "PROC",schemaName, functionName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + StringBuilder sb = new StringBuilder(); + while (resultSet.next()) { + sb.append(resultSet.getString("TEXT") + "\n"); + } + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(functionName); + function.setFunctionBody(sb.toString()); + return function; + + }); + + } + + @Override + public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, + String procedureName) { + String sql = String.format(ROUTINES_SQL, "PROC", schemaName,procedureName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + StringBuilder sb = new StringBuilder(); + while (resultSet.next()) { + sb.append(resultSet.getString("TEXT") + "\n"); + } + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setProcedureName(procedureName); + procedure.setProcedureBody(sb.toString()); + return procedure; + }); + } + + private static String TRIGGER_SQL + = "SELECT OWNER, TRIGGER_NAME, TABLE_OWNER, TABLE_NAME, TRIGGER_TYPE, TRIGGERING_EVENT, STATUS, TRIGGER_BODY " + + "FROM ALL_TRIGGERS WHERE OWNER = '%s' AND TRIGGER_NAME = '%s'"; + + private static String TRIGGER_SQL_LIST = "SELECT OWNER, TRIGGER_NAME FROM ALL_TRIGGERS WHERE OWNER = '%s'"; + + @Override + public List triggers(Connection connection, String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + String sql = String.format(TRIGGER_SQL_LIST, schemaName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + return triggers; + }); + } + + @Override + public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, + String triggerName) { + + String sql = String.format(TRIGGER_SQL, schemaName, triggerName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setTriggerName(triggerName); + if (resultSet.next()) { + trigger.setTriggerBody(resultSet.getString("TRIGGER_BODY")); + } + return trigger; + }); + } + + private static String VIEW_SQL + = "SELECT OWNER, VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER = '%s' AND VIEW_NAME = '%s'"; + + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, schemaName, viewName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + if (resultSet.next()) { + table.setDdl(resultSet.getString("TEXT")); + } + return table; + }); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java index 8d90de4d2..e063d7920 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java @@ -94,7 +94,7 @@ private String getDDL(Connection connection, String databaseName, String schemaN private static String ROUTINES_SQL = - "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + "SELECT SPECIFIC_NAME, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + "routine_name = '%s';"; @@ -110,7 +110,6 @@ public Function function(Connection connection, @NotEmpty String databaseName, S function.setFunctionName(functionName); if (resultSet.next()) { function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - function.setRemarks(resultSet.getString("ROUTINE_COMMENT")); function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); } @@ -120,16 +119,16 @@ public Function function(Connection connection, @NotEmpty String databaseName, S } private static String TRIGGER_SQL - = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " + = "SELECT TRIGGER_NAME,JAVA_CLASS FROM INFORMATION_SCHEMA.TRIGGERS where " + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; private static String TRIGGER_SQL_LIST - = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; + = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_CATALOG = '%s' AND TRIGGER_SCHEMA = '%s';"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); - String sql = String.format(TRIGGER_SQL_LIST, databaseName); + String sql = String.format(TRIGGER_SQL_LIST, databaseName,schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Trigger trigger = new Trigger(); @@ -153,7 +152,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str trigger.setSchemaName(schemaName); trigger.setTriggerName(triggerName); if (resultSet.next()) { - trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); + trigger.setTriggerBody(resultSet.getString("JAVA_CLASS")); } return trigger; }); @@ -170,7 +169,6 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, procedure.setProcedureName(procedureName); if (resultSet.next()) { procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); } return procedure; diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java index e967240bf..b2c488052 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java @@ -1,7 +1,121 @@ package ai.chat2db.plugin.mariadb; +import java.sql.Connection; +import java.util.ArrayList; +import java.util.List; + import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.model.Function; +import ai.chat2db.spi.model.Procedure; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.sql.SQLExecutor; +import jakarta.validation.constraints.NotEmpty; public class MariaDBMetaData extends DefaultMetaService implements MetaData { + + + private static String ROUTINES_SQL + = + "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + + "routine_name = '%s';"; + + @Override + public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, + String functionName) { + + String sql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + function.setFunctionName(functionName); + if (resultSet.next()) { + function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + function.setRemarks(resultSet.getString("ROUTINE_COMMENT")); + function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); + } + return function; + }); + + } + + private static String TRIGGER_SQL + = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " + + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; + + private static String TRIGGER_SQL_LIST + = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; + + @Override + public List triggers(Connection connection, String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + String sql = String.format(TRIGGER_SQL_LIST, databaseName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + return triggers; + }); + } + + @Override + public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, + String triggerName) { + + String sql = String.format(TRIGGER_SQL, databaseName, triggerName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Trigger trigger = new Trigger(); + trigger.setDatabaseName(databaseName); + trigger.setSchemaName(schemaName); + trigger.setTriggerName(triggerName); + if (resultSet.next()) { + trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); + } + return trigger; + }); + } + + @Override + public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, + String procedureName) { + String sql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setProcedureName(procedureName); + if (resultSet.next()) { + procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); + procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); + procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); + } + return procedure; + }); + } + + private static String VIEW_SQL + = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " + + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; + + @Override + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, databaseName, viewName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + if (resultSet.next()) { + table.setDdl(resultSet.getString("definition")); + } + return table; + }); + } } From 10764491e049f0dce46ee79d83f854f094fa0b29 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 17:39:55 +0800 Subject: [PATCH 0626/1069] =?UTF-8?q?=E5=8E=BB=E9=99=A4tab=20=E6=BB=9A?= =?UTF-8?q?=E5=8A=A8=E6=9D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/components/Tabs/index.less | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 09316058b..e31567621 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -3,7 +3,6 @@ .tab-focus() { background-color: var(--color-bg-elevated) !important; // 添加内阴影 - // box-shadow: inset 0 -1px 0 0 var(--color-primary); border-bottom: 1px solid var(--color-primary); .icon{ display: flex; @@ -18,9 +17,7 @@ .tab { display: flex; overflow: auto; - &:hover{ - &::-webkit-scrollbar {display:block;} - } + &::-webkit-scrollbar {display: none;} } .tabList { From 2c0c484fafdcf8a671eaec9a1ac04b440ecb8a67 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 17:49:13 +0800 Subject: [PATCH 0627/1069] Complete team user code --- .../chat2db/server/domain/core/impl/TeamUserServiceImpl.java | 3 +-- .../server/domain/repository/mapper/TeamUserCustomMapper.java | 3 ++- .../src/main/resources/mapper/DataSourceAccessCustomMapper.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java index a8ae4c2b1..09c53b2e9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java @@ -52,8 +52,7 @@ public PageResult comprehensivePageQuery(TeamUserComprehensivePageQuer Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = teamUserCustomMapper.comprehensivePageQuery(page, param.getTeamId(), - param.getUserId(), - param.getTeamRoleCode()); + param.getUserId(), param.getTeamRoleCode(),param.getTeamSearchKey(),param.getUserSearchKey()); List list = teamUserConverter.do2dto(iPage.getRecords()); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java index db758e397..dd77f43a1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java @@ -13,5 +13,6 @@ public interface TeamUserCustomMapper extends Mapper { IPage comprehensivePageQuery(IPage page, @Param("teamId") Long teamId, - @Param("userId") Long userId, @Param("teamRoleCode") String teamRoleCode); + @Param("userId") Long userId, @Param("teamRoleCode") String teamRoleCode, + @Param("teamSearchKey") String teamSearchKey, @Param("userSearchKey") String userSearchKey); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml index 22c8c62f6..0a64944b0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml @@ -15,7 +15,7 @@ and dsa.DATA_SOURCE_ID = #{dataSourceId} - + and (t.CODE like concat('%',#{userOrTeamSearchKey},'%') or t.NAME like concat('%',#{userOrTeamSearchKey},'%') or du.USER_NAME like concat('%',#{userOrTeamSearchKey},'%') or du.NICK_NAME like concat('%',#{userOrTeamSearchKey},'%') or du.EMAIL like concat('%',#{userOrTeamSearchKey},'%')) From 6343ae70779e59c354face796b9d50c1a1402c27 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 17:58:49 +0800 Subject: [PATCH 0628/1069] =?UTF-8?q?style:=20tab=E9=A2=9C=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/SearchResult/index.less | 2 +- chat2db-client/src/components/Tabs/index.less | 48 +++++++++++-------- chat2db-client/src/layouts/index.tsx | 3 +- chat2db-client/src/utils/check.ts | 4 -- 4 files changed, 31 insertions(+), 26 deletions(-) delete mode 100644 chat2db-client/src/utils/check.ts diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 3a6bc3fda..6007a1a3f 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -18,7 +18,7 @@ .resultHeader { flex-shrink: 0; overflow-x: scroll; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-base); &::-webkit-scrollbar { display: none; diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index e31567621..61f44f680 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -1,23 +1,28 @@ @import '../../styles/var.less'; .tab-focus() { - background-color: var(--color-bg-elevated) !important; + background-color: var(--color-bg-elevated); // 添加内阴影 border-bottom: 1px solid var(--color-primary); - .icon{ + .icon { display: flex; } } .tab-focus-line() { color: var(--color-primary); - background-color: var(--color-bg-base); + background-color: var(--color-bg-elevated); + .icon { + display: flex; + } } .tab { display: flex; overflow: auto; - &::-webkit-scrollbar {display: none;} + &::-webkit-scrollbar { + display: none; + } } .tabList { @@ -35,12 +40,12 @@ cursor: pointer; user-select: none; border-right: 1px solid var(--color-border); - .textBox{ + .textBox { flex: 1; display: flex; align-items: center; } - .text{ + .text { flex: 1; width: 0; .f-single-line(); @@ -60,10 +65,7 @@ } } &:hover { - .icon { - display: flex; - } - background-color: var(--color-bg-hover); + .tab-focus(); color: var(--color-primary); } } @@ -78,11 +80,15 @@ width: 100px; cursor: pointer; border-right: 1px solid var(--color-border); - &:hover { - .tab-focus-line(); - .text { - color: var(--color-primary); - } + .textBox { + flex: 1; + display: flex; + align-items: center; + } + .text { + flex: 1; + width: 0; + .f-single-line(); } .text { flex: 1; @@ -90,7 +96,7 @@ .f-single-line(); } .icon { - display: flex; + display: none; align-items: center; flex-shrink: 0; height: 20px; @@ -103,6 +109,10 @@ color: var(--color-primary); } } + &:hover { + .tab-focus-line(); + color: var(--color-primary); + } } .activeTab { @@ -129,7 +139,7 @@ } } -.input{ +.input { border: 0; width: 86px; flex: 1; @@ -137,12 +147,12 @@ outline: none; font-size: 12px; font-weight: 400; - input:focus{ + input:focus { outline: none; } } -.prefixIcon{ +.prefixIcon { flex-shrink: 0; margin-right: 4px; } diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index ea8ea78af..0589a9285 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -7,11 +7,10 @@ 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 { isEn } from '@/utils/check'; +import { isEn } from '@/i18n'; import { ThemeType, PrimaryColorType, LangType } from '@/constants/'; import { InjectThemeVar } from '@/theme'; import styles from './index.less'; diff --git a/chat2db-client/src/utils/check.ts b/chat2db-client/src/utils/check.ts deleted file mode 100644 index f003c34ca..000000000 --- a/chat2db-client/src/utils/check.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { getLang } from './localStorage'; - -export const isEn = getLang() === 'en-us'; -export const isZH = getLang() === 'zh-cn'; \ No newline at end of file From 61b1b620487a60564cc194b6543809198442c31d Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 18:04:32 +0800 Subject: [PATCH 0629/1069] Modifying clob returns --- .../src/main/java/ai/chat2db/spi/util/JdbcUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f7d132934..c26b14b81 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 @@ -148,7 +148,7 @@ public static String getResultSetValue(ResultSet rs, int index) throws SQLExcept } if (obj instanceof Blob blob) { - return "(BLOB " + blob.length() + ")"; + return rs.getString(index); } if (obj instanceof Clob clob) { return clob.getSubString(1, Math.toIntExact(clob.length())); From 523432dbe0584f61b0cdcad543f3355d433d6a6e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 19:37:49 +0800 Subject: [PATCH 0630/1069] fix:bug --- .../src/components/Console/index.tsx | 2 +- chat2db-client/src/constants/database.ts | 28 +++++++++---------- chat2db-client/src/i18n/zh-cn/workspace.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index f7c5d2283..698d86082 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -471,7 +471,7 @@ function Console(props: IProps) { type="text" onClick={() => { // 格式化sql - const sql = editorRef?.current?.getCurrentSelectContent() || '' + const sql = editorRef?.current?.getCurrentSelectContent() || editorRef?.current?.getAllContent() || '' formatSql(sql, executeParams.type!).then((res) => { editorRef?.current?.setValue(res, 'select'); }); diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index bd3c12247..1ad935545 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -118,20 +118,20 @@ export const databaseMap: { // port: 54321, icon: '\ue6a0', }, - [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', - }, + // [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) => { diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 0bca87de9..c5893625c 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -17,6 +17,6 @@ export default { 'workspace.tree.view': '视图', 'workspace.tree.trigger': '触发器', 'workspace.tree.function': '函数', - 'workspace.tree.procedure': '过程', + 'workspace.tree.procedure': '过程过程', }; From 1f2786565e4454d6d2804e30f184b6b8289ec5a1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 19:38:16 +0800 Subject: [PATCH 0631/1069] fix:bug --- chat2db-client/src/i18n/zh-cn/workspace.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index c5893625c..9358dc2c3 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -17,6 +17,6 @@ export default { 'workspace.tree.view': '视图', 'workspace.tree.trigger': '触发器', 'workspace.tree.function': '函数', - 'workspace.tree.procedure': '过程过程', + 'workspace.tree.procedure': '存储过程', }; From be906a8570bc52a0c7be6680ae42e9e5dcb7eff6 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 19:40:57 +0800 Subject: [PATCH 0632/1069] support view trigger producer function --- CHANGELOG.md | 11 +++++- .../server/domain/core/util/H2Functions.java | 7 ++++ .../server/domain/core/util/H2Triggers.java | 36 +++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Functions.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Triggers.java diff --git a/CHANGELOG.md b/CHANGELOG.md index efa282921..1c48c259e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.0.12 +# 2.0.13 ## ⭐ New Features @@ -12,6 +12,12 @@ - Fixed an issue where locally stored theme colors and background colors are incompatible with the new version, causing page crashes - Logs desensitize sensitive data - Fix the issue of 'CLOB' not displaying specific content [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) +- Fix the problem that non-Select does not display query results +- Fix the problem that Oracle cannot query without schema +- Fix the problem of special type of SQL execution error reporting + + + ## ⭐ 新特性 @@ -25,6 +31,9 @@ - 修复本地存储的主题色、背景色与新版本不兼容时会导致页面崩溃问题 - 日志对敏感数据进行脱敏 - 修复 `CLOB` 不展示具体内容的问题 [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) +- 修复非Select不展示查询结果的问题 +- 修复Oracle不带schema无法查询的问题 +- 修复特殊类型的SQL执行报错的问题 # 2.0.11 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Functions.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Functions.java new file mode 100644 index 000000000..25ec0dd3c --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Functions.java @@ -0,0 +1,7 @@ +package ai.chat2db.server.domain.core.util; + +public class H2Functions { + public static String keyGeneratorFunction(String tableName) { + return null; + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Triggers.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Triggers.java new file mode 100644 index 000000000..d2c5dd3d4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/H2Triggers.java @@ -0,0 +1,36 @@ +package ai.chat2db.server.domain.core.util; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Arrays; + +import org.h2.api.Trigger; + +public class H2Triggers implements Trigger { + + @Override + public void init(Connection conn, String schemaName, String triggerName, + String tableName, boolean before, int type) throws SQLException { + // Initialization logic, if needed. + } + + @Override + public void fire(Connection conn, Object[] oldRow, Object[] newRow) + throws SQLException { + // This method is called when the trigger is executed. + // In this example, let's simply print the new values when a row is inserted. + if (newRow != null) { + System.out.println("New Row Inserted: " + Arrays.toString(newRow)); + } + } + + @Override + public void close() throws SQLException { + // Cleanup logic, if needed. + } + + @Override + public void remove() throws SQLException { + // Logic to execute when the trigger is dropped/removed. + } +} From 338db87ea1f62dab8107144a2b195c18293980ce Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 13 Aug 2023 19:42:00 +0800 Subject: [PATCH 0633/1069] Complete team user code --- ...urceAccessComprehensivePageQueryParam.java | 15 ++++++- .../converter/DataSourceAccessConverter.java | 2 + .../core/converter/DataSourceConverter.java | 44 +++++++++++++++++-- .../core/converter/TeamUserConverter.java | 12 +++++ .../impl/DataSourceAccessServiceImpl.java | 15 ++++++- .../domain/core/impl/TeamUserServiceImpl.java | 4 +- .../mapper/DataSourceAccessCustomMapper.java | 3 +- .../mapper/DataSourceAccessCustomMapper.xml | 15 +++++-- .../user/UserDataSourceAdminController.java | 6 +-- .../user/UserTeamAdminController.java | 4 +- .../UserDataSourcesAdminConverter.java | 9 ++-- .../converter/UserTeamAdminConverter.java | 4 +- .../UserTeamPageCommonQueryRequest.java | 25 +++++++++++ .../request/CommonPageQueryRequest.java | 1 + 14 files changed, 136 insertions(+), 23 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamPageCommonQueryRequest.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java index 78c5cd4f9..b3c94cae4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessComprehensivePageQueryParam.java @@ -1,7 +1,7 @@ package ai.chat2db.server.domain.api.param.datasource.access; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -14,9 +14,20 @@ public class DataSourceAccessComprehensivePageQueryParam extends PageQueryParam /** * 数据源id */ - @NotNull private Long dataSourceId; + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long accessObjectId; + /** * Query keywords for users or teams */ diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java index 65bae69f2..31b26833b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceAccessConverter.java @@ -28,6 +28,7 @@ public abstract class DataSourceAccessConverter { @Mappings({ @Mapping(target = "accessObject.id", source = "accessObjectId"), @Mapping(target = "accessObject.type", source = "accessObjectType"), + @Mapping(target = "dataSource.id", source = "dataSourceId"), }) public abstract DataSourceAccess do2dto(DataSourceAccessDO data); @@ -60,6 +61,7 @@ public abstract DataSourceAccessDO param2do(Long dataSourceId, Long accessObject }) public abstract DataSourceAccessDO param2do(DataSourceAccessCreatParam param, Long userId); + /** * convert * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java index e005329fa..be911e61e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java @@ -1,22 +1,28 @@ package ai.chat2db.server.domain.core.converter; import java.util.List; +import java.util.Map; import ai.chat2db.server.domain.api.model.DataSource; -import ai.chat2db.server.domain.api.param.datasource.DataSourceTestParam; -import ai.chat2db.server.domain.core.util.DesUtil; -import ai.chat2db.server.domain.api.param.ConsoleCreateParam; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; +import ai.chat2db.server.domain.api.param.ConsoleCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourceTestParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; +import ai.chat2db.server.domain.api.service.DataSourceService; +import ai.chat2db.server.domain.core.util.DesUtil; import ai.chat2db.server.domain.repository.entity.DataSourceDO; - +import ai.chat2db.server.tools.common.util.EasyCollectionUtils; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; +import org.springframework.context.annotation.Lazy; /** * @author moji @@ -27,6 +33,10 @@ @Mapper(componentModel = "spring") public abstract class DataSourceConverter { + @Resource + @Lazy + private DataSourceService dataSourceService; + /** * 参数转换 * @@ -178,4 +188,30 @@ public abstract DataSourceTestParam param2param( * @return */ public abstract List do2dto(List dataSourceDOList); + + /** + * Fill in detailed information + * + * @param list + */ + public void fillDetail(List list) { + if (CollectionUtils.isEmpty(list)) { + return; + } + List idList = EasyCollectionUtils.toList(list, DataSource::getId); + List queryList = dataSourceService.queryByIds(idList).getData(); + Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, DataSource::getId); + for (DataSource data : list) { + if (data == null || data.getId() == null) { + continue; + } + DataSource query = queryMap.get(data.getId()); + add(data, query); + } + } + + @Mappings({ + @Mapping(target = "id", ignore = true), + }) + public abstract void add(@MappingTarget DataSource target, DataSource source); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java index b4666d454..87ec85b0a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamUserConverter.java @@ -31,6 +31,18 @@ public abstract class TeamUserConverter { @Lazy private EnvironmentService environmentService; + /** + * convert + * + * @param data + * @return + */ + @Mappings({ + @Mapping(target = "team.id", source = "teamId"), + @Mapping(target = "user.id", source = "userId"), + }) + public abstract TeamUser do2dto(TeamUserDO data); + /** * convert * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java index e947595ff..51da26b0d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java @@ -15,6 +15,7 @@ import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.converter.DataSourceAccessConverter; +import ai.chat2db.server.domain.core.converter.DataSourceConverter; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; @@ -48,6 +49,8 @@ public class DataSourceAccessServiceImpl implements DataSourceAccessService { @Resource private DataSourceAccessConverter dataSourceAccessConverter; @Resource + private DataSourceConverter dataSourceConverter; + @Resource private UserService userService; @Resource private TeamService teamService; @@ -58,7 +61,8 @@ public PageResult comprehensivePageQuery(DataSourceAccessCompr Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = dataSourceAccessCustomMapper.comprehensivePageQuery(page, - param.getDataSourceId(), param.getUserOrTeamSearchKey(), + param.getDataSourceId(), param.getAccessObjectType(), param.getAccessObjectId(), + param.getUserOrTeamSearchKey(), param.getDataSourceSearchKey()); List list = dataSourceAccessConverter.do2dto(iPage.getRecords()); @@ -88,6 +92,15 @@ private void fillData(List list, DataSourceAccessSelector sele } fillAccessObject(list, selector); + + fillDataSource(list, selector); + } + + private void fillDataSource(List list, DataSourceAccessSelector selector) { + if (BooleanUtils.isNotTrue(selector.getDataSource())) { + return; + } + dataSourceConverter.fillDetail(EasyCollectionUtils.toList(list, DataSourceAccess::getDataSource)); } private void fillAccessObject(List list, DataSourceAccessSelector selector) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java index 09c53b2e9..8007aa47d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java @@ -52,7 +52,7 @@ public PageResult comprehensivePageQuery(TeamUserComprehensivePageQuer Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = teamUserCustomMapper.comprehensivePageQuery(page, param.getTeamId(), - param.getUserId(), param.getTeamRoleCode(),param.getTeamSearchKey(),param.getUserSearchKey()); + param.getUserId(), param.getTeamRoleCode(), param.getTeamSearchKey(), param.getUserSearchKey()); List list = teamUserConverter.do2dto(iPage.getRecords()); @@ -93,7 +93,7 @@ private void fillUser(List list, TeamUserSelector selector) { } private void fillTeam(List list, TeamUserSelector selector) { - if (BooleanUtils.isNotTrue(selector.getUser())) { + if (BooleanUtils.isNotTrue(selector.getTeam())) { return; } teamConverter.fillDetail(EasyCollectionUtils.toList(list, TeamUser::getTeam)); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java index 42b821c93..7daa7b463 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java @@ -13,7 +13,8 @@ public interface DataSourceAccessCustomMapper extends Mapper { IPage comprehensivePageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, + @Param("accessObjectType") String accessObjectType, + @Param("accessObjectId") Long accessObjectId, @Param("userOrTeamSearchKey") String userOrTeamSearchKey, @Param("dataSourceSearchKey") String dataSourceSearchKey); - } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml index 0a64944b0..cdacb8644 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml @@ -15,13 +15,22 @@ and dsa.DATA_SOURCE_ID = #{dataSourceId} + + and dsa.ACCESS_OBJECT_ID = #{accessObjectId} + + + and dsa.ACCESS_OBJECT_TYPE = #{accessObjectType} + - and (t.CODE like concat('%',#{userOrTeamSearchKey},'%') or t.NAME like concat('%',#{userOrTeamSearchKey},'%') or - du.USER_NAME like concat('%',#{userOrTeamSearchKey},'%') or du.NICK_NAME like concat('%',#{userOrTeamSearchKey},'%') or + and (t.CODE like concat('%',#{userOrTeamSearchKey},'%') or t.NAME like + concat('%',#{userOrTeamSearchKey},'%') or + du.USER_NAME like concat('%',#{userOrTeamSearchKey},'%') or du.NICK_NAME like + concat('%',#{userOrTeamSearchKey},'%') or du.EMAIL like concat('%',#{userOrTeamSearchKey},'%')) - and (ds.ALIAS like concat('%',#{dataSourceSearchKey},'%') or ds.URL like concat('%',#{dataSourceSearchKey},'%')) + and (ds.ALIAS like concat('%',#{dataSourceSearchKey},'%') or ds.URL like + concat('%',#{dataSourceSearchKey},'%')) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java index 473f60b96..8fe29cc3f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java @@ -3,8 +3,8 @@ import ai.chat2db.server.admin.api.controller.user.converter.UserDataSourcesAdminConverter; import ai.chat2db.server.admin.api.controller.user.request.UserDataSourceBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserTeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; @@ -30,7 +30,7 @@ @RestController public class UserDataSourceAdminController { private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() - .accessObject(Boolean.TRUE) + .dataSource(Boolean.TRUE) .build(); @Resource @@ -46,7 +46,7 @@ public class UserDataSourceAdminController { * @version 2.1.0 */ @GetMapping("/page") - public WebPageResult page(@Valid CommonPageQueryRequest request) { + public WebPageResult page(@Valid UserTeamPageCommonQueryRequest request) { return dataSourceAccessService.comprehensivePageQuery(userDataSourcesAdminConverter.request2param(request), DATA_SOURCE_ACCESS_SELECTOR) .mapToWeb(userDataSourcesAdminConverter::dto2vo); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java index 9888f3cfa..56860f171 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java @@ -2,9 +2,9 @@ package ai.chat2db.server.admin.api.controller.user; import ai.chat2db.server.admin.api.controller.user.converter.UserTeamAdminConverter; +import ai.chat2db.server.admin.api.controller.user.request.UserTeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; @@ -44,7 +44,7 @@ public class UserTeamAdminController { * @version 2.1.0 */ @GetMapping("/page") - public WebPageResult page(@Valid CommonPageQueryRequest request) { + public WebPageResult page(@Valid UserTeamPageCommonQueryRequest request) { return teamUserService.comprehensivePageQuery(userTeamAdminConverter.request2param(request), TEAM_USER_SELECTOR) .mapToWeb(userTeamAdminConverter::dto2vo); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java index 6e2eaf36f..0bc8df59c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java @@ -1,8 +1,9 @@ package ai.chat2db.server.admin.api.controller.user.converter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserTeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; @@ -16,7 +17,7 @@ * * @author Jiaju Zhuang */ -@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) public abstract class UserDataSourcesAdminConverter { /** @@ -26,10 +27,12 @@ public abstract class UserDataSourcesAdminConverter { * @return */ @Mappings({ + @Mapping(source = "userId", target = "accessObjectId"), + @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.USER.name())"), @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) - public abstract DataSourceAccessComprehensivePageQueryParam request2param(CommonPageQueryRequest request); + public abstract DataSourceAccessComprehensivePageQueryParam request2param(UserTeamPageCommonQueryRequest request); /** * convert diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java index 41a0e1399..1e6415f18 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java @@ -1,7 +1,7 @@ package ai.chat2db.server.admin.api.controller.user.converter; +import ai.chat2db.server.admin.api.controller.user.request.UserTeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import org.mapstruct.Mapper; @@ -26,7 +26,7 @@ public abstract class UserTeamAdminConverter { @Mapping(source = "searchKey", target = "teamSearchKey"), @Mapping(target = "enableReturnCount", expression = "java(true)"), }) - public abstract TeamUserComprehensivePageQueryParam request2param(CommonPageQueryRequest request); + public abstract TeamUserComprehensivePageQueryParam request2param(UserTeamPageCommonQueryRequest request); /** * conversion diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamPageCommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamPageCommonQueryRequest.java new file mode 100644 index 000000000..45a4266dc --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamPageCommonQueryRequest.java @@ -0,0 +1,25 @@ + +package ai.chat2db.server.admin.api.controller.user.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserTeamPageCommonQueryRequest extends PageQueryRequest { + /** + * user id + */ + @NotNull + private Long userId; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java index 187e81a65..87c58b294 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java @@ -12,6 +12,7 @@ @Data public class CommonPageQueryRequest extends PageQueryRequest { + /** * searchKey */ From 3f7729032bb804800e6ff422ae361c5b6d7f8717 Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 19:46:22 +0800 Subject: [PATCH 0634/1069] support view trigger producer function --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c48c259e..cb0de7931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Fix the problem that non-Select does not display query results - Fix the problem that Oracle cannot query without schema - Fix the problem of special type of SQL execution error reporting +- Fix the problem that the test link is successful, but the error is reported when saving the link @@ -34,6 +35,7 @@ - 修复非Select不展示查询结果的问题 - 修复Oracle不带schema无法查询的问题 - 修复特殊类型的SQL执行报错的问题 +- 修复测试链接成功,但保存链接报错的问题 # 2.0.11 From 3619af7fa7cfd500cfe039540fa04149e5d755de Mon Sep 17 00:00:00 2001 From: jipengfei-jpf <1558143046@qq.com> Date: Sun, 13 Aug 2023 19:56:06 +0800 Subject: [PATCH 0635/1069] support view trigger producer function --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb0de7931..74be1c2a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 2.0.13 +# 2.0.12 ## ⭐ New Features From f67300c3741dd665b5052818074c0018c2486b6c Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 13 Aug 2023 19:56:40 +0800 Subject: [PATCH 0636/1069] =?UTF-8?q?=E5=88=A0=E9=99=A4Redis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/constants/database.ts | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/chat2db-client/src/constants/database.ts b/chat2db-client/src/constants/database.ts index 1ad935545..8afa48045 100644 --- a/chat2db-client/src/constants/database.ts +++ b/chat2db-client/src/constants/database.ts @@ -97,13 +97,13 @@ export const databaseMap: { // port: 2883, icon: '\ue982', }, - [DatabaseTypeCode.REDIS]: { - name: 'Redis', - img: moreDBLogo, - code: DatabaseTypeCode.REDIS, - // port: 6379, - icon: '\ue6a2', - }, + // [DatabaseTypeCode.REDIS]: { + // name: 'Redis', + // img: moreDBLogo, + // code: DatabaseTypeCode.REDIS, + // // port: 6379, + // icon: '\ue6a2', + // }, [DatabaseTypeCode.HIVE]: { name: 'Hive', img: moreDBLogo, @@ -125,13 +125,6 @@ export const databaseMap: { // // 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 6a90a585b7eeab9ad0677f5e00a82c91f3b63c04 Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Thu, 17 Aug 2023 11:37:05 +0800 Subject: [PATCH 0637/1069] Update README_CN.md --- README_CN.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README_CN.md b/README_CN.md index 110885dc7..1647a09f8 100644 --- a/README_CN.md +++ b/README_CN.md @@ -29,6 +29,9 @@ Languages: 中文 [English](README.md)
+如果觉得 Chat2DB 对您有帮助的话,请帮忙
github star +的右上角点个⭐ Star 和 Fork,您的支持是 Chat2DB 变得更好最大的动力 + ## 案例视频 https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d202e505f From 505c6a4980f95a9ca68c81856b26104b06254e3a Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Thu, 17 Aug 2023 11:39:05 +0800 Subject: [PATCH 0638/1069] Update README_CN.md --- README_CN.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README_CN.md b/README_CN.md index 1647a09f8..97c0ad117 100644 --- a/README_CN.md +++ b/README_CN.md @@ -12,21 +12,7 @@ [![GitHub Contributors](https://img.shields.io/github/contributors/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/graphs/contributors)
- -
-

分享 Chat2DB

-

- -Share on Telegram - -Share on Reddit - -

- -**许可说明**: Chat2DB 开源内容仅供个人免费使用,如想将该项目用于商业用途,请先联系该项目作者。 - Languages: 中文 [English](README.md) -
如果觉得 Chat2DB 对您有帮助的话,请帮忙github star From 00521e196c8df31b5623cd0076fd948f2f933f23 Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Thu, 17 Aug 2023 11:39:56 +0800 Subject: [PATCH 0639/1069] Update README_CN.md --- README_CN.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README_CN.md b/README_CN.md index 97c0ad117..8b1d0c9e2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -12,6 +12,8 @@ [![GitHub Contributors](https://img.shields.io/github/contributors/chat2db/Chat2DB)](https://github.com/chat2db/Chat2DB/graphs/contributors) + +
Languages: 中文 [English](README.md)
From 8ea1cb430c9241b3dad1c768e90dbcb3980bf44e Mon Sep 17 00:00:00 2001 From: jj <1558143046@qq.com> Date: Thu, 17 Aug 2023 11:43:20 +0800 Subject: [PATCH 0640/1069] Update README_CN.md --- README_CN.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README_CN.md b/README_CN.md index 8b1d0c9e2..749357b51 100644 --- a/README_CN.md +++ b/README_CN.md @@ -14,11 +14,14 @@
-Languages: 中文 [English](README.md) -
+ Languages: 中文 [English](README.md) + 如果觉得 Chat2DB 对您有帮助的话,请帮忙github star 的右上角点个⭐ Star 和 Fork,您的支持是 Chat2DB 变得更好最大的动力 + + + ## 案例视频 From cab05c2ce8cf4ee9c5eed98ad6df56497d03a4b7 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 18 Aug 2023 21:29:57 +0800 Subject: [PATCH 0641/1069] feat: Desktop add google measurement protocol --- chat2db-client/.gitignore | 3 +- chat2db-client/package.json | 8 ++- chat2db-client/src/main/analysis.js | 16 +++++ chat2db-client/src/main/ga4.js | 94 +++++++++++++++++++++++++++++ chat2db-client/src/main/index.js | 2 + chat2db-client/yarn.lock | 10 +++ 6 files changed, 130 insertions(+), 3 deletions(-) create mode 100644 chat2db-client/src/main/analysis.js create mode 100644 chat2db-client/src/main/ga4.js diff --git a/chat2db-client/.gitignore b/chat2db-client/.gitignore index b96b20b77..c8122000c 100644 --- a/chat2db-client/.gitignore +++ b/chat2db-client/.gitignore @@ -10,4 +10,5 @@ /release -/static \ No newline at end of file +/static +/versions \ No newline at end of file diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 9297ff704..34a501337 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -31,12 +31,14 @@ "copy-to-clipboard": "^3.3.3", "echarts": "^5.4.2", "echarts-for-react": "^3.0.2", + "electron-log": "^4.4.8", "event-source-polyfill": "^1.0.31", "lodash": "^4.17.21", "markdown-it-link-attributes": "^4.0.1", "monaco-editor": "^0.34.0", "monaco-editor-esm-webpack-plugin": "^2.1.0", "monaco-editor-webpack-plugin": "^7.0.1", + "node-machine-id": "^1.1.12", "react-sortablejs": "^6.1.4", "sql-formatter": "^12.2.1", "styled-components": "^6.0.1", @@ -79,11 +81,13 @@ "asar": false, "files": [ "dist/**/*", - "!node_modules", "static/", "src/main", "versions/**/*", - "package.json" + "package.json", + "!node_modules/**/*", + "node_modules/uuid", + "node_modules/node-machine-id" ], "nsis": { "oneClick": false, diff --git a/chat2db-client/src/main/analysis.js b/chat2db-client/src/main/analysis.js new file mode 100644 index 000000000..50352c98e --- /dev/null +++ b/chat2db-client/src/main/analysis.js @@ -0,0 +1,16 @@ +const os = require('os'); +const { readVersion } = require('./utils'); +const Analytics4 = require('./ga4'); +const log = require('electron-log'); + +function registerAnalytics() { + const analytics = new Analytics4('G-V8M4E5SF61', 'LShbzC_vRka5Sw5AWco7Tw'); + const customParams = { + platform: 'DESKTOP', + version: readVersion(), + os: os.platform(), + }; + analytics.setParams(customParams).event('first_enter'); +} + +module.exports = registerAnalytics; diff --git a/chat2db-client/src/main/ga4.js b/chat2db-client/src/main/ga4.js new file mode 100644 index 000000000..305697d6a --- /dev/null +++ b/chat2db-client/src/main/ga4.js @@ -0,0 +1,94 @@ +const { net } = require('electron'); +const { v4: uuidv4 } = require('uuid'); +const { machineIdSync } = require('node-machine-id'); +const log = require('electron-log'); + +class Analytics4 { + constructor(trackingID, secretKey, clientID = machineIdSync(), sessionID = uuidv4()) { + this.trackingID = trackingID; + this.secretKey = secretKey; + this.clientID = clientID; + this.sessionID = sessionID; + this.customParams = {}; + this.userProperties = null; + this.baseURL = 'https://google-analytics.com/mp'; + this.collectURL = '/collect'; + } + + set(key, value) { + if (value !== null) { + this.customParams[key] = value; + } else { + delete this.customParams[key]; + } + return this; + } + + setParams(params) { + if (typeof params === 'object' && Object.keys(params).length > 0) { + Object.assign(this.customParams, params); + } else { + this.customParams = {}; + } + return this; + } + + setUserProperties(upValue) { + if (typeof upValue === 'object' && Object.keys(upValue).length > 0) { + this.userProperties = upValue; + } else { + this.userProperties = null; + } + return this; + } + + event(eventName) { + const payload = { + client_id: this.clientID, + events: [ + { + name: eventName, + params: { + session_id: this.sessionID, + ...this.customParams, + }, + }, + ], + }; + + if (this.userProperties) { + Object.assign(payload, { user_properties: this.userProperties }); + } + + const url = `${this.baseURL}${this.collectURL}?measurement_id=${this.trackingID}&api_secret=${this.secretKey}`; + const request = net.request({ + method: 'POST', + url, + }); + + request.on('response', (response) => { + let responseData = ''; + response.on('data', (chunk) => { + responseData += chunk; + }); + + response.on('end', () => { + if (response.statusCode >= 200 && response.statusCode < 300) { + log.info('success', responseData); + } else { + log.error('response error', response.statusCode); + } + }); + }); + + request.on('error', (error) => { + log.error('Error posting data:', error); + }); + + request.write(JSON.stringify(payload)); + + request.end(); + } +} + +module.exports = Analytics4; diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index ff82e78f4..ad5ba65e2 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -4,6 +4,7 @@ const path = require('path'); const os = require('os'); const fs = require('fs'); const registerAppMenu = require('./menu'); +const registerAnalysis = require('./analysis'); const i18n = require('./i18n'); const { loadMainResource } = require('./utils'); @@ -52,6 +53,7 @@ app.commandLine.appendSwitch('--disable-gpu-sandbox'); app.on('ready', () => { createWindow(); registerAppMenu(); + registerAnalysis(); app.on('activate', function () { if (mainWindow === null) { diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 92f77a006..ad1eec950 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -4695,6 +4695,11 @@ electron-localshortcut@^3.1.0: keyboardevent-from-electron-accelerator "^2.0.0" keyboardevents-areequal "^0.2.1" +electron-log@^4.4.8: + version "4.4.8" + resolved "https://registry.npmmirror.com/electron-log/-/electron-log-4.4.8.tgz#fcb9f714dbcaefb6ac7984c4683912c74730248a" + integrity sha512-QQ4GvrXO+HkgqqEOYbi+DHL7hj5JM+nHi/j+qrN9zeeXVKy8ZABgbu4CnG+BBqDZ2+tbeq9tUC4DZfIWFU5AZA== + electron-osx-sign@^0.6.0: version "0.6.0" resolved "https://registry.npmmirror.com/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz#9b69c191d471d9458ef5b1e4fdd52baa059f1bb8" @@ -6944,6 +6949,11 @@ node-libs-browser@2.2.1: util "^0.11.0" vm-browserify "^1.0.1" +node-machine-id@^1.1.12: + version "1.1.12" + resolved "https://registry.npmmirror.com/node-machine-id/-/node-machine-id-1.1.12.tgz#37904eee1e59b320bb9c5d6c0a59f3b469cb6267" + integrity sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ== + node-releases@^2.0.12: version "2.0.13" resolved "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" From 391ea678b98c290563b513800172f63bc0efcf98 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 18 Aug 2023 21:48:07 +0800 Subject: [PATCH 0642/1069] fix: Fixed special field issues in tables --- chat2db-client/package.json | 1 + .../src/components/SearchResult/TableBox/index.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 34a501337..c07b110c8 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -87,6 +87,7 @@ "package.json", "!node_modules/**/*", "node_modules/uuid", + "node_modules/electron-log", "node_modules/node-machine-id" ], "nsis": { diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index dba8da5ae..cd8a63c98 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -48,6 +48,8 @@ const SupportBaseTable: any = styled(BaseTable)` } `; +const preCode = '$$chat2db_'; + export default function TableBox(props: ITableProps) { const { className, data, config, onConfigChange, onSearchTotal } = props; const { headerList, dataList, duration, description, sqlType } = data || {}; @@ -128,7 +130,7 @@ export default function TableBox(props: ITableProps) { const isNumericalOrder = dataType === TableDataType.CHAT2DB_ROW_NUMBER; if (isNumericalOrder) { return { - code: 'No.', + code: `${preCode}No.`, name: 'No.', key: name, lock: true, @@ -144,7 +146,7 @@ export default function TableBox(props: ITableProps) { }; } return { - code: name, + code: `${preCode}${name}`, name: name, key: name, width: 120, @@ -174,12 +176,13 @@ export default function TableBox(props: ITableProps) { const rowData: any = {}; item.map((i: string | null, index: number) => { const { dataType: type } = headerList[index] || {}; + const name = `${preCode}${columns[index].name}`; if (type === TableDataType.DATETIME && i) { - rowData[columns[index].name] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); + rowData[name] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); } else if (i === null) { - rowData[columns[index].name] = ''; + rowData[name] = ''; } else { - rowData[columns[index].name] = i; + rowData[name] = i; } }); return rowData; From f54a9fa70c3d018d9110c622d2162c9c3a7387f6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 19 Aug 2023 10:54:25 +0800 Subject: [PATCH 0643/1069] =?UTF-8?q?fix:=20=E4=B8=8D=E9=80=89=E4=B8=ADsql?= =?UTF-8?q?=E7=82=B9=E5=87=BB=E6=A0=BC=E5=BC=8F=E5=8C=96bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Console/index.tsx | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 698d86082..ba927760d 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -402,6 +402,21 @@ function Console(props: IProps) { setPopularizeModal(true); }; + /** + * 格式化sql + */ + const handelSQLFormat = () => { + let setValueType = 'select'; + let sql = editorRef?.current?.getCurrentSelectContent(); + if (!sql) { + sql = editorRef?.current?.getAllContent() || ''; + setValueType = 'cover'; + } + formatSql(sql, executeParams.type!).then(res => { + editorRef?.current?.setValue(res, setValueType); + }); + }; + return (
@@ -467,16 +482,7 @@ function Console(props: IProps) { )}
- From 5a1e98c826428316b14947c394ba1a8910fec0eb Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 19 Aug 2023 11:02:47 +0800 Subject: [PATCH 0644/1069] =?UTF-8?q?=E6=9A=82=E4=B8=8D=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E9=9D=9E=E5=85=B3=E7=B3=BB=E6=80=A7=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=20=E8=A7=A3=E5=86=B3=E7=94=A8=E6=88=B7=E5=B7=B2=E6=9C=89?= =?UTF-8?q?=E9=9D=9E=E6=83=AF=E6=80=A7=E6=95=B0=E6=8D=AE=E5=BA=93=20?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E8=AE=B0=E5=BD=95=E6=97=B6=E6=89=93=E5=BC=80?= =?UTF-8?q?=E4=BC=9A=E7=99=BD=E5=B1=8F=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/.vscode/settings.json | 3 +- .../CreateConnection/config/dataSource.ts | 480 +++++++++--------- 2 files changed, 242 insertions(+), 241 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 75c08e7a4..18101fc29 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -30,6 +30,7 @@ "Consolas", "datas", "datasource", + "DATETIME", "DBAI", "dbhub", "Dmaven", @@ -69,4 +70,4 @@ "wireframe" ], "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" -} \ No newline at end of file +} diff --git a/chat2db-client/src/components/CreateConnection/config/dataSource.ts b/chat2db-client/src/components/CreateConnection/config/dataSource.ts index ce3d2810b..224b04d02 100644 --- a/chat2db-client/src/components/CreateConnection/config/dataSource.ts +++ b/chat2db-client/src/components/CreateConnection/config/dataSource.ts @@ -1476,126 +1476,126 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ type: DatabaseTypeCode.OCEANBASE }, //redis -// { -// baseInfo: { -// items: [ -// { -// defaultValue: '@localhost', -// inputType: InputType.INPUT, -// labelNameCN: '名称', -// labelNameEN: 'Name', -// name: 'alias', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// { -// defaultValue: 'localhost', -// inputType: InputType.INPUT, -// labelNameCN: '主机', -// labelNameEN: 'Host', -// name: 'host', -// required: true, -// styles: { -// width: '70%', -// } -// }, -// { -// defaultValue: '6379', -// inputType: InputType.INPUT, -// labelNameCN: '端口', -// labelNameEN: 'Port', -// name: 'port', -// labelTextAlign: 'right', -// required: true, -// styles: { -// width: '30%', -// labelWidthEN: '40px', -// labelWidthCN: '40px', -// labelAlign: 'right' -// } -// }, -// { -// defaultValue: AuthenticationType.USERANDPASSWORD, -// inputType: InputType.SELECT, -// labelNameCN: '身份验证', -// labelNameEN: 'Authentication', -// name: 'authenticationType', -// required: true, -// selects: [ -// { -// items: [ -// { -// defaultValue: 'root', -// inputType: InputType.INPUT, -// labelNameCN: '用户名', -// labelNameEN: 'User', -// name: 'user', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// { -// defaultValue: '', -// inputType: InputType.PASSWORD, -// labelNameCN: '密码', -// labelNameEN: 'Password', -// name: 'password', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// ], -// label: 'User&Password', -// value: AuthenticationType.USERANDPASSWORD, -// }, -// { -// label: 'NONE', -// value: AuthenticationType.NONE, -// items: [], -// -// }, -// ], -// styles: { -// width: '50%', -// } -// }, -// { -// defaultValue: '', -// inputType: InputType.INPUT, -// labelNameCN: '数据库', -// labelNameEN: 'Database', -// name: 'database', -// required: false, -// styles: { -// width: '100%', -// } -// }, -// { -// defaultValue: 'jdbc:redis://localhost:6379', -// inputType: InputType.INPUT, -// labelNameCN: 'URL', -// labelNameEN: 'URL', -// name: 'url', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// -// ], -// pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, -// template: 'jdbc:redis://{host}:{port}/{database}', -// }, -// ssh: sshConfig, -// extendInfo: [ -// -// ], -// type: DatabaseTypeCode.REDIS -// }, + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '6379', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authenticationType', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + items: [], + + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'jdbc:redis://localhost:6379', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /jdbc:redis:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'jdbc:redis://{host}:{port}/{database}', + }, + ssh: sshConfig, + extendInfo: [ + + ], + type: DatabaseTypeCode.REDIS + }, //hive { baseInfo: { @@ -1839,124 +1839,124 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ type: DatabaseTypeCode.KINGBASE }, //MONGODB -// { -// baseInfo: { -// items: [ -// { -// defaultValue: '@localhost', -// inputType: InputType.INPUT, -// labelNameCN: '名称', -// labelNameEN: 'Name', -// name: 'alias', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// { -// defaultValue: 'localhost', -// inputType: InputType.INPUT, -// labelNameCN: '主机', -// labelNameEN: 'Host', -// name: 'host', -// required: true, -// styles: { -// width: '70%', -// } -// }, -// { -// defaultValue: '27017', -// inputType: InputType.INPUT, -// labelNameCN: '端口', -// labelNameEN: 'Port', -// name: 'port', -// labelTextAlign: 'right', -// required: true, -// styles: { -// width: '30%', -// labelWidthEN: '40px', -// labelWidthCN: '40px', -// labelAlign: 'right' -// } -// }, -// { -// defaultValue: AuthenticationType.USERANDPASSWORD, -// inputType: InputType.SELECT, -// labelNameCN: '身份验证', -// labelNameEN: 'Authentication', -// name: 'authenticationType', -// required: true, -// selects: [ -// { -// items: [ -// { -// defaultValue: 'root', -// inputType: InputType.INPUT, -// labelNameCN: '用户名', -// labelNameEN: 'User', -// name: 'user', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// { -// defaultValue: '', -// inputType: InputType.PASSWORD, -// labelNameCN: '密码', -// labelNameEN: 'Password', -// name: 'password', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// ], -// label: 'User&Password', -// value: AuthenticationType.USERANDPASSWORD, -// }, -// { -// label: 'NONE', -// value: AuthenticationType.NONE, -// items: [], -// -// }, -// ], -// styles: { -// width: '50%', -// } -// }, -// { -// defaultValue: '', -// inputType: InputType.INPUT, -// labelNameCN: '数据库', -// labelNameEN: 'Database', -// name: 'database', -// required: false, -// styles: { -// width: '100%', -// } -// }, -// { -// defaultValue: 'mongodb://localhost:27017', -// inputType: InputType.INPUT, -// labelNameCN: 'URL', -// labelNameEN: 'URL', -// name: 'url', -// required: true, -// styles: { -// width: '100%', -// } -// }, -// -// ], -// pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, -// template: 'mongodb://{host}:{port}/{database}', -// }, -// ssh: sshConfig, -// extendInfo: [ -// -// ], -// type: DatabaseTypeCode.MONGODB -// }, + { + baseInfo: { + items: [ + { + defaultValue: '@localhost', + inputType: InputType.INPUT, + labelNameCN: '名称', + labelNameEN: 'Name', + name: 'alias', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: 'localhost', + inputType: InputType.INPUT, + labelNameCN: '主机', + labelNameEN: 'Host', + name: 'host', + required: true, + styles: { + width: '70%', + } + }, + { + defaultValue: '27017', + inputType: InputType.INPUT, + labelNameCN: '端口', + labelNameEN: 'Port', + name: 'port', + labelTextAlign: 'right', + required: true, + styles: { + width: '30%', + labelWidthEN: '40px', + labelWidthCN: '40px', + labelAlign: 'right' + } + }, + { + defaultValue: AuthenticationType.USERANDPASSWORD, + inputType: InputType.SELECT, + labelNameCN: '身份验证', + labelNameEN: 'Authentication', + name: 'authenticationType', + required: true, + selects: [ + { + items: [ + { + defaultValue: 'root', + inputType: InputType.INPUT, + labelNameCN: '用户名', + labelNameEN: 'User', + name: 'user', + required: true, + styles: { + width: '100%', + } + }, + { + defaultValue: '', + inputType: InputType.PASSWORD, + labelNameCN: '密码', + labelNameEN: 'Password', + name: 'password', + required: true, + styles: { + width: '100%', + } + }, + ], + label: 'User&Password', + value: AuthenticationType.USERANDPASSWORD, + }, + { + label: 'NONE', + value: AuthenticationType.NONE, + items: [], + + }, + ], + styles: { + width: '50%', + } + }, + { + defaultValue: '', + inputType: InputType.INPUT, + labelNameCN: '数据库', + labelNameEN: 'Database', + name: 'database', + required: false, + styles: { + width: '100%', + } + }, + { + defaultValue: 'mongodb://localhost:27017', + inputType: InputType.INPUT, + labelNameCN: 'URL', + labelNameEN: 'URL', + name: 'url', + required: true, + styles: { + width: '100%', + } + }, + + ], + pattern: /mongodb:\/\/(.*):(\d+)(\/(\w+))?/, + template: 'mongodb://{host}:{port}/{database}', + }, + ssh: sshConfig, + extendInfo: [ + + ], + type: DatabaseTypeCode.MONGODB + }, ]; From 6eb2b58cf06aa40446f86266f1d39fc85776ca99 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 11:10:58 +0800 Subject: [PATCH 0645/1069] fix log --- .../config/config/WebLogConfiguration.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/WebLogConfiguration.java diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/WebLogConfiguration.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/WebLogConfiguration.java new file mode 100644 index 000000000..7813bf157 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/WebLogConfiguration.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.start.config.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.zalando.logbook.BodyFilter; + +/** + * log config + * + * @author Jiaju Zhuang + */ +@Configuration +public class WebLogConfiguration { + + @Bean + public BodyFilter bodyFilter() { + return BodyFilter.none(); + } +} From 8ad3516fba02359bd17843825090082e8faa20be Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 19 Aug 2023 11:35:18 +0800 Subject: [PATCH 0646/1069] =?UTF-8?q?style:=20tab=20=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/components/Iconfont/index.tsx | 6 +++--- chat2db-client/src/components/Tabs/index.less | 14 +++++++++++--- chat2db-client/src/constants/common.ts | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index fcd3057fb..d33e2e789 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_72e0owin532.woff2?t=1691843170071') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_72e0owin532.woff?t=1691843170071') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_72e0owin532.ttf?t=1691843170071') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_3p2ezsbklq9.woff2?t=1692415581018') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_3p2ezsbklq9.woff?t=1692415581018') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_3p2ezsbklq9.ttf?t=1692415581018') format('truetype'); } `; let style = document.createElement('style'); diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 61f44f680..9dd2751a3 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -54,14 +54,18 @@ display: none; align-items: center; flex-shrink: 0; - height: 20px; - margin-left: 4px; + height: 16px; + padding: 0px 2px; + margin-left: 2px; + border-radius: 2px; cursor: pointer; + color: var(--color-text-secondary); i { font-size: 12px; } &:hover { color: var(--color-primary); + background-color: var(--color-hover-bg); } } &:hover { @@ -99,14 +103,18 @@ display: none; align-items: center; flex-shrink: 0; - height: 20px; + height: 16px; + padding: 0px 2px; margin-left: 2px; + border-radius: 2px; + color: var(--color-text-secondary); cursor: pointer; i { font-size: 12px; } &:hover { color: var(--color-primary); + background-color: var(--color-hover-bg); } } &:hover { diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index a005802b4..57a872064 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -47,7 +47,7 @@ export const operationTypeConfig: { }; } = { [OperationType.CONSOLE]: { - icon: '\ue619' + icon: '\uec83' }, [OperationType.VIEW]: { icon: '\ue70c' From 0fef39fd920e6e21b7370b13192cd6b0605ef1b4 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 13:43:33 +0800 Subject: [PATCH 0647/1069] Add batch close interface --- .../saved/OperationSavedController.java | 23 ++++++++++++++++++- .../saved/request/BatchTabCloseRequest.java | 21 +++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/BatchTabCloseRequest.java 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 912febdc6..7ab64ce77 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 @@ -12,11 +12,12 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.controller.operation.saved.converter.OperationWebConverter; +import ai.chat2db.server.web.api.controller.operation.saved.request.BatchTabCloseRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationCreateRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationQueryRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationUpdateRequest; import ai.chat2db.server.web.api.controller.operation.saved.vo.OperationVO; - +import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -95,6 +96,26 @@ public ActionResult update(@RequestBody OperationUpdateRequest request) { return operationService.update(param); } + /** + * 批量关闭标签 + * + * @param request + * @return + */ + @RequestMapping(value = "/batch_tab_close", method = {RequestMethod.POST, RequestMethod.PUT}) + public ActionResult batchTabClose(@RequestBody BatchTabCloseRequest request) { + if (CollectionUtils.isEmpty(request.getIdList())) { + return ActionResult.isSuccess(); + } + request.getIdList().forEach(id -> { + OperationUpdateParam param = new OperationUpdateParam(); + param.setId(id); + param.setTabOpened("n"); + operationService.update(param); + }); + return ActionResult.isSuccess(); + } + /** * 删除我的保存 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/BatchTabCloseRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/BatchTabCloseRequest.java new file mode 100644 index 000000000..20969e96d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/request/BatchTabCloseRequest.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.web.api.controller.operation.saved.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * close tab + * @author Jiaju Zhuang + */ +@Data +public class BatchTabCloseRequest { + + /** + * 主键 + */ + @NotNull + private List idList; + +} From 005a388b8f6f284990482957357562bd22fa6f8a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 16:19:42 +0800 Subject: [PATCH 0648/1069] Adjusting Blob Query Results --- .../main/resources/i18n/messages.properties | 4 +- .../resources/i18n/messages_en_US.properties | 4 +- .../resources/i18n/messages_zh_CN.properties | 4 +- .../rdb/RdbDmlExportController.java | 6 +- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 22 +++++- .../java/ai/chat2db/spi/util/JdbcUtils.java | 77 +++++++------------ 6 files changed, 59 insertions(+), 58 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties index b787c66ba..429e03d23 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties @@ -15,4 +15,6 @@ connection.ssh.error=SSH connection failed, please check the connection informat connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number -sqlResult.success=Execution successful \ No newline at end of file +sqlResult.success=Execution successful + +execute.exportCsv=For more data, please click on Export CSV diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index 8f6980adf..39b19d969 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -12,4 +12,6 @@ connection.ssh.error=SSH connection failed, please check the connection informat connection.driver.load.error=Failed to load driver class, please check the driver jar package # sqlResult sqlResult.rowNumber=Row Number -sqlResult.success=Execution successful \ No newline at end of file +sqlResult.success=Execution successful + +execute.exportCsv=For more data, please click on Export CSV diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index 4ab11fffc..fad78d591 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -14,4 +14,6 @@ connection.ssh.error=SSH 链接异常,请检查SSH配置 connection.driver.load.error=数据库驱动加载异常,请检查驱动配置 # sqlResult sqlResult.rowNumber=行号 -sqlResult.success=执行成功 \ No newline at end of file +sqlResult.success=执行成功 + +execute.exportCsv=更多数据请点击导出csv diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java index 1a8ffc208..037eebe8f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlExportController.java @@ -107,7 +107,7 @@ public void export(@Valid @RequestBody DataExportRequest request, HttpServletRes } else { doExportInsert(sql, response, fileName, dbType, tableName); } - String SS= ConfigUtils.APP_PATH; + String SS = ConfigUtils.APP_PATH; } private void doExportCsv(String sql, HttpServletResponse response, String fileName) @@ -130,7 +130,7 @@ private void doExportCsv(String sql, HttpServletResponse response, String fileNa List> writeDataList = Lists.newArrayList(); writeDataList.add(dataList); excelWrapper.getExcelWriter().write(writeDataList, excelWrapper.getWriteSheet()); - }); + }, false); } finally { if (excelWrapper.getExcelWriter() != null) { excelWrapper.getExcelWriter().finish(); @@ -161,7 +161,7 @@ private void doExportInsert(String sql, HttpServletResponse response, String fil sqlInsertStatement.setValues(valuesClause); printWriter.println(SQLUtils.toSQLString(sqlInsertStatement, dbType, INSERT_FORMAT_OPTION) + ";"); - }); + }, false); } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 417945867..a109cf402 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -112,6 +112,11 @@ public R execute(Connection connection, String sql, ResultSetFunction fun public void executeSql(Connection connection, String sql, Consumer> headerConsumer, Consumer> rowConsumer) { + executeSql(connection, sql, headerConsumer, rowConsumer, true); + } + + public void executeSql(Connection connection, String sql, Consumer> headerConsumer, + Consumer> rowConsumer, boolean limitSize) { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); try (Statement stmt = connection.createStatement();) { @@ -139,7 +144,7 @@ public void executeSql(Connection connection, String sql, Consumer> while (rs.next()) { List row = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { - row.add(ai.chat2db.spi.util.JdbcUtils.getResultSetValue(rs, i)); + row.add(ai.chat2db.spi.util.JdbcUtils.getResultSetValue(rs, i, limitSize)); } rowConsumer.accept(row); } @@ -160,6 +165,17 @@ public void executeSql(Connection connection, String sql, Consumer> * @throws SQLException */ public ExecuteResult execute(final String sql, Connection connection) throws SQLException { + return execute(sql, connection, true); + } + + /** + * 执行sql + * + * @param sql + * @return + * @throws SQLException + */ + public ExecuteResult execute(final String sql, Connection connection, boolean limitSize) throws SQLException { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); @@ -199,7 +215,7 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL List row = Lists.newArrayListWithExpectedSize(col); dataList.add(row); for (int i = 1; i <= col; i++) { - row.add(ai.chat2db.spi.util.JdbcUtils.getResultSetValue(rs, i)); + row.add(ai.chat2db.spi.util.JdbcUtils.getResultSetValue(rs, i, limitSize)); } } executeResult.setDuration(timeInterval.interval()); @@ -224,7 +240,7 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL * @throws SQLException */ public ExecuteResult execute(Connection connection, String sql) throws SQLException { - return execute(sql, connection); + return execute(sql, connection, true); } /** 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 c26b14b81..e0b499664 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,27 +1,23 @@ package ai.chat2db.spi.util; import java.math.BigDecimal; -import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; -import java.sql.Date; import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Timestamp; import java.sql.Types; -import java.time.LocalDate; -import java.time.LocalDateTime; import java.util.Map; -import java.util.Objects; import com.alibaba.druid.DbType; +import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.enums.DataTypeEnum; import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.ssh.SSHManager; +import cn.hutool.core.io.unit.DataSizeUtil; import com.jcraft.jsch.Session; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -34,6 +30,8 @@ @Slf4j public class JdbcUtils { + private static final long MAX_RESULT_SIZE = 256 * 1024; + /** * 获取德鲁伊的的数据库类型 * @@ -141,59 +139,40 @@ private static int getTypeByTypeName(String typeName, int type) { * @return * @throws SQLException */ - public static String getResultSetValue(ResultSet rs, int index) throws SQLException { + public static String getResultSetValue(ResultSet rs, int index, boolean limitSize) throws SQLException { Object obj = rs.getObject(index); if (obj == null) { return null; } - - if (obj instanceof Blob blob) { - return rs.getString(index); - } - if (obj instanceof Clob clob) { - return clob.getSubString(1, Math.toIntExact(clob.length())); - } - if (obj instanceof Timestamp timestamp) { - return Objects.toString(timestamp); - } - - String className = obj.getClass().getName(); - if ("oracle.sql.TIMESTAMP".equals(className) || "oracle.sql.TIMESTAMPTZ".equals(className)) { - return Objects.toString(rs.getTimestamp(index)); - } - if (className.startsWith("oracle.sql.DATE")) { - String metaDataClassName = rs.getMetaData().getColumnClassName(index); - if ("java.sql.Timestamp".equals(metaDataClassName) || "oracle.sql.TIMESTAMP".equals(metaDataClassName)) { - return Objects.toString(rs.getTimestamp(index)); - } else { - return Objects.toString(rs.getDate(index)); - } - } - if (obj instanceof Date date) { - if ("java.sql.Timestamp".equals(rs.getMetaData().getColumnClassName(index))) { - return Objects.toString(rs.getDate(index)); - } - return Objects.toString(date); - } - if (obj instanceof LocalDateTime localDateTime) { - return Objects.toString(localDateTime); - } - if (obj instanceof LocalDate localDate) { - return Objects.toString(localDate); - } if (obj instanceof BigDecimal bigDecimal) { return bigDecimal.toPlainString(); - } - if (obj instanceof Double d) { + } else if (obj instanceof Double d) { return BigDecimal.valueOf(d).toPlainString(); - } - if (obj instanceof Float f) { + } else if (obj instanceof Float f) { return BigDecimal.valueOf(f).toPlainString(); + } else if (obj instanceof Clob) { + return largeString(rs, index, limitSize); + } else if (obj instanceof byte[]) { + return largeString(rs, index, limitSize); + } + return rs.getString(index); + } + + private static String largeString(ResultSet rs, int index, boolean limitSize) throws SQLException { + String result = rs.getString(index); + if (result == null) { + return null; } - if (obj instanceof Number num) { - return Objects.toString(num); + if (!limitSize) { + return result; + } + + if (result.length() > MAX_RESULT_SIZE) { + return "[ " + DataSizeUtil.format(MAX_RESULT_SIZE) + " of " + DataSizeUtil.format(result.length()) + " ," + + I18nUtils.getMessage("execute.exportCsv") + " ] " + result.substring(0, + Math.toIntExact(MAX_RESULT_SIZE)); } - return Objects.toString(obj); + return result; } /** From 3c7ac3f0cd888cb3001f9bfb75cef1703b41a072 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 19 Aug 2023 16:34:40 +0800 Subject: [PATCH 0649/1069] feat: Add login page --- chat2db-client/.umirc.ts | 13 +- chat2db-client/package.json | 1 + chat2db-client/src/pages/login/index.less | 64 +++++++ chat2db-client/src/pages/login/index.tsx | 62 ++++++ chat2db-client/src/service/base.ts | 4 +- chat2db-client/yarn.lock | 220 +++++++++++++++++++++- 6 files changed, 354 insertions(+), 10 deletions(-) create mode 100644 chat2db-client/src/pages/login/index.less create mode 100644 chat2db-client/src/pages/login/index.tsx diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index b9733526c..b769c8e62 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -1,5 +1,5 @@ -import { extractYarnConfig, transitionTimezoneTimestamp } from './src/utils/webpack'; import { defineConfig } from 'umi'; +import { extractYarnConfig, transitionTimezoneTimestamp } from './src/utils/webpack'; const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); @@ -27,9 +27,11 @@ export default defineConfig({ { 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' }, ], + npmClient: 'yarn', dva: {}, plugins: ['@umijs/plugins/dist/dva'], @@ -39,6 +41,10 @@ export default defineConfig({ target: 'http://127.0.0.1:10821', changeOrigin: true, }, + '/oauth': { + target: 'http://127.0.0.1:10821', + changeOrigin: true, + }, '/client/remaininguses/': { target: 'http://127.0.0.1:1889', changeOrigin: true, @@ -53,7 +59,10 @@ export default defineConfig({ localStorage.setItem('app-local-storage-versions', 'v2'); }`, `if (window.myAPI) { window.myAPI.startServerForSpawn() }`, - { src: 'https://www.googletagmanager.com/gtag/js?id=G-V8M4E5SF61', async: true }, + { + src: 'https://www.googletagmanager.com/gtag/js?id=G-V8M4E5SF61', + async: true, + }, // `window.dataLayer = window.dataLayer || []; // function gtag() { // window.dataLayer.push(arguments); diff --git a/chat2db-client/package.json b/chat2db-client/package.json index c07b110c8..6cbbbea3f 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -63,6 +63,7 @@ "prettier": "^2", "prettier-plugin-organize-imports": "^2", "prettier-plugin-packagejson": "^2", + "tailwindcss": "^3", "typescript": "^5.0.3" }, "peerDependencies": { diff --git a/chat2db-client/src/pages/login/index.less b/chat2db-client/src/pages/login/index.less new file mode 100644 index 000000000..c80136b71 --- /dev/null +++ b/chat2db-client/src/pages/login/index.less @@ -0,0 +1,64 @@ +.loginPage { + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.logo { + position: fixed; + top: 32px; + left: 32px; + app-region: 'no-drag'; + display: flex; + align-items: center; + + .logoImage { + width: 36px; + height: 36px; + margin-right: 12px; + } + + .logoText { + color: var(--color-text); + font-weight: bold; + font-size: 20px; + } +} + +.loginPlane { + width: 360px; + border: 1px solid var(--color-border); + box-shadow: rgb(22 14 45 / 2%) 0px 0px 40px, rgb(22 14 45 / 6%) 0px 0px 104px; + border-radius: 8px; + padding: 48px; + color: var(--color-text); + margin-bottom: 48px; + .loginWelcome { + font-size: 24px; + text-align: center; + } + .whyLogin { + margin-top: 8px; + text-align: center; + font-size: 12px; + line-height: 24px; + opacity: 0.4; + cursor: pointer; + + &:hover { + opacity: 0.6; + font-size: 14px; + text-decoration: underline; + } + } +} + +.loginForm { + margin-top: 36px; + .loginFormSubmit { + margin-top: 24px; + width: 100%; + } +} diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx new file mode 100644 index 000000000..309e4e4d6 --- /dev/null +++ b/chat2db-client/src/pages/login/index.tsx @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import { Button, Form, Input, Tooltip } from 'antd'; +import { getUser, userLogin } from '@/service/user'; +import { history } from 'umi'; +import LogoImg from '@/assets/logo/logo.png'; +import styles from './index.less'; + +interface IFormData { + userName: string; + password: string; +} + +const App: React.FC = () => { + const handleLogin = async (formData: { userName: string; password: string }) => { + let res = await userLogin(formData); + if (res) { + console.log('res', res); + window.location.href = '/'; + } + }; + + return ( +
+
+ +
Chat2DB
+
+
+
欢迎使用 Chat2DB
+ + Chat2DB 账号仅用于团队协作管理 +
+ } + > +
为什么需要登录?
+ + + + + + + + + + + +
+ + ); +}; + +export default App; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index a644a72a0..860fefe0f 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -35,7 +35,7 @@ const codeMessage: { [errorCode: number]: string } = { enum ErrorCode { /** 需要登录 */ - NEED_LOGGED_IN = 'NEED_LOGGED_IN', + NEED_LOGGED_IN = 'common.needLoggedIn', } const noNeedToastErrorCode = [ErrorCode.NEED_LOGGED_IN]; @@ -180,7 +180,7 @@ export default function createRequest

(url: string, options?: I errorMessage, errorDetail, solutionLink, - }) + }); // message.error(`${errorCode}: ${errorMessage}`); reject(`${errorCode}: ${errorMessage}`); }, delayTime); diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index ad1eec950..dfc4481c6 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -15,6 +15,11 @@ lodash.debounce "^4.0.8" lodash.throttle "^4.1.1" +"@alloc/quick-lru@^5.2.0": + version "5.2.0" + resolved "https://registry.npmmirror.com/@alloc/quick-lru/-/quick-lru-5.2.0.tgz#7bf68b20c0a350f936915fcae06f58e32007ce30" + integrity sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw== + "@ampproject/remapping@^2.2.0": version "2.2.1" resolved "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -3169,6 +3174,11 @@ antd@^5.6.0: scroll-into-view-if-needed "^3.0.3" throttle-debounce "^5.0.0" +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.npmmirror.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -3214,6 +3224,11 @@ app-builder-lib@23.6.0: tar "^6.1.11" temp-file "^3.4.0" +arg@^5.0.2: + version "5.0.2" + resolved "https://registry.npmmirror.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" + integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -3831,6 +3846,11 @@ camel-case@^4.1.2: pascal-case "^3.1.2" tslib "^2.0.3" +camelcase-css@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + camelcase@^5.3.1: version "5.3.1" resolved "https://registry.npmmirror.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" @@ -4004,7 +4024,7 @@ commander@^2.19.0, commander@^2.20.0: resolved "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== -commander@^4.0.1: +commander@^4.0.0, commander@^4.0.1: version "4.1.1" resolved "https://registry.npmmirror.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== @@ -4452,6 +4472,11 @@ detect-node@^2.0.4: resolved "https://registry.npmmirror.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== +didyoumean@^1.2.2: + version "1.2.2" + resolved "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" + integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.npmmirror.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -4483,6 +4508,11 @@ discontinuous-range@1.0.0: resolved "https://registry.npmmirror.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" integrity sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ== +dlv@^1.1.3: + version "1.1.3" + resolved "https://registry.npmmirror.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" + integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== + dmg-builder@23.6.0: version "23.6.0" resolved "https://registry.npmmirror.com/dmg-builder/-/dmg-builder-23.6.0.tgz#d39d3871bce996f16c07d2cafe922d6ecbb2a948" @@ -5171,6 +5201,17 @@ fast-glob@3.2.12: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.12: + version "3.3.1" + resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" + integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.0" resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0" @@ -5456,6 +5497,25 @@ glob-parent@^5.1.2, glob-parent@~5.1.2: dependencies: is-glob "^4.0.1" +glob-parent@^6.0.2: + version "6.0.2" + resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob@7.1.6: + version "7.1.6" + resolved "https://registry.npmmirror.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.3" resolved "https://registry.npmmirror.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -5982,6 +6042,13 @@ is-core-module@^2.11.0, is-core-module@^2.9.0: dependencies: has "^1.0.3" +is-core-module@^2.13.0: + version "2.13.0" + resolved "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== + dependencies: + has "^1.0.3" + is-date-object@^1.0.1, is-date-object@^1.0.5: version "1.0.5" resolved "https://registry.npmmirror.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -6331,6 +6398,11 @@ jest-worker@^29.6.1: merge-stream "^2.0.0" supports-color "^8.0.0" +jiti@^1.18.2: + version "1.19.3" + resolved "https://registry.npmmirror.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569" + integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w== + js-cookie@^2.x.x: version "2.2.1" resolved "https://registry.npmmirror.com/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -6524,6 +6596,11 @@ lightningcss@1.19.0: lightningcss-linux-x64-musl "1.19.0" lightningcss-win32-x64-msvc "1.19.0" +lilconfig@^2.0.5, lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.npmmirror.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -6673,7 +6750,7 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.4: +micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -6841,6 +6918,15 @@ ms@^2.1.1: resolved "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.npmmirror.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nanoid@^3.3.6: version "3.3.6" resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -6995,11 +7081,16 @@ nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -object-assign@4.x, object-assign@^4, object-assign@^4.1.1: +object-assign@4.x, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-inspect@^1.12.0, object-inspect@^1.12.3, object-inspect@^1.9.0: version "1.12.3" resolved "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" @@ -7290,6 +7381,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatc resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pify@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -7325,7 +7421,7 @@ pino@7.11.0: sonic-boom "^2.2.1" thread-stream "^0.15.1" -pirates@^4.0.4: +pirates@^4.0.1, pirates@^4.0.4: version "4.0.6" resolved "https://registry.npmmirror.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg== @@ -7458,11 +7554,27 @@ postcss-image-set-function@^4.0.6: dependencies: postcss-value-parser "^4.2.0" +postcss-import@^15.1.0: + version "15.1.0" + resolved "https://registry.npmmirror.com/postcss-import/-/postcss-import-15.1.0.tgz#41c64ed8cc0e23735a9698b3249ffdbf704adc70" + integrity sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + postcss-initial@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== +postcss-js@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/postcss-js/-/postcss-js-4.0.1.tgz#61598186f3703bab052f1c4f7d805f3991bee9d2" + integrity sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw== + dependencies: + camelcase-css "^2.0.1" + postcss-lab-function@^4.2.0: version "4.2.1" resolved "https://registry.npmmirror.com/postcss-lab-function/-/postcss-lab-function-4.2.1.tgz#6fe4c015102ff7cd27d1bd5385582f67ebdbdc98" @@ -7471,6 +7583,14 @@ postcss-lab-function@^4.2.0: "@csstools/postcss-progressive-custom-properties" "^1.1.0" postcss-value-parser "^4.2.0" +postcss-load-config@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/postcss-load-config/-/postcss-load-config-4.0.1.tgz#152383f481c2758274404e4962743191d73875bd" + integrity sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA== + dependencies: + lilconfig "^2.0.5" + yaml "^2.1.1" + postcss-logical@^5.0.4: version "5.0.4" resolved "https://registry.npmmirror.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" @@ -7509,6 +7629,13 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" +postcss-nested@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/postcss-nested/-/postcss-nested-6.0.1.tgz#f83dc9846ca16d2f4fa864f16e9d9f7d0961662c" + integrity sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ== + dependencies: + postcss-selector-parser "^6.0.11" + postcss-nesting@^10.1.4: version "10.2.0" resolved "https://registry.npmmirror.com/postcss-nesting/-/postcss-nesting-10.2.0.tgz#0b12ce0db8edfd2d8ae0aaf86427370b898890be" @@ -7616,7 +7743,7 @@ postcss-selector-not@^5.0.0: dependencies: balanced-match "^1.0.0" -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: +postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: version "6.0.13" resolved "https://registry.npmmirror.com/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz#d05d8d76b1e8e173257ef9d60b706a8e5e99bf1b" integrity sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ== @@ -7629,7 +7756,7 @@ postcss-syntax@0.36.2: resolved "https://registry.npmmirror.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c" integrity sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w== -postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: +postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== @@ -8332,6 +8459,13 @@ reactcss@^1.2.3: dependencies: lodash "^4.0.1" +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + read-config-file@6.2.0: version "6.2.0" resolved "https://registry.npmmirror.com/read-config-file/-/read-config-file-6.2.0.tgz#71536072330bcd62ba814f91458b12add9fc7ade" @@ -8509,6 +8643,15 @@ resolve-pkg-maps@^1.0.0: resolved "https://registry.npmmirror.com/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz#616b3dc2c57056b5588c31cdf4b3d64db133720f" integrity sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw== +resolve@^1.1.7, resolve@^1.22.2: + version "1.22.4" + resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.14.2: version "1.22.2" resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f" @@ -9166,6 +9309,19 @@ stylis@^4.0.13, stylis@^4.1.4, stylis@^4.3.0: resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== +sucrase@^3.32.0: + version "3.34.0" + resolved "https://registry.npmmirror.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f" + integrity sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.2" + commander "^4.0.0" + glob "7.1.6" + lines-and-columns "^1.1.6" + mz "^2.7.0" + pirates "^4.0.1" + ts-interface-checker "^0.1.9" + sumchecker@^3.0.1: version "3.0.1" resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" @@ -9239,6 +9395,34 @@ synckit@0.8.5: "@pkgr/utils" "^2.3.1" tslib "^2.5.0" +tailwindcss@^3: + version "3.3.3" + resolved "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf" + integrity sha512-A0KgSkef7eE4Mf+nKJ83i75TMyq8HqY3qmFIJSWy8bNt0v1lG7jUcpGpoTFxAwYcWOphcTBLPPJg+bDfhDf52w== + dependencies: + "@alloc/quick-lru" "^5.2.0" + arg "^5.0.2" + chokidar "^3.5.3" + didyoumean "^1.2.2" + dlv "^1.1.3" + fast-glob "^3.2.12" + glob-parent "^6.0.2" + is-glob "^4.0.3" + jiti "^1.18.2" + lilconfig "^2.1.0" + micromatch "^4.0.5" + normalize-path "^3.0.0" + object-hash "^3.0.0" + picocolors "^1.0.0" + postcss "^8.4.23" + postcss-import "^15.1.0" + postcss-js "^4.0.1" + postcss-load-config "^4.0.1" + postcss-nested "^6.0.1" + postcss-selector-parser "^6.0.11" + resolve "^1.22.2" + sucrase "^3.32.0" + tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -9283,6 +9467,20 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + thread-stream@^0.15.1: version "0.15.2" resolved "https://registry.npmmirror.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" @@ -9370,6 +9568,11 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" +ts-interface-checker@^0.1.9: + version "0.1.13" + resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" + integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== + tslib@2.3.0: version "2.3.0" resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" @@ -9808,6 +10011,11 @@ yaml@^1.10.0: resolved "https://registry.npmmirror.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.1.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" + integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== + yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" From 5ad69253f2c048d1204e57e4ffbfeb30614b05a0 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 16:39:43 +0800 Subject: [PATCH 0650/1069] Modify login interface --- .../team/user/TeamUserPageQueryParam.java | 27 +++++++++++++++++++ .../domain/api/service/TeamUserService.java | 10 +++++++ .../domain/core/impl/TeamUserServiceImpl.java | 20 ++++++++++++++ .../controller/oauth/OauthController.java | 2 +- .../base/wrapper/param/PageQueryParam.java | 9 ++++++- .../tools/base/wrapper/result/PageResult.java | 10 ++++++- .../user/UserTeamAdminController.java | 18 ++++++++++--- 7 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserPageQueryParam.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserPageQueryParam.java new file mode 100644 index 000000000..80cc51160 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserPageQueryParam.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.domain.api.param.team.user; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Team User + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserPageQueryParam extends PageQueryParam { + + /** + * 团队id + */ + @NotNull + private Long teamId; + + /** + * 用户id + */ + @NotNull + private Long userId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java index bc6d24a2d..cf3c2ffcc 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TeamUserService.java @@ -3,6 +3,7 @@ import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -16,6 +17,15 @@ */ public interface TeamUserService { + /** + * Comprehensive Paging Query Data + * + * @param param + * @param selector + * @return + */ + PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector); + /** * Comprehensive Paging Query Data * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java index 8007aa47d..43ceb1ba5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java @@ -5,6 +5,7 @@ import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.domain.core.converter.TeamConverter; @@ -18,6 +19,7 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; @@ -46,6 +48,24 @@ public class TeamUserServiceImpl implements TeamUserService { @Resource private TeamConverter teamConverter; + @Override + public PageResult pageQuery(TeamUserPageQueryParam param, TeamUserSelector selector) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TeamUserDO::getTeamId, param.getTeamId()) + .eq(TeamUserDO::getUserId, param.getUserId()) + ; + + Page page = new Page<>(param.getPageNo(), param.getPageSize()); + page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = teamUserMapper.selectPage(page, queryWrapper); + + List list = teamUserConverter.do2dto(iPage.getRecords()); + + fillData(list, selector); + + return PageResult.of(list, iPage.getTotal(), param); + } + @Override public PageResult comprehensivePageQuery(TeamUserComprehensivePageQueryParam param, TeamUserSelector selector) { diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java index 1a7d63753..4ff19fafe 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java @@ -30,7 +30,7 @@ * @author Jiaju Zhuang */ @RestController -@RequestMapping("/oauth") +@RequestMapping("/api/oauth") @Slf4j public class OauthController { diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java index 08b13e34c..762af5e97 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/param/PageQueryParam.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.List; - import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.enums.OrderByDirectionEnum; @@ -68,6 +67,14 @@ public void queryAll() { this.pageSize = Integer.MAX_VALUE; } + /** + * 查询1条加速 + */ + public void queryOne() { + this.pageNo = 1; + this.pageSize = 1; + } + /** * 新增一个排序 并替换原有排序 * diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java index 5f690e149..1e377cb33 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/PageResult.java @@ -160,7 +160,6 @@ public static PageResult of(List data, PageQueryParam param) { return new PageResult<>(data, 0L, param.getPageNo(), param.getPageSize()); } - /** * 构建空的返回对象 * @@ -223,6 +222,15 @@ public Boolean getHasNextPage() { return hasNextPage; } + /** + * 判断是否存在数据 + * + * @return 是否存在数据 + */ + public boolean hasData() { + return hasData(this); + } + /** * 返回查询异常信息 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java index 56860f171..28c79e48c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java @@ -6,6 +6,7 @@ import ai.chat2db.server.admin.api.controller.user.request.UserTeamBatchCreateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -59,10 +60,19 @@ public WebPageResult page(@Valid UserTeamPageCommonQueryReq @PostMapping("/batch_create") public ActionResult bacthCreate(@Valid @RequestBody UserTeamBatchCreateRequest request) { request.getTeamIdList() - .forEach(teamId -> teamUserService.create(TeamUserCreatParam.builder() - .teamId(teamId) - .userId(request.getUserId()) - .build())); + .forEach(teamId -> { + TeamUserPageQueryParam teamUserPageQueryParam = new TeamUserPageQueryParam(); + teamUserPageQueryParam.setTeamId(teamId); + teamUserPageQueryParam.setUserId(request.getUserId()); + teamUserPageQueryParam.queryOne(); + if (teamUserService.pageQuery(teamUserPageQueryParam, null).hasData()) { + return; + } + teamUserService.create(TeamUserCreatParam.builder() + .teamId(teamId) + .userId(request.getUserId()) + .build()); + }); return ActionResult.isSuccess(); } From 7a5f95ff1ee778c1300ec4c0d9d91add3d09003a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 17:03:08 +0800 Subject: [PATCH 0651/1069] Modifying data sources, adding, deleting, and querying --- .../DataSourceAccessPageQueryParam.java | 34 +++++++++++++++++++ .../access/DataSourceAccessSelector.java | 7 +++- .../api/service/DataSourceAccessService.java | 10 ++++++ .../domain/api/service/DataSourceService.java | 10 +++++- .../core/converter/DataSourceConverter.java | 15 ++++++-- .../impl/DataSourceAccessServiceImpl.java | 27 ++++++++++++++- .../core/impl/DataSourceServiceImpl.java | 20 ++++++++--- .../team/TeamDataSourceAdminController.java | 27 +++++++++++---- .../team/TeamUserAdminController.java | 18 +++++++--- .../TeamDataSourcesAdminConverter.java | 3 ++ .../user/UserDataSourceAdminController.java | 25 +++++++++++--- .../UserDataSourcesAdminConverter.java | 3 ++ 12 files changed, 174 insertions(+), 25 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessPageQueryParam.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessPageQueryParam.java new file mode 100644 index 000000000..d12310ae7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessPageQueryParam.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryParam extends PageQueryParam { + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java index de59b6def..65c50c2ba 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/access/DataSourceAccessSelector.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.api.param.datasource.access; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -21,9 +22,13 @@ public class DataSourceAccessSelector { */ private Boolean accessObject; - /** * 数据源 */ private Boolean dataSource; + + /** + * 数据源 + */ + private DataSourceSelector dataSourceSelector; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java index 5c974dec2..4cf4e531c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessService.java @@ -3,6 +3,7 @@ import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -16,6 +17,15 @@ */ public interface DataSourceAccessService { + /** + * Comprehensive Paging Query Data + * + * @param param + * @param selector + * @return + */ + PageResult pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector); + /** * Paging Query Data * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java index b136db031..87cc90be8 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java @@ -32,7 +32,6 @@ public interface DataSourceService { * @return */ DataResult createWithPermission(DataSourceCreateParam param); - /** * 更新数据源连接 @@ -100,9 +99,18 @@ public interface DataSourceService { * * @param ids * @return + * @deprecated Use {@link #listQuery(List, DataSourceSelector)} */ ListResult queryByIds(List ids); + /** + * 通过ID列表查询数据源 + * + * @param idList + * @return + */ + ListResult listQuery(List idList, DataSourceSelector selector); + /** * 数据源连接测试 * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java index be911e61e..ea308a390 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DataSourceConverter.java @@ -8,6 +8,7 @@ import ai.chat2db.server.domain.api.param.ConsoleCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceTestParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; @@ -177,7 +178,8 @@ public abstract DataSourceTestParam param2param( + ".DriverConfig" + ".class))") @Mapping(target = "extendInfo", - expression = "java(com.alibaba.fastjson2.JSON.parseArray(dataSourceDO.getExtendInfo(),ai.chat2db.spi.model.KeyValue.class))") + expression = "java(com.alibaba.fastjson2.JSON.parseArray(dataSourceDO.getExtendInfo(),ai.chat2db.spi.model" + + ".KeyValue.class))") @Mapping(target = "environment.id", source = "environmentId") public abstract DataSource do2dto(DataSourceDO dataSourceDO); @@ -195,11 +197,20 @@ public abstract DataSourceTestParam param2param( * @param list */ public void fillDetail(List list) { + fillDetail(list, null); + } + + /** + * Fill in detailed information + * + * @param list + */ + public void fillDetail(List list, DataSourceSelector selector) { if (CollectionUtils.isEmpty(list)) { return; } List idList = EasyCollectionUtils.toList(list, DataSource::getId); - List queryList = dataSourceService.queryByIds(idList).getData(); + List queryList = dataSourceService.listQuery(idList, selector).getData(); Map queryMap = EasyCollectionUtils.toIdentityMap(queryList, DataSource::getId); for (DataSource data : list) { if (data == null || data.getId() == null) { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java index 51da26b0d..94fc3ab9e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessServiceImpl.java @@ -7,16 +7,20 @@ import ai.chat2db.server.domain.api.model.DataSourceAccess; import ai.chat2db.server.domain.api.model.DataSourceAccessObject; import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.model.TeamUser; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; +import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.converter.DataSourceAccessConverter; import ai.chat2db.server.domain.core.converter.DataSourceConverter; import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; +import ai.chat2db.server.domain.repository.entity.TeamUserDO; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -24,6 +28,7 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; @@ -55,6 +60,25 @@ public class DataSourceAccessServiceImpl implements DataSourceAccessService { @Resource private TeamService teamService; + @Override + public PageResult pageQuery(DataSourceAccessPageQueryParam param, DataSourceAccessSelector selector) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(DataSourceAccessDO::getDataSourceId, param.getDataSourceId()) + .eq(DataSourceAccessDO::getAccessObjectType, param.getAccessObjectType()) + .eq(DataSourceAccessDO::getAccessObjectId, param.getAccessObjectId()) + ; + + Page page = new Page<>(param.getPageNo(), param.getPageSize()); + page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = dataSourceAccessMapper.selectPage(page, queryWrapper); + + List list = dataSourceAccessConverter.do2dto(iPage.getRecords()); + + fillData(list, selector); + + return PageResult.of(list, iPage.getTotal(), param); + } + @Override public PageResult comprehensivePageQuery(DataSourceAccessComprehensivePageQueryParam param, DataSourceAccessSelector selector) { @@ -100,7 +124,8 @@ private void fillDataSource(List list, DataSourceAccessSelecto if (BooleanUtils.isNotTrue(selector.getDataSource())) { return; } - dataSourceConverter.fillDetail(EasyCollectionUtils.toList(list, DataSourceAccess::getDataSource)); + dataSourceConverter.fillDetail(EasyCollectionUtils.toList(list, DataSourceAccess::getDataSource), + selector.getDataSourceSelector()); } private void fillAccessObject(List list, DataSourceAccessSelector selector) { 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 31753a6b2..25dd98d86 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 @@ -179,7 +179,7 @@ public PageResult queryPage(DataSourcePageQueryParam param, DataSour IPage iPage = dataSourceMapper.selectPage(page, queryWrapper); List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); - fillData(dataSources,selector); + fillData(dataSources, selector); return PageResult.of(dataSources, iPage.getTotal(), param); } @@ -192,7 +192,7 @@ public PageResult queryPageWithPermission(DataSourcePageQueryParam p BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey()); List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); - fillData(dataSources,selector); + fillData(dataSources, selector); return PageResult.of(dataSources, iPage.getTotal(), param); @@ -200,9 +200,19 @@ public PageResult queryPageWithPermission(DataSourcePageQueryParam p @Override public ListResult queryByIds(List ids) { - List dataSourceDOS = dataSourceMapper.selectBatchIds(ids); - List dataSources = dataSourceConverter.do2dto(dataSourceDOS); - return ListResult.of(dataSources); + return listQuery(ids, null); + } + + @Override + public ListResult listQuery(List idList, DataSourceSelector selector) { + if (CollectionUtils.isEmpty(idList)) { + return ListResult.empty(); + } + List dataList = dataSourceMapper.selectBatchIds(idList); + List list = dataSourceConverter.do2dto(dataList); + + fillData(list, selector); + return ListResult.of(list); } @Override diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java index 149ec7d81..b3f185769 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java @@ -6,7 +6,9 @@ import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -30,7 +32,10 @@ @RestController public class TeamDataSourceAdminController { private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() - .accessObject(Boolean.TRUE) + .dataSource(Boolean.TRUE) + .dataSourceSelector(DataSourceSelector.builder() + .environment(Boolean.TRUE) + .build()) .build(); @Resource @@ -62,11 +67,21 @@ public WebPageResult page(@Valid CommonPageQueryReque @PostMapping("/batch_create") public ActionResult create(@Valid @RequestBody TeamDataSourceBatchCreateRequest request) { request.getDataSourceIdList() - .forEach(dataSourceId -> dataSourceAccessService.create(DataSourceAccessCreatParam.builder() - .dataSourceId(dataSourceId) - .accessObjectId(request.getTeamId()) - .accessObjectType(AccessObjectTypeEnum.TEAM.getCode()) - .build())); + .forEach(dataSourceId -> { + DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); + dataSourceAccessPageQueryParam.setDataSourceId(dataSourceId); + dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.TEAM.getCode()); + dataSourceAccessPageQueryParam.setAccessObjectId(request.getTeamId()); + dataSourceAccessPageQueryParam.queryOne(); + if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { + return; + } + dataSourceAccessService.create(DataSourceAccessCreatParam.builder() + .dataSourceId(dataSourceId) + .accessObjectId(request.getTeamId()) + .accessObjectType(AccessObjectTypeEnum.TEAM.getCode()) + .build()); + }); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java index bb788b03a..2b448724e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java @@ -6,6 +6,7 @@ import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.team.user.TeamUserCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserPageQueryParam; import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -60,10 +61,19 @@ public WebPageResult page(@Valid CommonPageQueryRequest req @PostMapping("/batch_create") public ActionResult create(@Valid @RequestBody TeamUserBatchCreateRequest request) { request.getUserIdList() - .forEach(userId -> teamUserService.create(TeamUserCreatParam.builder() - .teamId(request.getTeamId()) - .userId(userId) - .build())); + .forEach(userId -> { + TeamUserPageQueryParam teamUserPageQueryParam = new TeamUserPageQueryParam(); + teamUserPageQueryParam.setTeamId(request.getTeamId()); + teamUserPageQueryParam.setUserId(userId); + teamUserPageQueryParam.queryOne(); + if (teamUserService.pageQuery(teamUserPageQueryParam, null).hasData()) { + return; + } + teamUserService.create(TeamUserCreatParam.builder() + .teamId(request.getTeamId()) + .userId(userId) + .build()); + }); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java index d3dc0ad52..8677b0621 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java @@ -45,6 +45,9 @@ public abstract class TeamDataSourcesAdminConverter { * @param dto * @return */ + @Mappings({ + @Mapping(target = "teamId", source = "accessObjectId"), + }) public abstract TeamDataSourcePageQueryVO dto2vo(DataSourceAccess dto); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java index 8fe29cc3f..abc949367 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java @@ -6,7 +6,9 @@ import ai.chat2db.server.admin.api.controller.user.request.UserTeamPageCommonQueryRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -31,6 +33,9 @@ public class UserDataSourceAdminController { private static final DataSourceAccessSelector DATA_SOURCE_ACCESS_SELECTOR = DataSourceAccessSelector.builder() .dataSource(Boolean.TRUE) + .dataSourceSelector(DataSourceSelector.builder() + .environment(Boolean.TRUE) + .build()) .build(); @Resource @@ -62,11 +67,21 @@ public WebPageResult page(@Valid UserTeamPageCommonQu @PostMapping("/batch_create") public ActionResult create(@RequestBody UserDataSourceBatchCreateRequest request) { request.getDataSourceIdList() - .forEach(dataSourceId -> dataSourceAccessService.create(DataSourceAccessCreatParam.builder() - .dataSourceId(dataSourceId) - .accessObjectId(request.getUserId()) - .accessObjectType(AccessObjectTypeEnum.USER.getCode()) - .build())); + .forEach(dataSourceId -> { + DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); + dataSourceAccessPageQueryParam.setDataSourceId(dataSourceId); + dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); + dataSourceAccessPageQueryParam.setAccessObjectId(request.getUserId()); + dataSourceAccessPageQueryParam.queryOne(); + if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { + return; + } + dataSourceAccessService.create(DataSourceAccessCreatParam.builder() + .dataSourceId(dataSourceId) + .accessObjectId(request.getUserId()) + .accessObjectType(AccessObjectTypeEnum.USER.getCode()) + .build()); + }); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java index 0bc8df59c..8b517d2f1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java @@ -48,6 +48,9 @@ public abstract class UserDataSourcesAdminConverter { * @param dto * @return */ + @Mappings({ + @Mapping(target = "userId", source = "accessObjectId"), + }) public abstract UserDataSourcePageQueryVO dto2vo(DataSourceAccess dto); } From fcd7d5d058e86f4be7d378af7158081aeb93ced2 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 19 Aug 2023 17:16:45 +0800 Subject: [PATCH 0652/1069] =?UTF-8?q?fix:=20=E6=89=93=E5=BC=80=E8=A7=86?= =?UTF-8?q?=E5=9B=BE=E5=8D=A1=E9=A1=BF=E9=97=AE=E9=A2=98=E3=80=82=E5=85=88?= =?UTF-8?q?=E5=88=9B=E5=BB=BA=E6=8E=A7=E5=88=B6=E5=8F=B0=EF=BC=8C=E5=86=8D?= =?UTF-8?q?=E5=8E=BB=E6=9F=A5=E8=AF=A2=E8=A7=86=E5=9B=BE=E8=AF=A6=E6=83=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SearchResult/TableBox/index.tsx | 5 +- chat2db-client/src/components/Tabs/index.less | 1 + chat2db-client/src/models/workspace.ts | 27 +-- .../components/WorkspaceLeft/index.tsx | 3 +- .../components/WorkspaceRight/index.tsx | 200 ++++++++++++++---- .../components/WorkspaceRightItem/index.tsx | 5 + chat2db-client/src/typings/common.ts | 25 +-- 7 files changed, 181 insertions(+), 85 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index cd8a63c98..62c888380 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -175,11 +175,8 @@ export default function TableBox(props: ITableProps) { return (dataList || []).map((item, rowIndex) => { const rowData: any = {}; item.map((i: string | null, index: number) => { - const { dataType: type } = headerList[index] || {}; const name = `${preCode}${columns[index].name}`; - if (type === TableDataType.DATETIME && i) { - rowData[name] = formatDate(i, 'yyyy-MM-dd hh:mm:ss'); - } else if (i === null) { + if (i === null) { rowData[name] = ''; } else { rowData[name] = i; diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 9dd2751a3..5b94854bd 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -155,6 +155,7 @@ outline: none; font-size: 12px; font-weight: 400; + background-color: var(--color-bg-elevated); input:focus { outline: none; } diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 306c59b56..cb53f6a16 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -48,7 +48,6 @@ export interface IWorkspaceModelType { fetchGetSavedConsole: Effect; fetchGetCurTableList: Effect; fetchGetSavedConsoleLoading: Effect; - fetchGetCurViewList: Effect; }; } @@ -96,7 +95,6 @@ const WorkspaceModel: IWorkspaceModelType = { consoleList: payload, }; }, - // 工作台页面打开的console列表 setOpenConsoleList(state, { payload }) { return { @@ -130,6 +128,7 @@ const WorkspaceModel: IWorkspaceModelType = { }, effects: { + // 获取当前连接下的及联databaseAndSchema数据 *fetchDatabaseAndSchema({ payload, callback }, { put }) { try { const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; @@ -145,6 +144,7 @@ const WorkspaceModel: IWorkspaceModelType = { } }, + // 获取当前连接下的及联databaseAndSchema数据Loading *fetchDatabaseAndSchemaLoading({ payload }, { put }) { try { const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; @@ -157,6 +157,7 @@ const WorkspaceModel: IWorkspaceModelType = { } }, + // 获取保存的控制台列表 *fetchGetSavedConsole({ payload, callback }, { put }) { try { const res = (yield historyService.getSavedConsoleList({ @@ -171,6 +172,7 @@ const WorkspaceModel: IWorkspaceModelType = { catch { } }, + // 获取保存的控制台列表Loading *fetchGetSavedConsoleLoading({ payload, callback }, { put }) { try { const res = (yield historyService.getSavedConsoleList({ @@ -185,6 +187,7 @@ const WorkspaceModel: IWorkspaceModelType = { catch { } }, + // 获取当前连接下的表列表 *fetchGetCurTableList({ payload, callback }, { put, call }) { try { const res = (yield treeConfig[TreeNodeType.TABLES].getChildren!({ @@ -203,26 +206,6 @@ const WorkspaceModel: IWorkspaceModelType = { } catch { - } - }, - *fetchGetCurViewList({ payload, callback }, { put, call }) { - try { - const res = (yield treeConfig[TreeNodeType.VIEWS].getChildren!({ - pageNo: 1, - pageSize: 999, - ...payload, - })) as ITreeNode[]; - // 异步操作完成后调用回调函数 - if (callback && typeof callback === 'function') { - callback(res); - } - yield put({ - type: 'setCurViewList', - payload: res, - }); - } - catch { - } }, }, 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 27acdc05e..7fff45c33 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -9,7 +9,7 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import historyServer from '@/service/history'; import Tree from '../Tree'; -import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; +import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus, OperationType } from '@/constants'; import { IConsole, ITreeNode, ICreateConsole } from '@/typings'; import styles from './index.less'; import { approximateTreeNode, approximateList } from '@/utils'; @@ -70,6 +70,7 @@ const WorkspaceLeft = memo(function (props) { type: databaseType, status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, + operationType: OperationType.CONSOLE } historyService.saveConsole(params || p).then(res => { dispatch({ 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 360a1612d..2ca5f9dd2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -28,6 +28,7 @@ const WorkspaceRight = memo(function (props) { const [activeConsoleId, setActiveConsoleId] = useState(); const { className, aiModel, workspaceModel, dispatch } = props; const { curWorkspaceParams, doubleClickTreeNodeData, openConsoleList, curConsoleId } = workspaceModel; + const openConsoleListRef = useRef(openConsoleList); useEffect(() => { if (!doubleClickTreeNodeData) { @@ -38,60 +39,143 @@ const WorkspaceRight = memo(function (props) { type: 'workspace/setConsoleList', payload: [], }) - + + // 打开视图 if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.VIEW) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, tableName, dataSourceId } = extraParams || {}; - sqlService.getViewDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - tableName: tableName!, - schemaName, - }).then(res=>{ - const name = doubleClickTreeNodeData.name - createConsole(doubleClickTreeNodeData, res.ddl, name); - }) + const callback = (consoleId: number) => { + sqlService.getViewDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + tableName: tableName!, + schemaName, + }).then(res => { + // 更新ddl + const newList = openConsoleListRef.current?.map(t => { + if (t.id === consoleId) { + return { + ...t, + ddl: res.ddl + } + } + return t + }) + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: newList, + }); + }) + } + const name = doubleClickTreeNodeData.name; + + createConsole({ + doubleClickTreeNodeData, + name, + callback + }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TRIGGER) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, triggerName, dataSourceId, } = extraParams || {}; - sqlService.getTriggerDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - triggerName: triggerName!, - schemaName, - }).then(res=>{ - const name = doubleClickTreeNodeData.name - createConsole(doubleClickTreeNodeData, res.triggerBody, name); - }) + const name = doubleClickTreeNodeData.name + const callback = (consoleId: number) => { + sqlService.getTriggerDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + triggerName: triggerName!, + schemaName, + }).then(res => { + // 更新ddl + const newList = openConsoleListRef.current?.map(t => { + if (t.id === consoleId) { + return { + ...t, + ddl: res.triggerBody + } + } + return t + }) + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: newList, + }); + }) + } + createConsole({ + doubleClickTreeNodeData, + name, + callback + }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.PROCEDURE) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, procedureName, dataSourceId } = extraParams || {}; - sqlService.getProcedureDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - procedureName: procedureName!, - schemaName, - }).then(res=>{ - const name = doubleClickTreeNodeData.name - createConsole(doubleClickTreeNodeData, res.procedureBody, name); - }) + const name = doubleClickTreeNodeData.name + const callback = (consoleId: number) => { + sqlService.getProcedureDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + procedureName: procedureName!, + schemaName, + }).then(res => { + // 更新ddl + const newList = openConsoleListRef.current?.map(t => { + if (t.id === consoleId) { + return { + ...t, + ddl: res.procedureBody + } + } + return t + }) + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: newList, + }); + }) + } + createConsole({ + doubleClickTreeNodeData, + name, + callback + }); } + if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.FUNCTION) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, functionName } = extraParams || {}; - sqlService.getFunctionDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - functionName: functionName!, - schemaName, - }).then(res=>{ - const name = doubleClickTreeNodeData.name - createConsole(doubleClickTreeNodeData, res.functionBody, name); - }) + const name = doubleClickTreeNodeData.name + const callback = (consoleId: number) => { + sqlService.getFunctionDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + functionName: functionName!, + schemaName, + }).then(res => { + // 更新ddl + const newList = openConsoleListRef.current?.map(t => { + if (t.id === consoleId) { + return { + ...t, + ddl: res.functionBody + } + } + return t + }) + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: newList, + }); + }) + } + createConsole({ + doubleClickTreeNodeData, + name, + callback + }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE && !openConsoleList?.length) { @@ -99,7 +183,11 @@ const WorkspaceRight = memo(function (props) { const { databaseName, schemaName, tableName, } = extraParams || {}; const ddl = `SELECT * FROM ${tableName};\n`; const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); - createConsole(doubleClickTreeNodeData, ddl, name); + createConsole({ + doubleClickTreeNodeData, + name, + ddl + }); } dispatch({ @@ -117,6 +205,7 @@ const WorkspaceRight = memo(function (props) { }, [activeConsoleId]) useEffect(() => { + openConsoleListRef.current = openConsoleList; const newActiveConsoleId = curConsoleId || activeConsoleId || Number(localStorage.getItem('active-console-id') || 0); // 用完之后就清掉curConsoleId if (!openConsoleList?.length) { @@ -150,7 +239,13 @@ const WorkspaceRight = memo(function (props) { }, [openConsoleList]); - function createConsole(doubleClickTreeNodeData: any, ddl: string, name: string) { + function createConsole(params: { + doubleClickTreeNodeData: any, + name: string, + callback?: Function, + ddl?: string, + }) { + const { doubleClickTreeNodeData, name, callback, ddl } = params; const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {}; let p: any = { @@ -162,13 +257,16 @@ const WorkspaceRight = memo(function (props) { dataSourceName: dataSourceName!, status: ConsoleStatus.DRAFT, operationType: doubleClickTreeNodeData.treeNodeType, - ddl, + ddl: ddl || '', tabOpened: ConsoleOpenedStatus.IS_OPEN, }; - addConsole(p); + addConsole({ + newConsole: p, + callback, + }); } - function getConsoleList() { + function getConsoleList(callback?: Function) { let p: any = { pageNo: 1, pageSize: 999, @@ -184,6 +282,7 @@ const WorkspaceRight = memo(function (props) { type: 'workspace/setOpenConsoleList', payload: res.data, }); + callback?.(); }, }); } @@ -201,7 +300,10 @@ const WorkspaceRight = memo(function (props) { } }; - const addConsole = (params?: ICreateConsole) => { + const addConsole = (params?: { + newConsole?: ICreateConsole; + callback?: Function; + }) => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; let p = { name: `new console${openConsoleList?.length}`, @@ -214,9 +316,15 @@ const WorkspaceRight = memo(function (props) { tabOpened: ConsoleOpenedStatus.IS_OPEN, operationType: OperationType.CONSOLE, }; - historyService.saveConsole(params || p).then((res) => { - setActiveConsoleId(res); - getConsoleList(); + historyService.saveConsole(params?.newConsole || p).then((res) => { + params?.callback?.(res); + const callback = () => { + dispatch({ + type: 'workspace/setCurConsoleId', + payload: res, + }); + } + getConsoleList(callback); }); }; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx index 1ceb9d53a..2c6579aab 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx @@ -16,6 +16,7 @@ import sql from '@/service/sql'; import { isNumber } from 'lodash'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import { downloadFile } from '@/utils/common'; +import { useUpdateEffect } from '@/hooks/useUpdateEffect' interface IProps { className?: string; isActive: boolean; @@ -68,6 +69,10 @@ const WorkspaceRightItem = memo(function (props) { }); }, [doubleClickTreeNodeData]); + useUpdateEffect(() => { + setAppendValue({ text: data.initDDL }); + }, [data.initDDL]) + /** * 执行SQL * @param sql diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 3b5922b0d..56eac3240 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -14,19 +14,20 @@ export interface IPageParams { pageSize: number; } +// 控制台详情 export interface IConsole { - id: number; - name: string; - ddl: string; - dataSourceId: number; - dataSourceName: string; - databaseName?: string; - schemaName?: string; - type: DatabaseTypeCode; - status: ConsoleStatus; - connectable: boolean; - tabOpened?: ConsoleOpenedStatus; - operationType: OperationType; + 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?: ConsoleOpenedStatus; // 控制台tab是否打开 + operationType: OperationType; // 操作类型 } export interface Option { From 45b159f8540e71840525f6dcc49e887acbd90f5b Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 17:33:29 +0800 Subject: [PATCH 0653/1069] Add logic for verifying connection permissions --- .../DataSourceAccessBusinessService.java | 19 ++++++ .../DataSourceAccessBusinessServiceImpl.java | 65 +++++++++++++++++++ .../core/impl/DataSourceServiceImpl.java | 6 +- .../domain/core/util/PermissionUtils.java | 22 ++++++- .../mapper/DataSourceAccessCustomMapper.java | 2 + .../mapper/DataSourceAccessCustomMapper.xml | 13 ++++ .../web/api/aspect/ConnectionInfoHandler.java | 21 ++++-- 7 files changed, 138 insertions(+), 10 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java new file mode 100644 index 000000000..e86105758 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.domain.api.service; + +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import jakarta.validation.constraints.NotNull; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +public interface DataSourceAccessBusinessService { + /** + * delete + * + * @param dataSourceId + * @return + */ + ActionResult checkPermission(@NotNull Long dataSourceId); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java new file mode 100644 index 000000000..02886ed49 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java @@ -0,0 +1,65 @@ +package ai.chat2db.server.domain.core.impl; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; +import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; +import ai.chat2db.server.domain.api.service.DataSourceAccessService; +import ai.chat2db.server.domain.repository.mapper.DataSourceAccessCustomMapper; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.common.exception.PermissionDeniedBusinessException; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Slf4j +@Service +public class DataSourceAccessBusinessServiceImpl implements DataSourceAccessBusinessService { + + @Resource + private DataSourceAccessService dataSourceAccessService; + @Resource + private DataSourceAccessCustomMapper dataSourceAccessCustomMapper; + + @Override + public ActionResult checkPermission(Long dataSourceId) { + LoginUser loginUser = ContextUtils.getLoginUser(); + // Representative is desktop mode + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(dataSourceId)) { + return ActionResult.isSuccess(); + } else { + throw new PermissionDeniedBusinessException(); + } + } + + // Administrators can edit anything + if (loginUser.getAdmin()) { + return ActionResult.isSuccess(); + } + + // Verify if user have permission + DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); + dataSourceAccessPageQueryParam.setDataSourceId(dataSourceId); + dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); + dataSourceAccessPageQueryParam.setAccessObjectId(loginUser.getId()); + dataSourceAccessPageQueryParam.queryOne(); + if (dataSourceAccessService.pageQuery(dataSourceAccessPageQueryParam, null).hasData()) { + return ActionResult.isSuccess(); + } + + // Verify if the team has permission + if (dataSourceAccessCustomMapper.checkTeamPermission(dataSourceId, loginUser.getId()) != null) { + return ActionResult.isSuccess(); + + } + throw new PermissionDeniedBusinessException(); + } +} 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 25dd98d86..ec586d983 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 @@ -117,7 +117,7 @@ private void preWarmingData(Long dataSourceId) { @Override public ActionResult updateWithPermission(DataSourceUpdateParam param) { DataSource dataSource = queryExistent(param.getId()).getData(); - PermissionUtils.checkPermission(dataSource.getUserId()); + PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtModified(LocalDateTime.now()); @@ -129,7 +129,7 @@ public ActionResult updateWithPermission(DataSourceUpdateParam param) { public ActionResult deleteWithPermission(Long id) { DataSource dataSource = queryExistent(id).getData(); - PermissionUtils.checkPermission(dataSource.getUserId()); + PermissionUtils.checkOperationPermission(dataSource.getUserId()); dataSourceMapper.deleteById(id); return ActionResult.isSuccess(); @@ -153,7 +153,7 @@ public DataResult queryExistent(Long id) { @Override public DataResult copyByIdWithPermission(Long id) { DataSource dataSource = queryExistent(id).getData(); - PermissionUtils.checkPermission(dataSource.getUserId()); + PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = dataSourceMapper.selectById(id); dataSourceDO.setId(null); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java index 466c5a852..d14357c52 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java @@ -17,7 +17,7 @@ public class PermissionUtils { * * @param createUserId The creator of the current content */ - public static void checkPermission(Long createUserId) { + public static void checkOperationPermission(Long createUserId) { LoginUser loginUser = ContextUtils.getLoginUser(); // Representative is desktop mode if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { @@ -36,4 +36,24 @@ public static void checkPermission(Long createUserId) { throw new PermissionDeniedBusinessException(); } } + + /** + * 校验是否有查询权限 + * + * @param createUserId + * @return + */ + public static boolean checkBaseQueryPermission(Long createUserId) { + LoginUser loginUser = ContextUtils.getLoginUser(); + // Representative is desktop mode + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(createUserId)) { + return true; + } else { + throw new PermissionDeniedBusinessException(); + } + } + // Administrators can edit anything + return loginUser.getAdmin(); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java index 7daa7b463..7093edc1d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessCustomMapper.java @@ -17,4 +17,6 @@ IPage comprehensivePageQuery(IPage page, @Param("accessObjectId") Long accessObjectId, @Param("userOrTeamSearchKey") String userOrTeamSearchKey, @Param("dataSourceSearchKey") String dataSourceSearchKey); + + DataSourceAccessDO checkTeamPermission( @Param("dataSourceId") Long dataSourceId, @Param("userId") Long userId); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml index cdacb8644..fe538e014 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml @@ -35,4 +35,17 @@ + + + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index c1f37d7e5..a56447234 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -2,6 +2,7 @@ package ai.chat2db.server.web.api.aspect; import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.exception.ParamBusinessException; @@ -11,6 +12,7 @@ import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; @@ -29,6 +31,8 @@ public class ConnectionInfoHandler { @Autowired private DataSourceService dataSourceService; + @Resource + private DataSourceAccessBusinessService dataSourceAccessBusinessService; @Around("within(@ai.chat2db.server.web.api.aspect.ConnectionInfoAspect *)") public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { @@ -37,16 +41,16 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr if (params != null && params.length > 0) { for (int i = 0; i < params.length; i++) { Object param = params[i]; - if(param instanceof DataSourceBaseRequest){ + if (param instanceof DataSourceBaseRequest) { Long dataSourceId = ((DataSourceBaseRequest)param).getDataSourceId(); String schemaName = ((DataSourceBaseRequest)param).getSchemaName(); String database = ((DataSourceBaseRequest)param).getDatabaseName(); - Chat2DBContext.putContext(toInfo(dataSourceId, database, null,schemaName)); - }else if (param instanceof DataSourceConsoleRequestInfo) { + Chat2DBContext.putContext(toInfo(dataSourceId, database, null, schemaName)); + } else if (param instanceof DataSourceConsoleRequestInfo) { Long dataSourceId = ((DataSourceConsoleRequestInfo)param).getDataSourceId(); Long consoleId = ((DataSourceConsoleRequestInfo)param).getConsoleId(); String database = ((DataSourceConsoleRequestInfo)param).getDatabaseName(); - Chat2DBContext.putContext(toInfo(dataSourceId, database, consoleId,null)); + Chat2DBContext.putContext(toInfo(dataSourceId, database, consoleId, null)); } else if (param instanceof DataSourceBaseRequestInfo) { Long dataSourceId = ((DataSourceBaseRequestInfo)param).getDataSourceId(); String database = ((DataSourceBaseRequestInfo)param).getDatabaseName(); @@ -60,12 +64,16 @@ public Object connectionInfoHandler(ProceedingJoinPoint proceedingJoinPoint) thr } } - public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId,String schemaName) { + public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, String schemaName) { DataResult result = dataSourceService.queryById(dataSourceId); DataSource dataSource = result.getData(); if (!result.success() || dataSource == null) { throw new ParamBusinessException("dataSourceId"); } + + // Verify permissions + dataSourceAccessBusinessService.checkPermission(dataSourceId); + ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setAlias(dataSource.getAlias()); connectInfo.setUser(dataSource.getUserName()); @@ -93,6 +101,7 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId,Str } public ConnectInfo toInfo(Long dataSourceId, String database) { - return toInfo(dataSourceId, database, null,null); + return toInfo(dataSourceId, database, null, null); } + } \ No newline at end of file From df8d5d8ca82e700cc07544a5b68c96b6ecbcf575 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 19 Aug 2023 17:38:47 +0800 Subject: [PATCH 0654/1069] Add logic for verifying connection permissions --- .../admin/api/controller/user/request/UserUpdateRequest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java index c102f2852..382826b0b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java @@ -20,7 +20,6 @@ public class UserUpdateRequest { /** * 密码 */ - @NotNull private String password; /** From 16c8b499203b5f74f4eb454949e9d83b818a5d04 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 19 Aug 2023 18:00:13 +0800 Subject: [PATCH 0655/1069] feat: Optimize code --- chat2db-client/.umirc.ts | 4 --- chat2db-client/src/pages/login/index.tsx | 1 - chat2db-client/src/service/base.ts | 3 +-- chat2db-client/src/service/user.ts | 33 +++++++----------------- chat2db-client/src/typings/user.ts | 10 +++++++ 5 files changed, 20 insertions(+), 31 deletions(-) create mode 100644 chat2db-client/src/typings/user.ts diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index b769c8e62..d2c9db13f 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -41,10 +41,6 @@ export default defineConfig({ target: 'http://127.0.0.1:10821', changeOrigin: true, }, - '/oauth': { - target: 'http://127.0.0.1:10821', - changeOrigin: true, - }, '/client/remaininguses/': { target: 'http://127.0.0.1:1889', changeOrigin: true, diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 309e4e4d6..7d8b45bb4 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -14,7 +14,6 @@ const App: React.FC = () => { const handleLogin = async (formData: { userName: string; password: string }) => { let res = await userLogin(formData); if (res) { - console.log('res', res); window.location.href = '/'; } }; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 860fefe0f..57f62e6d8 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -118,9 +118,8 @@ request.interceptors.response.use(async (response, options) => { } const { errorCode, codeMessage } = res; if (errorCode === ErrorCode.NEED_LOGGED_IN) { - // window.location.href = '#/login?callback=' + window.location.hash.substr(1); const callback = window.location.hash.substr(1).split('?')[0]; - window.location.href = '#/login?' + (callback === '/login' ? '' : `callback=${callback}`); + window.location.href = '#/login' + (callback === '/login' ? '' : `?callback=${callback}`); } return response; diff --git a/chat2db-client/src/service/user.ts b/chat2db-client/src/service/user.ts index 46f7ae8f2..8c90f62ac 100644 --- a/chat2db-client/src/service/user.ts +++ b/chat2db-client/src/service/user.ts @@ -1,30 +1,24 @@ import createRequest from './base'; -import { IPageResponse, IPageParams } from '@/types'; -import { IUser } from '@/typings'; +import { IPageParams, IPageResponse } from '@/typings'; +import { IUser } from '@/typings/user'; /** 用户登录接口 */ -const userLogin = createRequest< - { userName: string; password: string }, - boolean ->('/oauth/login_a', { +const userLogin = createRequest<{ userName: string; password: string }, boolean>('/api/oauth/login_a', { method: 'post', }); /** 用户登出 */ -const userLogout = createRequest('/oauth/logout_a', { +const userLogout = createRequest('/api/oauth/logout_a', { method: 'post', }); /** 获取用户信息 */ -const getUser = createRequest('/oauth/user_a', { method: 'get' }); +const getUser = createRequest('/api/oauth/user_a', { method: 'get' }); /** 获取用户列表信息 */ -const getUserList = createRequest>( - '/api/user/list', - { - method: 'get', - }, -); +const getUserList = createRequest>('/api/user/list', { + method: 'get', +}); /** 创建新用户 */ const createUser = createRequest('/api/user/create', { @@ -46,13 +40,4 @@ const deleteUser = createRequest<{ id: number }>('/api/user/:id', { method: 'delete', }); -export { - createUser, - updateUser, - queryUserById, - deleteUser, - getUserList, - userLogin, - userLogout, - getUser, -}; +export { createUser, updateUser, queryUserById, deleteUser, getUserList, userLogin, userLogout, getUser }; diff --git a/chat2db-client/src/typings/user.ts b/chat2db-client/src/typings/user.ts new file mode 100644 index 000000000..514061d17 --- /dev/null +++ b/chat2db-client/src/typings/user.ts @@ -0,0 +1,10 @@ +export type IRole = 'admin' | 'normal'; +export interface IUser { + id?: number; + userName: string; + nickName: string; + password?: string; + password2?: string; + email: string; + role?: IRole; +} From efbaba0b7c8682bfa7ead9f4b97a3e6f3eb376d5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 19 Aug 2023 21:38:20 +0800 Subject: [PATCH 0656/1069] feat: Add setting module and bg image to login page --- chat2db-client/src/blocks/Setting/index.less | 14 ++++++------ chat2db-client/src/blocks/Setting/index.tsx | 12 +++++----- chat2db-client/src/pages/login/index.less | 23 ++++++++++++++++++++ chat2db-client/src/pages/login/index.tsx | 15 +++++++++++++ 4 files changed, 52 insertions(+), 12 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index fa1270082..b6f7bd7f0 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -11,14 +11,14 @@ } } -.setText { - color: var(--custom-primary); - font-size: 16px; +// .setText { +// color: var(--custom-primary); +// font-size: 16px; - &:hover { - text-decoration: underline; - } -} +// &:hover { +// text-decoration: underline; +// } +// } .title { font-size: 14px; diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index a2471bbde..3e40e9fb2 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { ReactNode, useEffect, useMemo, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import { Modal } from 'antd'; @@ -17,12 +17,13 @@ import styles from './index.less'; interface IProps { aiConfig: IAiConfig; className?: string; - text?: string; + render?: ReactNode; + // text?: string; dispatch: Function; } function Setting(props: IProps) { - const { className, text, dispatch } = props; + const { className, dispatch } = props; const [isModalVisible, setIsModalVisible] = useState(false); const [currentMenu, setCurrentMenu] = useState(0); @@ -93,11 +94,12 @@ function Setting(props: IProps) { return ( <>

- {text ? ( + {props.render ? props.render : } + {/* {text ? ( {text} ) : ( - )} + )} */}
{ + + } + className={styles.settingBtn} + > + 设 置 + + } + /> ); }; From 9e7d9084b6d64af1b3ca1350af153302799e593b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 19 Aug 2023 22:14:39 +0800 Subject: [PATCH 0657/1069] feat: Add logout function --- chat2db-client/src/pages/main/index.less | 2 +- chat2db-client/src/pages/main/index.tsx | 45 ++++++++++++++++++++++-- chat2db-client/src/service/user.ts | 4 +-- chat2db-client/src/typings/user.ts | 15 ++++++++ 4 files changed, 60 insertions(+), 6 deletions(-) diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 0692ee6b7..a55d202e4 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -95,7 +95,7 @@ .questionIcon { font-size: 20px; - margin-bottom: 8px; + margin-bottom: 16px; cursor: pointer; color: var(--custom-color-icon); &:hover { diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 56e187320..25ba0ca61 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -14,6 +14,9 @@ import Workspace from './workspace'; import Dashboard from './dashboard'; import styles from './index.less'; +import { getUser, userLogout } from '@/service/user'; +import { ILoginUser } from '@/typings/user'; +import { Dropdown } from 'antd'; const navConfig: INavItem[] = [ { @@ -58,9 +61,18 @@ interface IProps { } function MainPage(props: IProps) { - const { mainModel, workspaceModel, connectionModel, dispatch } = props; + const { mainModel, dispatch } = props; const { curPage } = mainModel; const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); + const [userInfo, setUserInfo] = useState(); + + useEffect(() => { + getUser().then((res) => { + if (res) { + setUserInfo(res); + } + }); + }, []); useEffect(() => { dispatch({ @@ -101,10 +113,36 @@ function MainPage(props: IProps) { } } + const handleLogout = () => { + userLogout().then((res) => { + setUserInfo(undefined); + window.location.href = '/#/login'; + }); + }; + + const renderUser = () => { + return ( + 退出登录, + }, + ], + }} + placement="bottomRight" + arrow={{ pointAtCenter: true }} + > + + + ); + }; + return (
- { }} className={styles.brandLogo} /> + {}} className={styles.brandLogo} />
    {navConfig.map((item, index) => { return ( @@ -129,7 +167,8 @@ function MainPage(props: IProps) { window.open('https://github.com/chat2db/chat2db/wiki'); }} /> */} - + {userInfo ? renderUser() : null} +
diff --git a/chat2db-client/src/service/user.ts b/chat2db-client/src/service/user.ts index 8c90f62ac..66ee1ef89 100644 --- a/chat2db-client/src/service/user.ts +++ b/chat2db-client/src/service/user.ts @@ -1,6 +1,6 @@ import createRequest from './base'; import { IPageParams, IPageResponse } from '@/typings'; -import { IUser } from '@/typings/user'; +import { ILoginUser, IUser } from '@/typings/user'; /** 用户登录接口 */ const userLogin = createRequest<{ userName: string; password: string }, boolean>('/api/oauth/login_a', { @@ -13,7 +13,7 @@ const userLogout = createRequest('/api/oauth/logout_a', { }); /** 获取用户信息 */ -const getUser = createRequest('/api/oauth/user_a', { method: 'get' }); +const getUser = createRequest('/api/oauth/user_a', { method: 'get' }); /** 获取用户列表信息 */ const getUserList = createRequest>('/api/user/list', { diff --git a/chat2db-client/src/typings/user.ts b/chat2db-client/src/typings/user.ts index 514061d17..7c11a48c0 100644 --- a/chat2db-client/src/typings/user.ts +++ b/chat2db-client/src/typings/user.ts @@ -8,3 +8,18 @@ export interface IUser { email: string; role?: IRole; } + +export interface ILoginUser { + /** + * Is it an administrator + */ + admin?: boolean; + /** + * 用户id + */ + id?: number; + /** + * 昵称 + */ + nickName?: string; +} From 54fbaef909043a03b7a85e4f2b5aa9eb6778e22a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 20 Aug 2023 15:24:55 +0800 Subject: [PATCH 0658/1069] CHANGELOG --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74be1c2a2..167fb7567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +# 2.0.13 + +## ⭐ New Features + +## 🐞 Bug Fixes +- Fixed a bug where sql formatting was not selected +- Fixed open view lag issue +- Solve the white screen problem of connected non-relational databases (non-relational databases are not supported) + +## ⭐ 新特性 + + + +## 🐞 问题修复 +- 修复不选中sql格式化的bug +- 修复打开视图卡顿问题 +- 解决已连接的非关系型数据库打开白屏问题(暂不支持非关系性数据库) + + # 2.0.12 ## ⭐ New Features From 5a77746b0a863dcace3815bf053ffca369965756 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 20 Aug 2023 23:07:58 +0800 Subject: [PATCH 0659/1069] feat: Staged team management code --- .vscode/settings.json | 1 + chat2db-client/src/pages/main/index.tsx | 14 +- .../team/datasource-management/index.less | 6 + .../main/team/datasource-management/index.tsx | 115 ++++++++++++ chat2db-client/src/pages/main/team/index.less | 4 + chat2db-client/src/pages/main/team/index.tsx | 54 ++++++ .../main/team/team-management/index.less | 6 + .../pages/main/team/team-management/index.tsx | 169 +++++++++++++++++ .../main/team/user-management/index.less | 6 + .../pages/main/team/user-management/index.tsx | 172 ++++++++++++++++++ chat2db-client/src/service/team.ts | 35 ++++ chat2db-client/src/typings/team.ts | 171 +++++++++++++++++ 12 files changed, 752 insertions(+), 1 deletion(-) create mode 100644 chat2db-client/src/pages/main/team/datasource-management/index.less create mode 100644 chat2db-client/src/pages/main/team/datasource-management/index.tsx create mode 100644 chat2db-client/src/pages/main/team/index.less create mode 100644 chat2db-client/src/pages/main/team/index.tsx create mode 100644 chat2db-client/src/pages/main/team/team-management/index.less create mode 100644 chat2db-client/src/pages/main/team/team-management/index.tsx create mode 100644 chat2db-client/src/pages/main/team/user-management/index.less create mode 100644 chat2db-client/src/pages/main/team/user-management/index.tsx create mode 100644 chat2db-client/src/service/team.ts create mode 100644 chat2db-client/src/typings/team.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index eace4f66d..118483391 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,7 @@ "ossutil", "pgsql", "pnpm", + "Popconfirm", "remaininguses", "RESTAI", "samuelmeuli", diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 25ba0ca61..f6bc56cf4 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -17,8 +17,9 @@ import styles from './index.less'; import { getUser, userLogout } from '@/service/user'; import { ILoginUser } from '@/typings/user'; import { Dropdown } from 'antd'; +import Team from './team'; -const navConfig: INavItem[] = [ +let navConfig: INavItem[] = [ { key: 'workspace', icon: '\ue616', @@ -64,12 +65,23 @@ function MainPage(props: IProps) { const { mainModel, dispatch } = props; const { curPage } = mainModel; const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); + // const [activeNav, setActiveNav] = useState(navConfig[4]); const [userInfo, setUserInfo] = useState(); useEffect(() => { getUser().then((res) => { if (res) { setUserInfo(res); + if (res.admin) { + navConfig.splice(3, 0, { + key: 'team', + icon: '\ue66d', + iconFontSize: 20, + isLoad: false, + component: , + }); + setActiveNav(navConfig[3]); + } } }); }, []); diff --git a/chat2db-client/src/pages/main/team/datasource-management/index.less b/chat2db-client/src/pages/main/team/datasource-management/index.less new file mode 100644 index 000000000..5f94a7858 --- /dev/null +++ b/chat2db-client/src/pages/main/team/datasource-management/index.less @@ -0,0 +1,6 @@ +.tableTop { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; +} diff --git a/chat2db-client/src/pages/main/team/datasource-management/index.tsx b/chat2db-client/src/pages/main/team/datasource-management/index.tsx new file mode 100644 index 000000000..6ce6d6af6 --- /dev/null +++ b/chat2db-client/src/pages/main/team/datasource-management/index.tsx @@ -0,0 +1,115 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Button, Input, Table, Popconfirm, message } from 'antd'; +import { IDataSourcePageQueryVO } from '@/typings/team'; +import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; +import styles from './index.less'; +import { getDataSourceList } from '@/service/team'; + +function DataSourceManagement() { + const [dataSource, setDataSource] = useState([]); + const [pagination, setPagination] = useState({ + searchKey: '', + current: 1, + pageSize: 10, + total: 0, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '30', '40'], + }); + const [isModalVisible, setIsModalVisible] = useState(false); + + const columns = useMemo( + () => [ + { + title: '链接名称', + dataIndex: 'alias', + key: 'alias', + }, + { + title: '链接地址', + dataIndex: 'url', + key: 'url', + }, + { + title: '操作', + key: 'action', + render: (_, record: any) => ( + handleDelete(record.id)} + okText="确认" + cancelText="取消" + > + e.preventDefault()}> + 删除 + + + ), + }, + ], + [], + ); + + useEffect(() => { + queryDataSourceList(); + }, [pagination.current, pagination.pageSize, pagination.searchKey]); + + const queryDataSourceList = async () => { + const { searchKey, current: pageNo, pageSize } = pagination; + let res = await getDataSourceList({ searchKey, pageNo, pageSize }); + if (res) { + setDataSource(res?.data ?? []); + } + }; + + const handleSearch = (searchKey: string) => { + setPagination({ + ...pagination, + searchKey, + }); + }; + + const handleTableChange = (p: any) => { + console.log('handleTableChange', p); + setPagination({ + ...pagination, + ...p, + }); + }; + + const handleDelete = async (recordId: number) => { + const success = true; // Replace with actual API response + + if (success) { + message.success('删除成功'); + queryDataSourceList(); + } else { + message.error('删除失败'); + } + }; + + return ( +
+
+ } + /> + +
+
+ + ); +} + +export default DataSourceManagement; diff --git a/chat2db-client/src/pages/main/team/index.less b/chat2db-client/src/pages/main/team/index.less new file mode 100644 index 000000000..3f464307f --- /dev/null +++ b/chat2db-client/src/pages/main/team/index.less @@ -0,0 +1,4 @@ +.teamWrapper { + height: 100vh; + padding: 24px 36px; +} diff --git a/chat2db-client/src/pages/main/team/index.tsx b/chat2db-client/src/pages/main/team/index.tsx new file mode 100644 index 000000000..12f611a97 --- /dev/null +++ b/chat2db-client/src/pages/main/team/index.tsx @@ -0,0 +1,54 @@ +import React, { useMemo, useState } from 'react'; +import { ApiOutlined, UserOutlined, TeamOutlined } from '@ant-design/icons'; +import { Tabs } from 'antd'; +import styles from './index.less'; +import DataSourceManagement from './datasource-management'; +import UserManagement from './user-management'; +import TeamManagement from './team-management'; + +const Team = () => { + const [activeKey, setActiveKey] = useState('0'); + const tabList = useMemo( + () => [ + { + label: '共享链接管理', + icon: , + children: , + }, + { + label: '用户管理', + icon: , + children: , + }, + { + label: '团队管理', + icon: , + children: , + }, + ], + [], + ); + + return ( +
+ setActiveKey(activeKey)} + items={tabList.map((tab, index) => { + return { + key: String(index), + label: ( + + {tab.icon} + {tab.label} + + ), + children: tab.children, + }; + })} + /> +
+ ); +}; + +export default Team; diff --git a/chat2db-client/src/pages/main/team/team-management/index.less b/chat2db-client/src/pages/main/team/team-management/index.less new file mode 100644 index 000000000..5f94a7858 --- /dev/null +++ b/chat2db-client/src/pages/main/team/team-management/index.less @@ -0,0 +1,6 @@ +.tableTop { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; +} diff --git a/chat2db-client/src/pages/main/team/team-management/index.tsx b/chat2db-client/src/pages/main/team/team-management/index.tsx new file mode 100644 index 000000000..99bd280da --- /dev/null +++ b/chat2db-client/src/pages/main/team/team-management/index.tsx @@ -0,0 +1,169 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { createTeam, getTeamManagementList } from '@/service/team'; +import { ITeamPageQueryVO, ITeamVO, RoleStatusType, TeamStatusType } from '@/typings/team'; +import { Button, Form, Input, Modal, Radio, Table, Tag } from 'antd'; +import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; +import styles from './index.less'; + +const formItemLayout = { + labelCol: { span: 6 }, + wrapperCol: { span: 16 }, + colon: false, +}; + +const requireRule = { required: true, message: 'Require field empty!' }; + +function TeamManagement() { + const [form] = Form.useForm(); + const [dataSource, setDataSource] = useState([]); + const [pagination, setPagination] = useState({ + searchKey: '', + current: 1, + pageSize: 10, + total: 0, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '30', '40'], + }); + const [isModalVisible, setIsModalVisible] = useState(false); + const columns = useMemo( + () => [ + { + title: '团队编码', + dataIndex: 'code', + key: 'code', + }, + { + title: '团队名', + dataIndex: 'name', + key: 'name', + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: TeamStatusType) => ( + {status} + ), + }, + ], + [], + ); + + useEffect(() => { + queryTeamList(); + }, [pagination.current, pagination.pageSize, pagination.searchKey]); + + const queryTeamList = async () => { + const { searchKey, current: pageNo, pageSize } = pagination; + let res = await getTeamManagementList({ searchKey, pageNo, pageSize }); + if (res) { + setDataSource(res?.data ?? []); + } + }; + + const handleTableChange = (p: any) => { + console.log('handleTableChange', p); + setPagination({ + ...pagination, + ...p, + }); + }; + + const handleSearch = (searchKey: string) => { + setPagination({ + ...pagination, + searchKey, + }); + }; + + const handleCreateTeam = async (teamInfo: ITeamVO) => { + let res = await createTeam(teamInfo); + if (res) { + queryTeamList(); + } + }; + + return ( +
+
+ } + /> + +
+
+ { + form + .validateFields() + .then((values) => { + console.log('Form values:', JSON.stringify(values)); + handleCreateTeam(values); + setIsModalVisible(false); + form.resetFields(); + }) + .catch((errorInfo) => { + console.log('Validation failed:', errorInfo); + form.scrollToField(errorInfo.errorFields[0].name); + form.setFields(errorInfo.errorFields); + }) + .finally(() => { + form.resetFields(); + }); + }} + onCancel={() => { + form.resetFields(); + setIsModalVisible(false); + }} + > +
+ + + + + + + + + 管理员 + 用户 + + + + + 有效 + 无效 + + + + + + +
+ + ); +} + +export default TeamManagement; diff --git a/chat2db-client/src/pages/main/team/user-management/index.less b/chat2db-client/src/pages/main/team/user-management/index.less new file mode 100644 index 000000000..5f94a7858 --- /dev/null +++ b/chat2db-client/src/pages/main/team/user-management/index.less @@ -0,0 +1,6 @@ +.tableTop { + display: flex; + justify-content: space-between; + align-items: center; + margin: 16px 0; +} diff --git a/chat2db-client/src/pages/main/team/user-management/index.tsx b/chat2db-client/src/pages/main/team/user-management/index.tsx new file mode 100644 index 000000000..23e411c24 --- /dev/null +++ b/chat2db-client/src/pages/main/team/user-management/index.tsx @@ -0,0 +1,172 @@ +import { createUser, getUserManagementList } from '@/service/team'; +import { IUserPageQueryVO, IUserStatus, IUserVO, RoleStatusType, UserStatusType } from '@/typings/team'; +import { Button, Form, Input, Modal, Radio, Table, Tag } from 'antd'; +import React, { useEffect, useMemo, useState } from 'react'; +import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; +import styles from './index.less'; + +const formItemLayout = { + labelCol: { span: 6 }, + wrapperCol: { span: 16 }, + colon: false, +}; + +const requireRule = { required: true, message: 'Require field empty!' }; + +function UserManagement() { + const [form] = Form.useForm(); + const [dataSource, setDataSource] = useState([]); + const [pagination, setPagination] = useState({ + searchKey: '', + current: 1, + pageSize: 10, + total: 0, + showSizeChanger: true, + showQuickJumper: true, + pageSizeOptions: ['10', '20', '30', '40'], + }); + const [isModalVisible, setIsModalVisible] = useState(false); + + const columns = useMemo( + () => [ + { + title: '用户名', + dataIndex: 'userName', + key: 'userName', + }, + { + title: '昵称', + dataIndex: 'nickName', + key: 'nickName', + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + render: (status: IUserStatus) => {status}, + }, + ], + [], + ); + + useEffect(() => { + queryUserList(); + }, [pagination.current, pagination.pageSize, pagination.searchKey]); + + const queryUserList = async () => { + const { searchKey, current: pageNo, pageSize } = pagination; + let res = await getUserManagementList({ searchKey, pageNo, pageSize }); + if (res) { + setDataSource(res?.data ?? []); + } + }; + + const handleTableChange = (p: any) => { + console.log('handleTableChange', p); + setPagination({ + ...pagination, + ...p, + }); + }; + + const handleSearch = (searchKey: string) => { + setPagination({ + ...pagination, + searchKey, + }); + }; + + const handleCreateUser = async (userInfo: IUserVO) => { + let res = await createUser(userInfo); + if (res) { + queryUserList(); + } + }; + + return ( +
+
+ } + /> + +
+
+ + { + form + .validateFields() + .then((values) => { + console.log('Form values:', JSON.stringify(values)); + handleCreateUser(values); + setIsModalVisible(false); + form.resetFields(); + }) + .catch((errorInfo) => { + console.log('Validation failed:', errorInfo); + form.scrollToField(errorInfo.errorFields[0].name); + form.setFields(errorInfo.errorFields); + }) + .finally(() => { + form.resetFields(); + }); + }} + onCancel={() => { + form.resetFields(); + setIsModalVisible(false); + }} + > +
+ + + + + + + + + + + + + + + 管理员 + 用户 + + + + + 有效 + 无效 + + + +
+ + ); +} + +export default UserManagement; diff --git a/chat2db-client/src/service/team.ts b/chat2db-client/src/service/team.ts new file mode 100644 index 000000000..edb694b57 --- /dev/null +++ b/chat2db-client/src/service/team.ts @@ -0,0 +1,35 @@ +import createRequest from './base'; +import { IPageParams, IPageResponse } from '@/typings'; +import { IDataSourcePageQueryVO, ITeamPageQueryVO, ITeamVO, IUserPageQueryVO, IUserVO } from '@/typings/team'; + +/** + * 获取共享链接列表 + */ +const getDataSourceList = createRequest>( + '/api/admin/data_source/page', + { + method: 'get', + }, +); + +/** 用户管理列表查询 */ +const getUserManagementList = createRequest>('/api/admin/user/page', { + method: 'get', +}); + +/** 创建用户 */ +const createUser = createRequest('/api/admin/user/create', { + method: 'post', +}); + +/** 团队-团队管理列表查询 */ +const getTeamManagementList = createRequest>('/api/admin/team/page', { + method: 'get', +}); + +/** 团队-创建团队 */ +const createTeam = createRequest('/api/admin/team/create', { + method: 'post', +}); + +export { getDataSourceList, getUserManagementList, createUser, getTeamManagementList, createTeam }; diff --git a/chat2db-client/src/typings/team.ts b/chat2db-client/src/typings/team.ts new file mode 100644 index 000000000..08451fa3f --- /dev/null +++ b/chat2db-client/src/typings/team.ts @@ -0,0 +1,171 @@ +// ===================== DataSource ================== +/** + * Pagination query + * + * DataSourcePageQueryVO + */ +export interface IDataSourcePageQueryVO { + /** + * 连接别名 + */ + alias?: string; + /** + * 环境 + */ + environment?: ISimpleEnvironmentVO; + /** + * 环境id + */ + environmentId?: number; + /** + * 主键id + */ + id?: number; + /** + * 连接地址 + */ + url?: string; +} + +/** + * 环境 + * + * SimpleEnvironmentVO + */ +export interface ISimpleEnvironmentVO { + /** + * 主键 + */ + id?: number; + /** + * 环境名称 + */ + name?: string; + /** + * 环境缩写 + */ + shortName?: string; + /** + * 样式类型 + */ + style?: StyleType; +} +/** + * 样式类型 + */ +export enum StyleType { + Release = 'RELEASE', + Test = 'TEST', +} + +// ===================== User ====================== +export enum RoleStatusType { + ADMIN = 'ADMIN', + USER = 'USER', +} +export enum UserStatusType { + INVALID = 'INVALID', + VALID = 'VALID', +} +export type IRoleStatus = keyof typeof RoleStatusType; +export type IUserStatus = keyof typeof UserStatusType; +/** + * Pagination query + * + * UserPageQueryVO + */ +export interface IUserPageQueryVO { + /** + * 主键 + */ + id: number; + /** + * 昵称 + */ + nickName: string; + /** + * 用户状态 + */ + status?: IUserStatus; + /** + * 用户名 + */ + userName: string; +} + +/** + * UserCreateRequest + */ +export interface IUserVO { + /** + * 邮箱 + */ + email: string; + /** + * 昵称 + */ + nickName: string; + /** + * 密码 + */ + password: string; + /** + * 角色编码 + */ + roleCode: IRoleStatus; + /** + * 用户状态 + */ + status: IUserStatus; + /** + * 用户名 + */ + userName: string; +} + +// ===================== Team ===================== +export enum TeamStatusType { + INVALID = 'INVALID', + VALID = 'VALID', +} +export interface ITeamPageQueryVO { + /** + * 团队编码 + */ + code?: string; + /** + * 主键 + */ + id?: number; + /** + * 团队名称 + */ + name?: string; + /** + * 团队状态 + */ + status?: TeamStatusType; +} + +export interface ITeamVO { + /** + * 团队编码 + */ + code: string; + /** + * 团队描述 + */ + description?: string; + /** + * 团队名称 + */ + name: string; + /** + * 角色编码 + */ + roleCode: string; + /** + * 团队状态 + */ + status: TeamStatusType; +} From 9854097d024a6e7a1213b609631bd2d883c41e85 Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" <1558143046@qq.com> Date: Mon, 21 Aug 2023 20:27:58 +0800 Subject: [PATCH 0660/1069] test --- chat2db-client/src/pages/main/team/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/pages/main/team/index.tsx b/chat2db-client/src/pages/main/team/index.tsx index 12f611a97..c6aa0f5b0 100644 --- a/chat2db-client/src/pages/main/team/index.tsx +++ b/chat2db-client/src/pages/main/team/index.tsx @@ -7,7 +7,7 @@ import UserManagement from './user-management'; import TeamManagement from './team-management'; const Team = () => { - const [activeKey, setActiveKey] = useState('0'); + const [activeKey, setActiveKey] = useState('2'); const tabList = useMemo( () => [ { From a4b6844d563239c0822b28a04342aba48f352e10 Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" <1558143046@qq.com> Date: Mon, 21 Aug 2023 20:32:51 +0800 Subject: [PATCH 0661/1069] test --- chat2db-client/src/pages/main/team/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/src/pages/main/team/index.tsx b/chat2db-client/src/pages/main/team/index.tsx index c6aa0f5b0..beaa41658 100644 --- a/chat2db-client/src/pages/main/team/index.tsx +++ b/chat2db-client/src/pages/main/team/index.tsx @@ -8,6 +8,7 @@ import TeamManagement from './team-management'; const Team = () => { const [activeKey, setActiveKey] = useState('2'); + const tabList = useMemo( () => [ { From 68df2c2e73894793f507bd74a76293d12ba48f96 Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" Date: Mon, 21 Aug 2023 20:37:04 +0800 Subject: [PATCH 0662/1069] test --- chat2db-client/src/pages/main/team/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/chat2db-client/src/pages/main/team/index.tsx b/chat2db-client/src/pages/main/team/index.tsx index beaa41658..c6aa0f5b0 100644 --- a/chat2db-client/src/pages/main/team/index.tsx +++ b/chat2db-client/src/pages/main/team/index.tsx @@ -8,7 +8,6 @@ import TeamManagement from './team-management'; const Team = () => { const [activeKey, setActiveKey] = useState('2'); - const tabList = useMemo( () => [ { From 6e522bd677ab59a0eb8f28475c33ed243643ae14 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 21 Aug 2023 23:02:41 +0800 Subject: [PATCH 0663/1069] feat: Stage team management code --- .../pages/main/team/team-management/index.tsx | 96 +++++++++-- .../main/team/universal-add-modal/index.less | 0 .../main/team/universal-add-modal/index.tsx | 75 +++++++++ .../main/team/universal-drawer/index.less | 6 + .../main/team/universal-drawer/index.tsx | 155 ++++++++++++++++++ .../pages/main/team/user-management/index.tsx | 16 +- chat2db-client/src/service/team.ts | 45 ++++- chat2db-client/src/styles/antd.less | 1 + chat2db-client/src/typings/team.ts | 75 ++++----- 9 files changed, 400 insertions(+), 69 deletions(-) create mode 100644 chat2db-client/src/pages/main/team/universal-add-modal/index.less create mode 100644 chat2db-client/src/pages/main/team/universal-add-modal/index.tsx create mode 100644 chat2db-client/src/pages/main/team/universal-drawer/index.less create mode 100644 chat2db-client/src/pages/main/team/universal-drawer/index.tsx diff --git a/chat2db-client/src/pages/main/team/team-management/index.tsx b/chat2db-client/src/pages/main/team/team-management/index.tsx index 99bd280da..5f9b46f74 100644 --- a/chat2db-client/src/pages/main/team/team-management/index.tsx +++ b/chat2db-client/src/pages/main/team/team-management/index.tsx @@ -1,9 +1,10 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { createTeam, getTeamManagementList } from '@/service/team'; -import { ITeamPageQueryVO, ITeamVO, RoleStatusType, TeamStatusType } from '@/typings/team'; -import { Button, Form, Input, Modal, Radio, Table, Tag } from 'antd'; +import { createTeam, deleteTeam, getTeamManagementList, updateTeam } from '@/service/team'; +import { ITeamPageQueryVO, ITeamVO, ManagementType, StatusType } from '@/typings/team'; +import { Button, Form, Input, Modal, Popconfirm, Radio, Table, Tag, message } from 'antd'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import styles from './index.less'; +import UniversalDrawer from '../universal-drawer'; const formItemLayout = { labelCol: { span: 6 }, @@ -23,9 +24,13 @@ function TeamManagement() { total: 0, showSizeChanger: true, showQuickJumper: true, - pageSizeOptions: ['10', '20', '30', '40'], + // pageSizeOptions: ['10', '20', '30', '40'], }); const [isModalVisible, setIsModalVisible] = useState(false); + const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: ManagementType; teamId?: number }>({ + open: false, + type: ManagementType.USER, + }); const columns = useMemo( () => [ { @@ -42,8 +47,38 @@ function TeamManagement() { title: '状态', dataIndex: 'status', key: 'status', - render: (status: TeamStatusType) => ( - {status} + render: (status: StatusType) => {status}, + }, + { + title: '操作', + key: 'action', + width: 260, + render: (_: any, record: ITeamVO) => ( + <> + + + + handleDelete(record.id)} okText="确认" cancelText="取消"> + e.preventDefault()}> + 删除 + + + ), }, ], @@ -77,13 +112,27 @@ function TeamManagement() { }); }; - const handleCreateTeam = async (teamInfo: ITeamVO) => { - let res = await createTeam(teamInfo); + const handleCreateOrUpdateTeam = async (teamInfo: ITeamVO) => { + const requestApi = teamInfo.id ? updateTeam : createTeam; + let res = await requestApi(teamInfo); if (res) { queryTeamList(); } }; + const handleEdit = (record: ITeamVO) => { + form.setFieldsValue(record); + setIsModalVisible(true); + }; + + const handleDelete = async (id?: number) => { + if (id !== undefined) { + await deleteTeam({ id }); + message.success('删除成功'); + queryTeamList(); + } + }; + return (
@@ -94,7 +143,7 @@ function TeamManagement() { enterButton={} />
{ form .validateFields() .then((values) => { - console.log('Form values:', JSON.stringify(values)); - handleCreateTeam(values); + const formValues = form.getFieldsValue(true); + handleCreateOrUpdateTeam(formValues); setIsModalVisible(false); form.resetFields(); }) @@ -135,8 +184,8 @@ function TeamManagement() { form={form} autoComplete={'off'} initialValues={{ - roleCode: RoleStatusType.USER, - status: TeamStatusType.VALID, + // roleCode: RoleStatusType.USER, + status: StatusType.VALID, }} > @@ -145,16 +194,16 @@ function TeamManagement() { - + {/* 管理员 用户 - + */} - 有效 - 无效 + 有效 + 无效 @@ -162,6 +211,17 @@ function TeamManagement() { + + { + setDrawerInfo({ + ...drawerInfo, + open: false, + }); + }} + /> ); } diff --git a/chat2db-client/src/pages/main/team/universal-add-modal/index.less b/chat2db-client/src/pages/main/team/universal-add-modal/index.less new file mode 100644 index 000000000..e69de29bb diff --git a/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx b/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx new file mode 100644 index 000000000..cc09872aa --- /dev/null +++ b/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx @@ -0,0 +1,75 @@ +import { getCommonUserList } from '@/service/team'; +import { IUserPageQueryVO, ManagementType } from '@/typings/team'; +import { Input, Modal, Select, Spin } from 'antd'; +import debounce from 'lodash/debounce'; +import React, { useMemo, useState } from 'react'; +import styles from './index.less'; + +interface IProps { + open: boolean; + + type: ManagementType; + + onClose: () => void; +} + +interface ValueType { + key: number; + + label: React.ReactNode; + + value: number; +} + +function UniversalAddModal(props: IProps) { + const { open } = props; + + const [fetching, setFetching] = useState(false); + + const [options, setOptions] = useState([]); + + const loadOptions = (value: string) => { + setOptions([]); + + setFetching(true); + + getCommonUserList({ searchKey: value }).then((res) => { + const newOptions = (res || []).map((i) => ({ + value: i.id, + + label: i.userName, + + key: i.id, + })); + + console.log('newOptions', newOptions); + + setOptions(newOptions); + + setFetching(false); + }); + }; + + return ( + { + props.onClose && props.onClose(); + }} + title="添加xx" + > +
+ + { + setModalInfo({ + ...modalInfo, + open: false, + }); + }} + /> + + ); +} + +export default UniversalDrawer; diff --git a/chat2db-client/src/pages/main/team/user-management/index.tsx b/chat2db-client/src/pages/main/team/user-management/index.tsx index 23e411c24..0b603e4d0 100644 --- a/chat2db-client/src/pages/main/team/user-management/index.tsx +++ b/chat2db-client/src/pages/main/team/user-management/index.tsx @@ -1,5 +1,5 @@ import { createUser, getUserManagementList } from '@/service/team'; -import { IUserPageQueryVO, IUserStatus, IUserVO, RoleStatusType, UserStatusType } from '@/typings/team'; +import { IUserPageQueryVO, IUserVO, RoleType, StatusType } from '@/typings/team'; import { Button, Form, Input, Modal, Radio, Table, Tag } from 'antd'; import React, { useEffect, useMemo, useState } from 'react'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; @@ -43,7 +43,7 @@ function UserManagement() { title: '状态', dataIndex: 'status', key: 'status', - render: (status: IUserStatus) => {status}, + render: (status: StatusType) => {status}, }, ], [], @@ -135,8 +135,8 @@ function UserManagement() { form={form} autoComplete={'off'} initialValues={{ - roleCode: RoleStatusType.USER, - status: UserStatusType.VALID, + roleCode: RoleType.USER, + status: StatusType.VALID, }} > @@ -153,14 +153,14 @@ function UserManagement() { - 管理员 - 用户 + 管理员 + 用户 - 有效 - 无效 + 有效 + 无效 diff --git a/chat2db-client/src/service/team.ts b/chat2db-client/src/service/team.ts index edb694b57..6128cdf6a 100644 --- a/chat2db-client/src/service/team.ts +++ b/chat2db-client/src/service/team.ts @@ -1,6 +1,13 @@ import createRequest from './base'; import { IPageParams, IPageResponse } from '@/typings'; -import { IDataSourcePageQueryVO, ITeamPageQueryVO, ITeamVO, IUserPageQueryVO, IUserVO } from '@/typings/team'; +import { + IDataSourcePageQueryVO, + ITeamPageQueryVO, + ITeamVO, + IUserPageQueryVO, + IUserVO, + TeamUserPageQueryVO, +} from '@/typings/team'; /** * 获取共享链接列表 @@ -31,5 +38,39 @@ const getTeamManagementList = createRequest('/api/admin/team/create', { method: 'post', }); +const updateTeam = createRequest('/api/admin/team/update', { + method: 'post', +}); +const deleteTeam = createRequest<{ id: number }, {}>('/api/admin/team/:id', { + method: 'delete', +}); + +/** 团队-团队管理中获取包含用户列表 */ +const getUserListFromTeam = createRequest>( + '/api/admin/team/user/page', + { + method: 'get', + }, +); +const deleteUserFromTeam = createRequest<{ id: number }, {}>('/api/admin/team/user/:id', { + method: 'delete', +}); + +// ======================= 通用列表 ===================== +/** 通用-获取user列表 */ +const getCommonUserList = createRequest<{ searchKey: string }, IUserPageQueryVO[]>('/api/admin/common/user/list', { + method: 'get', +}); -export { getDataSourceList, getUserManagementList, createUser, getTeamManagementList, createTeam }; +export { + getDataSourceList, + getUserManagementList, + createUser, + getTeamManagementList, + createTeam, + updateTeam, + deleteTeam, + getUserListFromTeam, + deleteUserFromTeam, + getCommonUserList, +}; diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index 6c6235e77..c58674e4c 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -16,6 +16,7 @@ } .ant-modal-footer { border-top: 0px; + padding: 8px; } // .ant-notification-notice { diff --git a/chat2db-client/src/typings/team.ts b/chat2db-client/src/typings/team.ts index 08451fa3f..c0b06228c 100644 --- a/chat2db-client/src/typings/team.ts +++ b/chat2db-client/src/typings/team.ts @@ -1,9 +1,22 @@ +// ===================== Universal ================== +export enum ManagementType { + DATASOURCE = 'DATASOURCE', + TEAM = 'TEAM', + USER = 'USER', +} + +export enum StatusType { + INVALID = 'INVALID', + VALID = 'VALID', +} + +export enum RoleType { + ADMIN = 'ADMIN', + USER = 'USER', +} + // ===================== DataSource ================== -/** - * Pagination query - * - * DataSourcePageQueryVO - */ + export interface IDataSourcePageQueryVO { /** * 连接别名 @@ -27,11 +40,6 @@ export interface IDataSourcePageQueryVO { url?: string; } -/** - * 环境 - * - * SimpleEnvironmentVO - */ export interface ISimpleEnvironmentVO { /** * 主键 @@ -59,21 +67,6 @@ export enum StyleType { } // ===================== User ====================== -export enum RoleStatusType { - ADMIN = 'ADMIN', - USER = 'USER', -} -export enum UserStatusType { - INVALID = 'INVALID', - VALID = 'VALID', -} -export type IRoleStatus = keyof typeof RoleStatusType; -export type IUserStatus = keyof typeof UserStatusType; -/** - * Pagination query - * - * UserPageQueryVO - */ export interface IUserPageQueryVO { /** * 主键 @@ -86,7 +79,7 @@ export interface IUserPageQueryVO { /** * 用户状态 */ - status?: IUserStatus; + status?: StatusType; /** * 用户名 */ @@ -112,11 +105,11 @@ export interface IUserVO { /** * 角色编码 */ - roleCode: IRoleStatus; + roleCode: RoleType; /** * 用户状态 */ - status: IUserStatus; + status: StatusType; /** * 用户名 */ @@ -124,30 +117,28 @@ export interface IUserVO { } // ===================== Team ===================== -export enum TeamStatusType { - INVALID = 'INVALID', - VALID = 'VALID', -} + export interface ITeamPageQueryVO { /** * 团队编码 */ - code?: string; + code: string; /** * 主键 */ - id?: number; + id: number; /** * 团队名称 */ - name?: string; + name: string; /** * 团队状态 */ - status?: TeamStatusType; + status: StatusType; } export interface ITeamVO { + id?: number; /** * 团队编码 */ @@ -160,12 +151,14 @@ export interface ITeamVO { * 团队名称 */ name: string; - /** - * 角色编码 - */ - roleCode: string; /** * 团队状态 */ - status: TeamStatusType; + status: StatusType; +} + +export interface TeamUserPageQueryVO { + id: number; + teamId: number; + user: IUserVO; } From 9bc052ba70973be80af8faf4c7d3d733bbe7cf38 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Wed, 23 Aug 2023 15:09:57 +0800 Subject: [PATCH 0664/1069] Basic interface --- .../server/common/api/controller/CommonCommonController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java index b7fc56e6e..327a2aab6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java @@ -12,7 +12,7 @@ import org.springframework.web.bind.annotation.RestController; /** - * Some general data queries + * Basic interface * * @author Jiaju Zhuang */ From d85287021d9a00268d49938bc140f86bfcf7d424 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 26 Aug 2023 10:13:43 +0800 Subject: [PATCH 0665/1069] =?UTF-8?q?feat:=20=E8=BF=9E=E6=8E=A5=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=8E=AF=E5=A2=83=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 1 + .../ConnectionEdit/config/dataSource.ts | 12 +++++++ .../src/components/ConnectionEdit/index.tsx | 35 ++++++++++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 118483391..a886d6e3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,6 +47,7 @@ "togglefullscreen", "umijs", "umirc", + "USERANDPASSWORD", "uuidv", "VIEWCOLUMN", "VIEWCOLUMNS", diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 224b04d02..25c747ef6 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -143,6 +143,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ name: 'alias', required: true, }, + { + defaultValue: '', + inputType: InputType.SELECT, + labelNameCN: '环境', + labelNameEN: 'Env', + name: 'env', + required: true, + selects: [], + styles: { + width: '50%', + } + }, { defaultValue: 'localhost', inputType: InputType.INPUT, diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index aac255119..d1b3f6014 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -53,15 +53,40 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde backfillDataLoading: false, sshTestLoading: false }); + const [envList, setEnvList] = useState<{value:string,label:string}[]>([]); + + + useEffect(() => { + setTimeout(() => { + setEnvList([ + { + value: 'prod', + label: '生产环境' + }, + { + value: 'daily', + label: '日常环境' + }, + { + value: 'pre', + label: '预发环境' + }, + ]) + }, 3000); + }, []); const dataSourceFormConfigPropsMemo = useMemo(() => { const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) - return deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { - const flag = t.type === backfillData.type; - return flag + const data = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { + return t.type === backfillData.type }); - - }, [backfillData]); + data.baseInfo.items.forEach((t: IFormItem) => { + if (t.name === 'env' && envList?.length) { + t.selects = envList; + } + }) + return data; + }, [backfillData, envList]); useEffect(() => { setBackfillData(props.connectionData); From 3aa014cd627bf5b3a832d8d073f99dc592034d3f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 26 Aug 2023 10:16:08 +0800 Subject: [PATCH 0666/1069] feat: Added connection environment selection --- .vscode/settings.json | 1 + .../ConnectionEdit/config/dataSource.ts | 12 +++++++ .../src/components/ConnectionEdit/index.tsx | 35 ++++++++++++++++--- 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 118483391..a886d6e3c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -47,6 +47,7 @@ "togglefullscreen", "umijs", "umirc", + "USERANDPASSWORD", "uuidv", "VIEWCOLUMN", "VIEWCOLUMNS", diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 224b04d02..25c747ef6 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -143,6 +143,18 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ name: 'alias', required: true, }, + { + defaultValue: '', + inputType: InputType.SELECT, + labelNameCN: '环境', + labelNameEN: 'Env', + name: 'env', + required: true, + selects: [], + styles: { + width: '50%', + } + }, { defaultValue: 'localhost', inputType: InputType.INPUT, diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index aac255119..d1b3f6014 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -53,15 +53,40 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde backfillDataLoading: false, sshTestLoading: false }); + const [envList, setEnvList] = useState<{value:string,label:string}[]>([]); + + + useEffect(() => { + setTimeout(() => { + setEnvList([ + { + value: 'prod', + label: '生产环境' + }, + { + value: 'daily', + label: '日常环境' + }, + { + value: 'pre', + label: '预发环境' + }, + ]) + }, 3000); + }, []); const dataSourceFormConfigPropsMemo = useMemo(() => { const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) - return deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { - const flag = t.type === backfillData.type; - return flag + const data = deepCloneDataSourceFormConfigs.find((t: IConnectionConfig) => { + return t.type === backfillData.type }); - - }, [backfillData]); + data.baseInfo.items.forEach((t: IFormItem) => { + if (t.name === 'env' && envList?.length) { + t.selects = envList; + } + }) + return data; + }, [backfillData, envList]); useEffect(() => { setBackfillData(props.connectionData); From 0e19e42632bde3522af4b9c2747068522d48406d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 26 Aug 2023 10:34:07 +0800 Subject: [PATCH 0667/1069] fix:Chain call --- chat2db-client/src/components/ConnectionEdit/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 2c0b1ce44..518ab6b61 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -62,7 +62,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde function getEnvList() { connectionService.getEnvList().then((res) => { - setEnvList(res.map((t) => { + setEnvList(res?.map(t => { return { value: t.code, label: t.name From 2f8cfed77927a071099c254e56d471893998d110 Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" Date: Sat, 26 Aug 2023 11:24:15 +0800 Subject: [PATCH 0668/1069] feat: Optimize i18n and style --- .../src/blocks/CreateConnection/index.less | 107 +----------------- .../src/blocks/CreateConnection/index.tsx | 6 +- chat2db-client/src/i18n/en-us/common.ts | 3 + chat2db-client/src/i18n/zh-cn/common.ts | 5 +- 4 files changed, 15 insertions(+), 106 deletions(-) diff --git a/chat2db-client/src/blocks/CreateConnection/index.less b/chat2db-client/src/blocks/CreateConnection/index.less index 05684ca2d..3e89f8ccf 100644 --- a/chat2db-client/src/blocks/CreateConnection/index.less +++ b/chat2db-client/src/blocks/CreateConnection/index.less @@ -4,101 +4,7 @@ display: flex; align-items: center; justify-content: center; -} - -.layoutLeft { - flex-shrink: 0; - display: flex; - flex-direction: column; - width: 220px; - overflow: hidden; - background-color: var(--color-bg-elevated); - border-right: 1px solid var(--color-border-secondary); - border-top: 0px; - border-bottom: 0px; -} - -.pageTitle { - font-size: 20px; - line-height: 24px; - font-weight: 500; - margin: 20px 0px 10px; - padding-left: 20px; -} - -.menuBox { - flex: 1; - 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; - align-items: center; - cursor: pointer; - padding: 8px; - margin-bottom: 4px; - height: 20px; - border-radius: 8px; - user-select: none; - - .menuItemsTitle { - flex: 1; - width: 0; - .f-single-line(); - } - - .moreButton { - flex-shrink: 0; - display: none; - transform: rotate(90deg); - } - - &:hover { - background-color: var(--color-hover-bg); - .moreButton { - display: block; - } - } - } - - .menuItemActive { - color: var(--color-primary); - // background-color: var(--color-primary-bg); - background-color: var(--color-hover-bg); - } - - :global { - .ant-menu-inline { - border-inline-end: none !important; - } - - .ant-menu-item { - padding-left: 14px !important; - } - } -} - -.addConnection { - margin: 0 20px 10px; -} - -.layoutRight { - flex: 1; - overflow: auto; - display: flex; - justify-content: center; - align-items: center; - position: relative; + min-height: 100%; } .dataBaseList { @@ -135,6 +41,7 @@ .databaseItemRight { display: none; + i { font-size: 16px; } @@ -148,6 +55,7 @@ .databaseItemRight { display: block; + i { color: var(--color-primary); } @@ -178,12 +86,6 @@ } .createConnections { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: -1; background-color: var(--color-bg); overflow-y: auto; transform: scale(0.2); @@ -191,7 +93,6 @@ } .showCreateConnections { - z-index: 1; transform: scale(1); transition: transform 0.3s ease-in-out; -} +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/CreateConnection/index.tsx b/chat2db-client/src/blocks/CreateConnection/index.tsx index 9ea4c1864..4842b46e3 100644 --- a/chat2db-client/src/blocks/CreateConnection/index.tsx +++ b/chat2db-client/src/blocks/CreateConnection/index.tsx @@ -21,6 +21,8 @@ export default memo(function CreateConnection(props) { useEffect(() => { if (connectionDetail) { setCurConnection(connectionDetail) + } else { + setCurConnection({}) } }, [connectionDetail]) @@ -35,7 +37,7 @@ export default memo(function CreateConnection(props) { }); } - function handelSubmit(data: IConnectionDetails) { + function handleSubmit(data: IConnectionDetails) { onSubmit?.(data) } @@ -53,7 +55,7 @@ export default memo(function CreateConnection(props) { setCurConnection({}); }} connectionData={curConnection as any} - submit={handelSubmit} + submit={handleSubmit} /> } diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 96bf9faf3..8812789a9 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -73,4 +73,7 @@ export default { 'common.text.saveConsole': 'Save console', 'common.text.textToSQL': 'Plain text to SQL', 'common.text.editorRightClick': 'Editor right click', + 'common.form.error.required': 'This field is required!', + 'common.form.error.email': 'The input is not a valid email!', + 'common.tips.delete.confirm': 'Are you sure to delete it?' }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 4610e36ae..6beb8eec2 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -2,7 +2,7 @@ export default { 'common.text.no': '否', 'common.text.is': '是', 'common.button.affirm': '确认', - 'common.button.edit': '修改', + 'common.button.edit': '编辑', 'common.button.confirm': '确认', 'common.button.cancel': '取消', 'common.data.hour': '{1}小时', @@ -71,4 +71,7 @@ export default { 'common.text.saveConsole': '保存控制台', 'common.text.textToSQL': '纯文本转SQL', 'common.text.editorRightClick': '编辑器右键', + 'common.form.error.required': '此项必填!', + 'common.form.error.email': '不是正确的邮箱格式', + 'common.tips.delete.confirm': '确认要删除吗?' }; From d108cb234c78ef023f7b4194a95cb3abcec6e7d3 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 14:00:39 +0800 Subject: [PATCH 0669/1069] Team Create Complete Fields --- .../chat2db/server/domain/api/model/Team.java | 19 ++++++--- .../domain/api/param/team/TeamSelector.java | 4 +- .../domain/core/converter/TeamConverter.java | 11 +++++ .../core/impl/DataSourceServiceImpl.java | 12 +++--- .../domain/core/impl/TeamServiceImpl.java | 14 +++++++ .../repository/entity/DataSourceAccessDO.java | 8 ++-- .../repository/entity/DataSourceDO.java | 8 ++-- .../domain/repository/entity/DbhubUserDO.java | 8 ++-- .../repository/entity/EnvironmentDO.java | 8 ++-- .../domain/repository/entity/TeamDO.java | 11 ++--- .../mapper/DataSourceAccessMapper.java | 2 +- .../repository/mapper/DataSourceMapper.java | 2 +- .../repository/mapper/DbhubUserMapper.java | 2 +- .../repository/mapper/EnvironmentMapper.java | 2 +- .../domain/repository/mapper/TeamMapper.java | 2 +- .../src/main/resources/application.yml | 3 ++ .../test/mybatis/MybatisGeneratorTest.java | 3 ++ .../controller/team/TeamAdminController.java | 7 +++- .../team/request/TeamCreateRequest.java | 8 ---- .../controller/team/vo/TeamPageQueryVO.java | 24 ++++++++++- .../api/controller/vo/SimpleUserVO.java | 41 +++++++++++++++++++ 21 files changed, 150 insertions(+), 49 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java index 9c6814e55..c951b0e39 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Team.java @@ -2,6 +2,7 @@ import java.io.Serial; import java.io.Serializable; +import java.util.Date; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import jakarta.validation.constraints.NotNull; @@ -51,15 +52,23 @@ public class Team implements Serializable { private String status; /** - * 角色编码 + * 团队描述 */ - @NotNull - private String roleCode; + private String description; + + /** + * 修改时间 + */ + private Date gmtModified; + /** + * 修改人用户id + */ + private Long modifiedUserId; /** - * 团队描述 + * 修改人用户 */ - private String description; + private User modifiedUser; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java index a8252fe3f..5be026ec1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamSelector.java @@ -16,8 +16,8 @@ @AllArgsConstructor public class TeamSelector { /** - * empty + * 修改人用户 */ - private Boolean empty; + private Boolean modifiedUser; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java index ec8c02bdd..62a4b4fd6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/TeamConverter.java @@ -39,6 +39,17 @@ public abstract class TeamConverter { */ public abstract List do2dto(List list); + /** + * convert + * + * @param data + * @return + */ + @Mappings({ + @Mapping(target = "modifiedUser.id", source = "modifiedUserId"), + }) + public abstract Team do2dto(TeamDO data); + /** * convert * 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 ec586d983..97e505a75 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 @@ -1,7 +1,6 @@ package ai.chat2db.server.domain.core.impl; import java.sql.Connection; -import java.time.LocalDateTime; import java.util.List; import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; @@ -41,6 +40,7 @@ import ai.chat2db.spi.sql.IDriverManager; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.JdbcUtils; +import cn.hutool.core.date.DateUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -84,8 +84,8 @@ public DataResult createWithPermission(DataSourceCreateParam param) { throw new PermissionDeniedBusinessException(); } DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); - dataSourceDO.setGmtCreate(LocalDateTime.now()); - dataSourceDO.setGmtModified(LocalDateTime.now()); + dataSourceDO.setGmtCreate(DateUtil.date()); + dataSourceDO.setGmtModified(DateUtil.date()); dataSourceDO.setUserId(ContextUtils.getUserId()); dataSourceMapper.insert(dataSourceDO); preWarmingData(dataSourceDO.getId()); @@ -120,7 +120,7 @@ public ActionResult updateWithPermission(DataSourceUpdateParam param) { PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); - dataSourceDO.setGmtModified(LocalDateTime.now()); + dataSourceDO.setGmtModified(DateUtil.date()); dataSourceMapper.updateById(dataSourceDO); return ActionResult.isSuccess(); } @@ -159,8 +159,8 @@ public DataResult copyByIdWithPermission(Long id) { dataSourceDO.setId(null); String alias = dataSourceDO.getAlias() + "Copy"; dataSourceDO.setAlias(alias); - dataSourceDO.setGmtCreate(LocalDateTime.now()); - dataSourceDO.setGmtModified(LocalDateTime.now()); + dataSourceDO.setGmtCreate(DateUtil.date()); + dataSourceDO.setGmtModified(DateUtil.date()); dataSourceMapper.insert(dataSourceDO); return DataResult.of(dataSourceDO.getId()); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java index d25118185..34d4e12b0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java @@ -10,6 +10,7 @@ import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.core.converter.TeamConverter; +import ai.chat2db.server.domain.core.converter.UserConverter; import ai.chat2db.server.domain.repository.entity.TeamDO; import ai.chat2db.server.domain.repository.mapper.TeamMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -19,12 +20,14 @@ import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -41,6 +44,8 @@ public class TeamServiceImpl implements TeamService { private TeamMapper teamMapper; @Resource private TeamConverter teamConverter; + @Resource + private UserConverter userConverter; @Override public ListResult listQuery(List idList) { @@ -108,5 +113,14 @@ private void fillData(List list, TeamSelector selector) { if (CollectionUtils.isEmpty(list) || selector == null) { return; } + fillUser(list, selector); + } + + private void fillUser(List list, TeamSelector selector) { + if (BooleanUtils.isNotTrue(selector.getModifiedUser())) { + return; + } + userConverter.fillDetail(EasyCollectionUtils.toList(list, Team::getModifiedUser)); } + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java index 55ab3d3ab..121b12abf 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceAccessDO.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -14,7 +14,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ @Getter @Setter @@ -32,12 +32,12 @@ public class DataSourceAccessDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 创建人用户id diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java index 2a43fefff..c01cbb900 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DataSourceDO.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -14,7 +14,7 @@ *

* * @author chat2db - * @since 2023-08-06 + * @since 2023-08-26 */ @Getter @Setter @@ -32,12 +32,12 @@ public class DataSourceDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 别名 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java index a420dc8bf..6089ccbd1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -14,7 +14,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ @Getter @Setter @@ -32,12 +32,12 @@ public class DbhubUserDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 用户名 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java index b32f8a07e..65508a413 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -14,7 +14,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ @Getter @Setter @@ -32,12 +32,12 @@ public class EnvironmentDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 创建人用户id diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java index eb009d5ea..a2ae0345a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java @@ -1,10 +1,11 @@ package ai.chat2db.server.domain.repository.entity; +import java.io.Serializable; +import java.util.Date; + import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; -import java.io.Serializable; -import java.time.LocalDateTime; import lombok.Getter; import lombok.Setter; @@ -14,7 +15,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ @Getter @Setter @@ -32,12 +33,12 @@ public class TeamDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 创建人用户id diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java index f5664b87f..27a80eeb9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceAccessMapper.java @@ -9,7 +9,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ public interface DataSourceAccessMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java index f8d68c9b1..2f4f8be93 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceMapper.java @@ -9,7 +9,7 @@ *

* * @author chat2db - * @since 2023-08-06 + * @since 2023-08-26 */ public interface DataSourceMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java index c6be265bc..4ff5dce70 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DbhubUserMapper.java @@ -9,7 +9,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ public interface DbhubUserMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java index 8762bcd6d..9626ffb80 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java @@ -9,7 +9,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ public interface EnvironmentMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java index 932d03b60..07cfc32d1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamMapper.java @@ -9,7 +9,7 @@ *

* * @author chat2db - * @since 2023-07-30 + * @since 2023-08-26 */ public interface TeamMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-start/src/main/resources/application.yml b/chat2db-server/chat2db-server-start/src/main/resources/application.yml index 2fc54ddaf..d0c41505b 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/application.yml +++ b/chat2db-server/chat2db-server-start/src/main/resources/application.yml @@ -34,6 +34,9 @@ spring: multipart: max-file-size: -1 max-request-size: -1 + jackson: + serialization: + write-dates-as-timestamps: true chat2db: version: 1.0.0 diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index e51195b87..a7c738a92 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -11,6 +11,7 @@ import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.converts.MySqlTypeConvert; +import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import com.google.common.collect.Lists; import jakarta.annotation.Resource; @@ -63,6 +64,8 @@ private void doGenerator(List tableList) { builder.author("chat2db") //执行完毕不打开文件夹 .disableOpenDir() + // 还是使用date + .dateType(DateType.ONLY_DATE) // 指定输出目录 .outputDir(outputDir); }) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java index 1e1ef7e60..47c096720 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java @@ -6,6 +6,7 @@ import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; +import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -28,6 +29,10 @@ @RequestMapping("/api/admin/team") @RestController public class TeamAdminController { + private static final TeamSelector TEAM_SELECTOR=TeamSelector.builder() + .modifiedUser(Boolean.TRUE) + .build(); + @Resource private TeamService teamService; @Resource @@ -42,7 +47,7 @@ public class TeamAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return teamService.pageQuery(teamAdminConverter.request2param(request), null) + return teamService.pageQuery(teamAdminConverter.request2param(request), TEAM_SELECTOR) .mapToWeb(teamAdminConverter::dto2vo); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java index 77341f198..1b3bcf9a2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java @@ -31,14 +31,6 @@ public class TeamCreateRequest { @NotNull private String status; - /** - * 角色编码 - * - * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum - */ - @NotNull - private String roleCode; - /** * 团队描述 */ diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java index d04846015..75e2b9a51 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java @@ -1,6 +1,9 @@ package ai.chat2db.server.admin.api.controller.team.vo; +import java.util.Date; + +import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; import lombok.Data; /** @@ -25,11 +28,30 @@ public class TeamPageQueryVO { */ private String name; - /** * 团队状态 * * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum */ private String status; + + /** + * 团队描述 + */ + private String description; + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 修改人用户 + */ + private SimpleUserVO modifiedUser; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java new file mode 100644 index 000000000..d9c67b84d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.common.api.controller.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * user + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class +SimpleUserVO implements Serializable { + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String userName; + + /** + * 昵称 + */ + private String nickName; +} \ No newline at end of file From d99b1f6858a62cfc8acf98e74235de1e491b764a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 14:15:26 +0800 Subject: [PATCH 0670/1069] Team Create Complete Fields --- .../chat2db/server/domain/api/model/User.java | 17 ++++++++++ .../domain/api/param/user/UserSelector.java | 4 +-- .../domain/core/converter/UserConverter.java | 14 ++++++-- .../domain/core/impl/UserServiceImpl.java | 27 +++++++++++++-- .../domain/repository/entity/DbhubUserDO.java | 10 ++++++ .../domain/repository/entity/TeamDO.java | 10 ++---- ...0\346\210\267\346\235\203\351\231\220.sql" | 5 ++- .../test/mybatis/MybatisGeneratorTest.java | 2 +- .../controller/user/UserAdminController.java | 7 +++- .../controller/user/vo/UserPageQueryVO.java | 34 +++++++++++++++++++ 10 files changed, 112 insertions(+), 18 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java index 67e31c213..2662063ad 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/User.java @@ -1,5 +1,7 @@ package ai.chat2db.server.domain.api.model; +import java.util.Date; + import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; @@ -62,4 +64,19 @@ public class User { */ @NotNull private String status; + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 修改人用户 + */ + private User modifiedUser; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java index babf2efb4..0f25ce938 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserSelector.java @@ -16,8 +16,8 @@ @AllArgsConstructor public class UserSelector { /** - * empty + * 修改人用户 */ - private Boolean empty; + private Boolean modifiedUser; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java index 8cc7ac0a7..3ca22755b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/UserConverter.java @@ -35,6 +35,9 @@ public abstract class UserConverter { * @param data * @return */ + @Mappings({ + @Mapping(target = "modifiedUser.id", source = "modifiedUserId"), + }) public abstract User do2dto(DbhubUserDO data); /** @@ -57,14 +60,21 @@ public abstract class UserConverter { * @param user * @return */ - public abstract DbhubUserDO param2do(UserCreateParam user); + @Mappings({ + @Mapping(target = "createUserId", source = "userId"), + @Mapping(target = "modifiedUserId", source = "userId"), + }) + public abstract DbhubUserDO param2do(UserCreateParam user, Long userId); /** * * @param user * @return */ - public abstract DbhubUserDO param2do(UserUpdateParam user); + @Mappings({ + @Mapping(target = "modifiedUserId", source = "userId"), + }) + public abstract DbhubUserDO param2do(UserUpdateParam user, Long userId); /** * Fill in detailed information diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java index 5738a679e..731c13f56 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java @@ -20,12 +20,15 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import cn.hutool.crypto.digest.DigestUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import jakarta.annotation.Resource; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; @@ -84,7 +87,10 @@ public PageResult pageQuery(UserPageQueryParam param, UserSelector selecto Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = dbhubUserMapper.selectPage(page, queryWrapper); - return PageResult.of(userConverter.do2dto(iPage.getRecords()), iPage.getTotal(), param); + List list = userConverter.do2dto(iPage.getRecords()); + + fillData(list, selector); + return PageResult.of(list, iPage.getTotal(), param); } @Override @@ -96,7 +102,7 @@ public DataResult update(UserUpdateParam param) { throw new ParamBusinessException("roleCode"); } - DbhubUserDO data = userConverter.param2do(param); + DbhubUserDO data = userConverter.param2do(param, ContextUtils.getUserId()); if (Objects.nonNull(data.getPassword())) { String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); data.setPassword(bcryptPassword); @@ -138,10 +144,25 @@ public DataResult create(UserCreateParam param) { throw new ParamBusinessException("roleCode"); } - DbhubUserDO data = userConverter.param2do(param); + DbhubUserDO data = userConverter.param2do(param, ContextUtils.getUserId()); String bcryptPassword = DigestUtil.bcrypt(data.getPassword()); data.setPassword(bcryptPassword); dbhubUserMapper.insert(data); return DataResult.of(data.getId()); } + + private void fillData(List list, UserSelector selector) { + if (CollectionUtils.isEmpty(list) || selector == null) { + return; + } + fillUser(list, selector); + } + + private void fillUser(List list, UserSelector selector) { + if (BooleanUtils.isNotTrue(selector.getModifiedUser())) { + return; + } + userConverter.fillDetail(EasyCollectionUtils.toList(list, User::getModifiedUser)); + } + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java index 6089ccbd1..3e0504dc2 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DbhubUserDO.java @@ -68,4 +68,14 @@ public class DbhubUserDO implements Serializable { * 用户状态 */ private String status; + + /** + * 创建人用户id + */ + private Long createUserId; + + /** + * 修改人用户id + */ + private Long modifiedUserId; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java index a2ae0345a..15e04221d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TeamDO.java @@ -1,11 +1,10 @@ package ai.chat2db.server.domain.repository.entity; -import java.io.Serializable; -import java.util.Date; - import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -65,11 +64,6 @@ public class TeamDO implements Serializable { */ private String status; - /** - * 角色编码 - */ - private String roleCode; - /** * 团队描述 */ diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" index aca87e264..1a24e0730 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -38,7 +38,11 @@ ALTER TABLE `dbhub_user` ALTER TABLE `dbhub_user` ADD `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '用户状态'; +ALTER TABLE `dbhub_user` + ADD `create_user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '创建人用户id'; +ALTER TABLE `dbhub_user` + ADD `modified_user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '修改人用户id'; update dbhub_user set role_code= 'DESKTOP',user_name='_desktop_default_user_name',password='_desktop_default_user_name',nick_name='桌面端用户' @@ -58,7 +62,6 @@ CREATE TABLE IF NOT EXISTS `team` `code` varchar(128) DEFAULT NOT NULL COMMENT '团队编码', `name` varchar(512) DEFAULT NULL COMMENT '团队名称', `status` varchar(32) NOT NULL DEFAULT 'VALID' COMMENT '团队状态', - `role_code` varchar(32) DEFAULT NULL COMMENT '角色编码', `description` text DEFAULT NULL COMMENT '团队描述', PRIMARY KEY (`id`) ) ENGINE = InnoDB diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index a7c738a92..ab9440990 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -35,7 +35,7 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("operation_saved")); //doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access", // "dbhub_user")); - doGenerator(Lists.newArrayList("data_source")); + doGenerator(Lists.newArrayList("dbhub_user","team")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java index f94f70bf8..ced4ebaa0 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java @@ -6,6 +6,7 @@ import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -29,6 +30,10 @@ @RestController public class UserAdminController { + private static final UserSelector USER_SELECTOR= UserSelector.builder() + .modifiedUser(Boolean.TRUE) + .build(); + @Resource private UserService userService; @Resource @@ -43,7 +48,7 @@ public class UserAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return userService.pageQuery(userAdminConverter.request2param(request), null) + return userService.pageQuery(userAdminConverter.request2param(request), USER_SELECTOR) .mapToWeb(userAdminConverter::dto2vo); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java index 490032b6e..5ff1abaf7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java @@ -1,6 +1,10 @@ package ai.chat2db.server.admin.api.controller.user.vo; +import java.util.Date; + +import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -36,4 +40,34 @@ public class UserPageQueryVO { * @see ValidStatusEnum */ private String status; + + /** + * 邮箱 + */ + @NotNull + private String email; + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + private String roleCode; + + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 修改人用户 + */ + private SimpleUserVO modifiedUser; + } From fcd908811f32cbb1a676090585abec3debc826d5 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 14:27:43 +0800 Subject: [PATCH 0671/1069] Team Create Complete Fields --- .../server/domain/api/service/DataSourceService.java | 2 +- .../domain/core/impl/DataSourceServiceImpl.java | 4 ++-- .../tools/base/wrapper/result/ActionResult.java | 12 ++++++++++++ .../datasource/DataSourceAccessAdminController.java | 5 +++-- .../datasource/DataSourceAdminController.java | 9 ++++----- .../api/controller/team/TeamAdminController.java | 7 +++---- .../team/TeamDataSourceAdminController.java | 5 +++-- .../api/controller/team/TeamUserAdminController.java | 5 +++-- .../api/controller/user/UserAdminController.java | 5 ++--- .../user/UserDataSourceAdminController.java | 5 +++-- .../api/controller/user/UserTeamAdminController.java | 5 +++-- .../controller/data/source/DataSourceController.java | 2 +- 12 files changed, 40 insertions(+), 26 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java index 87cc90be8..ca7c23de6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java @@ -39,7 +39,7 @@ public interface DataSourceService { * @param param * @return */ - ActionResult updateWithPermission(DataSourceUpdateParam param); + DataResult updateWithPermission(DataSourceUpdateParam param); /** * 删除数据源连接 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 97e505a75..ef08f6800 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 @@ -115,14 +115,14 @@ private void preWarmingData(Long dataSourceId) { } @Override - public ActionResult updateWithPermission(DataSourceUpdateParam param) { + public DataResult updateWithPermission(DataSourceUpdateParam param) { DataSource dataSource = queryExistent(param.getId()).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); dataSourceDO.setGmtModified(DateUtil.date()); dataSourceMapper.updateById(dataSourceDO); - return ActionResult.isSuccess(); + return DataResult.of(dataSourceDO.getId()); } @Override diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java index 269d29746..1dae8adc0 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-base/src/main/java/ai/chat2db/server/tools/base/wrapper/result/ActionResult.java @@ -134,4 +134,16 @@ public static ActionResult fail(String errorCode, String errorMessage, String er return result; } + public DataResult toBooleaSuccessnDataResult() { + return DataResult.builder() + .success(success) + .errorCode(errorCode) + .errorMessage(errorMessage) + .errorDetail(errorDetail) + .solutionLink(solutionLink) + .traceId(traceId) + .data(Boolean.TRUE) + .build(); + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java index f23df0c2c..1e9c66690 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAccessAdminController.java @@ -9,6 +9,7 @@ import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -77,8 +78,8 @@ public ActionResult batchCreate(@Valid @RequestBody DataSourceAccessBatchCreateR * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return dataSourceAccessService.delete(id); + public DataResult delete(@PathVariable Long id) { + return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java index cf15f8324..f12c30c74 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java @@ -1,17 +1,16 @@ package ai.chat2db.server.admin.api.controller.datasource; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.datasource.converter.DataSourceAdminConverter; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCloneRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; @@ -75,7 +74,7 @@ public DataResult create(@Valid @RequestBody DataSourceCreateRequest reque * @version 2.1.0 */ @PostMapping("/update") - public ActionResult update(@Valid @RequestBody DataSourceUpdateRequest request) { + public DataResult update(@Valid @RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceAdminConverter.updateReq2param(request); return dataSourceService.updateWithPermission(param); } @@ -100,7 +99,7 @@ public DataResult clone(@RequestBody DataSourceCloneRequest request) { * @version 2.1.0 */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return dataSourceService.deleteWithPermission(id); + public DataResult delete(@PathVariable Long id) { + return dataSourceService.deleteWithPermission(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java index 47c096720..7704b87ee 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java @@ -2,13 +2,12 @@ package ai.chat2db.server.admin.api.controller.team; import ai.chat2db.server.admin.api.controller.team.converter.TeamAdminConverter; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.service.TeamService; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; @@ -82,7 +81,7 @@ public DataResult update(@RequestBody TeamUpdateRequest request) { * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return teamService.delete(id); + public DataResult delete(@PathVariable Long id) { + return teamService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java index b3f185769..87e538d59 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamDataSourceAdminController.java @@ -12,6 +12,7 @@ import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -92,7 +93,7 @@ public ActionResult create(@Valid @RequestBody TeamDataSourceBatchCreateRequest * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return dataSourceAccessService.delete(id); + public DataResult delete(@PathVariable Long id) { + return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java index 2b448724e..5ace6086e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamUserAdminController.java @@ -10,6 +10,7 @@ import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -84,7 +85,7 @@ public ActionResult create(@Valid @RequestBody TeamUserBatchCreateRequest reques * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return teamUserService.delete(id); + public DataResult delete(@PathVariable Long id) { + return teamUserService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java index ced4ebaa0..5fd7d4c14 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java @@ -8,7 +8,6 @@ import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.service.UserService; -import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; @@ -83,7 +82,7 @@ public DataResult update(@RequestBody UserUpdateRequest request) { * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return userService.delete(id); + public DataResult delete(@PathVariable Long id) { + return userService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java index abc949367..33842bcb4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserDataSourceAdminController.java @@ -12,6 +12,7 @@ import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessSelector; import ai.chat2db.server.domain.api.service.DataSourceAccessService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -92,7 +93,7 @@ public ActionResult create(@RequestBody UserDataSourceBatchCreateRequest request * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return dataSourceAccessService.delete(id); + public DataResult delete(@PathVariable Long id) { + return dataSourceAccessService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java index 28c79e48c..ad1193224 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserTeamAdminController.java @@ -10,6 +10,7 @@ import ai.chat2db.server.domain.api.param.team.user.TeamUserSelector; import ai.chat2db.server.domain.api.service.TeamUserService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -83,7 +84,7 @@ public ActionResult bacthCreate(@Valid @RequestBody UserTeamBatchCreateRequest r * @return */ @DeleteMapping("/{id}") - public ActionResult delete(@PathVariable Long id) { - return teamUserService.delete(id); + public DataResult delete(@PathVariable Long id) { + return teamUserService.delete(id).toBooleaSuccessnDataResult(); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java index 19efc5c80..ff049056e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java @@ -208,7 +208,7 @@ public DataResult create(@RequestBody DataSourceCreateRequest request) { * @return */ @RequestMapping(value = "/datasource/update",method = {RequestMethod.POST, RequestMethod.PUT}) - public ActionResult update(@RequestBody DataSourceUpdateRequest request) { + public DataResult update(@RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceWebConverter.updateReq2param(request); return dataSourceService.updateWithPermission(param); } From fcbcb88e644e5cfb38a077806c0b331fbaeb9ad8 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 16:03:44 +0800 Subject: [PATCH 0672/1069] Add Support Type Query --- .../server/domain/core/impl/DataSourceServiceImpl.java | 2 +- .../domain/repository/mapper/DataSourceCustomMapper.java | 3 +-- .../src/main/resources/mapper/DataSourceCustomMapper.xml | 3 +++ .../controller/common/converter/CommonAdminConverter.java | 4 +++- .../data/source/request/DataSourceQueryRequest.java | 6 ++++++ 5 files changed, 14 insertions(+), 4 deletions(-) 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 ef08f6800..b299b076d 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 @@ -189,7 +189,7 @@ public PageResult queryPageWithPermission(DataSourcePageQueryParam p LoginUser loginUser = ContextUtils.getLoginUser(); IPage iPage = dataSourceCustomMapper.selectPageWithPermission( new Page<>(param.getPageNo(), param.getPageSize()), - BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey()); + BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey(),param.getKind()); List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); fillData(dataSources, selector); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java index 249f5fcbe..d90a05fc6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java @@ -12,7 +12,6 @@ */ public interface DataSourceCustomMapper extends Mapper { IPage selectPageWithPermission(IPage page, @Param("admin") Boolean admin, - @Param("userId") Long userId, - @Param("searchKey") String searchKey); + @Param("userId") Long userId, @Param("searchKey") String searchKey, @Param("kind") String kind); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml index 89e622a84..569248ab8 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml @@ -33,6 +33,9 @@ and (ds.alias like concat('%',#{searchKey},'%') or ds.url like concat('%',#{searchKey},'%')) + + and ds.kind = #{kind} + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java index 24034a69f..c3468591b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/common/converter/CommonAdminConverter.java @@ -6,6 +6,7 @@ import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; import ai.chat2db.server.common.api.controller.request.CommonQueryRequest; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.model.User; @@ -21,7 +22,7 @@ * * @author Jiaju Zhuang */ -@Mapper(componentModel = "spring", imports = {AccessObjectTypeEnum.class}) +@Mapper(componentModel = "spring", imports = {AccessObjectTypeEnum.class, DataSourceKindEnum.class}) public abstract class CommonAdminConverter { /** @@ -54,6 +55,7 @@ public abstract class CommonAdminConverter { */ @Mappings({ @Mapping(target = "pageSize", expression = "java(10)"), + @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), }) public abstract DataSourcePageQueryParam request2paramDataSource(CommonQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java index 141f927fe..28cbef2ae 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java @@ -16,4 +16,10 @@ public class DataSourceQueryRequest extends PageQueryRequest { * 别名模糊搜索词 */ private String searchKey; + /** + * 连接类型 + * + * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum + */ + private String kind; } From c1fcfa5afb71c300eaecb53af659ab323e87eed3 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 16:08:11 +0800 Subject: [PATCH 0673/1069] Add Support Type Query --- .../api/controller/data/source/DataSourceController.java | 6 +++++- .../web/api/controller/data/source/vo/DataSourceVO.java | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java index ff049056e..77f336dac 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java @@ -63,6 +63,10 @@ @Slf4j public class DataSourceController { + private static final DataSourceSelector DATA_SOURCE_SELECTOR= DataSourceSelector.builder() + .environment(Boolean.TRUE) + .build(); + @Autowired private DataSourceService dataSourceService; @@ -166,7 +170,7 @@ public ActionResult closeConsole(@Valid @NotNull ConsoleCloseRequest request) { @GetMapping("/datasource/list") public WebPageResult list(DataSourceQueryRequest request) { DataSourcePageQueryParam param = dataSourceWebConverter.queryReq2param(request); - PageResult result = dataSourceService.queryPage(param, new DataSourceSelector()); + PageResult result = dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR); List dataSourceVOS = dataSourceWebConverter.dto2vo(result.getData()); return WebPageResult.of(dataSourceVOS, result.getTotal(), result.getPageNo(), result.getPageSize()); } 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 15964537e..e70822017 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 @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; import ai.chat2db.spi.config.DriverConfig; import ai.chat2db.spi.model.KeyValue; import ai.chat2db.spi.model.SSHInfo; @@ -99,4 +100,9 @@ public class DataSourceVO { * 驱动配置 */ private DriverConfig driverConfig; + + /** + * 环境 + */ + private SimpleEnvironmentVO environment; } From 01026b95da875aad07c104abfe6f50f9f45d06e7 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 16:44:27 +0800 Subject: [PATCH 0674/1069] Add Support Type Query --- .../web/api/controller/data/source/DataSourceController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java index 77f336dac..4453994c8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java @@ -166,6 +166,7 @@ public ActionResult closeConsole(@Valid @NotNull ConsoleCloseRequest request) { * * @param request * @return + * @version 2.1.0 */ @GetMapping("/datasource/list") public WebPageResult list(DataSourceQueryRequest request) { From f35b9764896927cbc4e33892cd8f5b79d8cd21b0 Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" Date: Sat, 26 Aug 2023 16:50:55 +0800 Subject: [PATCH 0675/1069] feat: Build backend management page --- .../main/team/datasource-management/index.tsx | 136 +++++++-- chat2db-client/src/pages/main/team/index.tsx | 2 +- .../pages/main/team/team-management/index.tsx | 63 ++-- .../main/team/universal-add-modal/index.tsx | 95 ++++-- .../main/team/universal-drawer/index.tsx | 284 +++++++++++++++--- .../pages/main/team/user-management/index.tsx | 119 ++++++-- chat2db-client/src/service/team.ts | 211 +++++++++++-- chat2db-client/src/typings/team.ts | 141 ++++++--- chat2db-client/src/utils/check.ts | 32 ++ 9 files changed, 898 insertions(+), 185 deletions(-) create mode 100644 chat2db-client/src/utils/check.ts 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 6ce6d6af6..f65e0d20d 100644 --- a/chat2db-client/src/pages/main/team/datasource-management/index.tsx +++ b/chat2db-client/src/pages/main/team/datasource-management/index.tsx @@ -1,12 +1,19 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import { Button, Input, Table, Popconfirm, message } from 'antd'; -import { IDataSourcePageQueryVO } from '@/typings/team'; +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { Button, Input, Table, Popconfirm, message, Drawer } from 'antd'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; +import ConnectionServer from '@/service/connection' +import { createDataSource, deleteDataSource, getDataSourceList, updateDataSource } from '@/service/team'; +import { IConnectionDetails } from '@/typings'; +import { AffiliationType, IDataSourceVO } from '@/typings/team'; +import i18n from '@/i18n'; +import { isValid } from '@/utils/check'; +import CreateConnection from '@/blocks/CreateConnection'; +import UniversalDrawer from '../universal-drawer'; import styles from './index.less'; -import { getDataSourceList } from '@/service/team'; +import { isNumber } from 'lodash'; function DataSourceManagement() { - const [dataSource, setDataSource] = useState([]); + const [dataSource, setDataSource] = useState([]); const [pagination, setPagination] = useState({ searchKey: '', current: 1, @@ -14,9 +21,15 @@ function DataSourceManagement() { total: 0, showSizeChanger: true, showQuickJumper: true, - pageSizeOptions: ['10', '20', '30', '40'], + // pageSizeOptions: ['10', '20', '30', '40'], }); - const [isModalVisible, setIsModalVisible] = useState(false); + const [showCreateConnection, setShowCreateConnection] = useState(false) + const connectionInfo = useRef(); + + const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: AffiliationType; id?: number }>({ + open: false, + type: AffiliationType['DATASOURCE_USER/TEAM'] + }) const columns = useMemo( () => [ @@ -33,17 +46,34 @@ function DataSourceManagement() { { title: '操作', key: 'action', - render: (_, record: any) => ( - handleDelete(record.id)} - okText="确认" - cancelText="取消" - > - e.preventDefault()}> - 删除 - - + width: 180, + render: (_: any, record: IDataSourceVO) => ( + <> + + + handleDelete(record.id)} + okText={i18n('common.button.affirm')} + cancelText={i18n('common.button.cancel')} + > + e.preventDefault()}> + {i18n('common.button.delete')} + + + ), }, ], @@ -70,24 +100,58 @@ function DataSourceManagement() { }; const handleTableChange = (p: any) => { - console.log('handleTableChange', p); + setPagination({ ...pagination, ...p, }); }; - const handleDelete = async (recordId: number) => { - const success = true; // Replace with actual API response + const handleAddDataSource = () => { + connectionInfo.current = undefined; + setShowCreateConnection(true); + } + + const handleEdit = async (record: IDataSourceVO) => { + const { id } = record; + if (!id) { + return; + } - if (success) { + let detail = await ConnectionServer.getDetails({ id }) + connectionInfo.current = detail; + setShowCreateConnection(true) + } + + const handleDelete = async (id?: number) => { + if (isNumber(id)) { + await deleteDataSource({ id }) message.success('删除成功'); queryDataSourceList(); - } else { - message.error('删除失败'); } }; + const handleConfirmConnection = async (data: IConnectionDetails) => { + if (JSON.stringify(connectionInfo.current) === '{}') { + return; + } + connectionInfo.current = { + ...data, + environmentId: 2, + } + + const isUpdate = isValid(connectionInfo?.current?.id); + const requestApi = isUpdate ? updateDataSource : createDataSource; + try { + await requestApi({ ...connectionInfo.current }) + message.success(isUpdate ? '更新成功' : '创建成功') + setShowCreateConnection(false) + queryDataSourceList() + } catch { + + } + } + return (
@@ -97,8 +161,8 @@ function DataSourceManagement() { onSearch={handleSearch} enterButton={} /> -
+ + setShowCreateConnection(false)} + > + + + + { + setDrawerInfo({ + ...drawerInfo, + open: false + }) + }} + /> ); } diff --git a/chat2db-client/src/pages/main/team/index.tsx b/chat2db-client/src/pages/main/team/index.tsx index c6aa0f5b0..12f611a97 100644 --- a/chat2db-client/src/pages/main/team/index.tsx +++ b/chat2db-client/src/pages/main/team/index.tsx @@ -7,7 +7,7 @@ import UserManagement from './user-management'; import TeamManagement from './team-management'; const Team = () => { - const [activeKey, setActiveKey] = useState('2'); + const [activeKey, setActiveKey] = useState('0'); const tabList = useMemo( () => [ { diff --git a/chat2db-client/src/pages/main/team/team-management/index.tsx b/chat2db-client/src/pages/main/team/team-management/index.tsx index 5f9b46f74..063960b53 100644 --- a/chat2db-client/src/pages/main/team/team-management/index.tsx +++ b/chat2db-client/src/pages/main/team/team-management/index.tsx @@ -1,10 +1,11 @@ import React, { useEffect, useMemo, useState } from 'react'; import { createTeam, deleteTeam, getTeamManagementList, updateTeam } from '@/service/team'; -import { ITeamPageQueryVO, ITeamVO, ManagementType, StatusType } from '@/typings/team'; import { Button, Form, Input, Modal, Popconfirm, Radio, Table, Tag, message } from 'antd'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import styles from './index.less'; import UniversalDrawer from '../universal-drawer'; +import i18n from '@/i18n'; +import { AffiliationType, ITeamVO, StatusType } from '@/typings/team'; const formItemLayout = { labelCol: { span: 6 }, @@ -12,11 +13,12 @@ const formItemLayout = { colon: false, }; -const requireRule = { required: true, message: 'Require field empty!' }; +const requireRule = { required: true, message: i18n('common.form.error.required') }; function TeamManagement() { const [form] = Form.useForm(); - const [dataSource, setDataSource] = useState([]); + const [loadding, setLoading] = useState(false) + const [dataSource, setDataSource] = useState([]); const [pagination, setPagination] = useState({ searchKey: '', current: 1, @@ -27,9 +29,8 @@ function TeamManagement() { // pageSizeOptions: ['10', '20', '30', '40'], }); const [isModalVisible, setIsModalVisible] = useState(false); - const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type: ManagementType; teamId?: number }>({ + const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type?: AffiliationType; teamId?: number }>({ open: false, - type: ManagementType.USER, }); const columns = useMemo( () => [ @@ -56,7 +57,7 @@ function TeamManagement() { render: (_: any, record: ITeamVO) => ( <> - - handleDelete(record.id)} okText="确认" cancelText="取消"> + handleDelete(record.id)} + okText={i18n('common.button.affirm')} + cancelText={i18n('common.button.cancel')} + > e.preventDefault()}> - 删除 + {i18n('common.button.delete')} @@ -90,15 +106,21 @@ function TeamManagement() { }, [pagination.current, pagination.pageSize, pagination.searchKey]); const queryTeamList = async () => { - const { searchKey, current: pageNo, pageSize } = pagination; - let res = await getTeamManagementList({ searchKey, pageNo, pageSize }); - if (res) { - setDataSource(res?.data ?? []); + setLoading(true); + try { + const { searchKey, current: pageNo, pageSize } = pagination; + let res = await getTeamManagementList({ searchKey, pageNo, pageSize }); + if (res) { + setDataSource(res?.data ?? []); + } + } catch (error) { + + } finally { + setLoading(false) } }; const handleTableChange = (p: any) => { - console.log('handleTableChange', p); setPagination({ ...pagination, ...p, @@ -148,11 +170,13 @@ function TeamManagement() {
+ { - console.log('Validation failed:', errorInfo); form.scrollToField(errorInfo.errorFields[0].name); form.setFields(errorInfo.errorFields); }) - .finally(() => { - form.resetFields(); - }); }} onCancel={() => { form.resetFields(); @@ -184,7 +204,6 @@ function TeamManagement() { form={form} autoComplete={'off'} initialValues={{ - // roleCode: RoleStatusType.USER, status: StatusType.VALID, }} > @@ -194,12 +213,6 @@ function TeamManagement() { - {/* - - 管理员 - 用户 - - */} 有效 diff --git a/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx b/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx index cc09872aa..166cda0a6 100644 --- a/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx +++ b/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx @@ -1,72 +1,127 @@ -import { getCommonUserList } from '@/service/team'; -import { IUserPageQueryVO, ManagementType } from '@/typings/team'; -import { Input, Modal, Select, Spin } from 'antd'; +import { getCommonDataSourceList, getCommonTeamList, getCommonUserAndTeamList, getCommonUserList } from '@/service/team'; +import { IDataSourceVO, ITeamAndUserVO, ITeamVO, IUserVO, ManagementType, SearchType } from '@/typings/team'; +import { Modal, Select, Spin } from 'antd'; import debounce from 'lodash/debounce'; import React, { useMemo, useState } from 'react'; import styles from './index.less'; interface IProps { open: boolean; - - type: ManagementType; - + type?: SearchType; + onConfirm: (values: Object) => void; onClose: () => void; } interface ValueType { + id?: number; key: number; - label: React.ReactNode; - value: number; } +const addAuthMap = { + [SearchType['USER/TEAM']]: { + title: '添加人员/团队', + loadRequest: getCommonUserAndTeamList, + searchLabel: (data: ITeamAndUserVO) => data.name, + searchValue: (data: ITeamAndUserVO) => JSON.stringify({ id: data.id, type: data.type }), + searchListKey: 'accessObjectList', + placeholder: '搜索人员/团队', + }, + [SearchType.TEAM]: { + title: '添加团队', + loadRequest: getCommonTeamList, + searchLabel: (data: ITeamVO) => data.name, + searchValue: (data: ITeamVO) => data.id, + searchListKey: 'teamIdList', + placeholder: '搜索团队', + }, + [SearchType.USER]: { + title: '添加人员', + loadRequest: getCommonUserList, + searchLabel: (data: IUserVO) => data.userName, + searchValue: (data: IUserVO) => data.id, + searchListKey: 'userIdList', + placeholder: '搜索人员', + }, + [SearchType.DATASOURCE]: { + title: '添加链接', + loadRequest: getCommonDataSourceList, + searchLabel: (data: IDataSourceVO) => data.alias, + searchValue: (data: IDataSourceVO) => data.id, + searchListKey: 'dataSourceIdList', + placeholder: '搜索链接', + }, +} + function UniversalAddModal(props: IProps) { - const { open } = props; + const { open, type } = props; const [fetching, setFetching] = useState(false); - const [options, setOptions] = useState([]); + const [selectedValues, setSelectedValues] = useState([]) + + const authData = useMemo(() => { + if (type) { + return addAuthMap[type] + } + }, [type]) const loadOptions = (value: string) => { setOptions([]); - setFetching(true); - getCommonUserList({ searchKey: value }).then((res) => { + authData?.loadRequest({ searchKey: value }).then((res) => { const newOptions = (res || []).map((i) => ({ - value: i.id, - - label: i.userName, - + ...i, + label: authData.searchLabel(i), + value: authData.searchValue(i), key: i.id, })); - console.log('newOptions', newOptions); - setOptions(newOptions); setFetching(false); }); }; + const handleOk = () => { + if (!props.onConfirm || !authData) { + return; + } + + const realValue = { + [authData.searchListKey]: type !== SearchType['USER/TEAM'] ? selectedValues : selectedValues.map(i => JSON.parse(i)) + } + + props.onConfirm(realValue) + props.onClose && props.onClose(); + setSelectedValues([]); + setOptions([]); + } + return ( { props.onClose && props.onClose(); }} - title="添加xx" + title={authData?.title} >
{ form .validateFields() .then((values) => { - console.log('Form values:', JSON.stringify(values)); - handleCreateUser(values); + const formValues = form.getFieldsValue(true); + handleCreateOrUpdateUser(formValues); setIsModalVisible(false); form.resetFields(); }) .catch((errorInfo) => { - console.log('Validation failed:', errorInfo); form.scrollToField(errorInfo.errorFields[0].name); form.setFields(errorInfo.errorFields); }) .finally(() => { form.resetFields(); - }); + }) }} onCancel={() => { form.resetFields(); @@ -140,16 +207,19 @@ function UserManagement() { }} > - + - + - - + + - + @@ -165,6 +235,17 @@ function UserManagement() { + + { + setDrawerInfo({ + ...drawerInfo, + open: false + }) + }} + /> ); } diff --git a/chat2db-client/src/service/team.ts b/chat2db-client/src/service/team.ts index 6128cdf6a..b7a56053e 100644 --- a/chat2db-client/src/service/team.ts +++ b/chat2db-client/src/service/team.ts @@ -1,76 +1,239 @@ import createRequest from './base'; -import { IPageParams, IPageResponse } from '@/typings'; -import { - IDataSourcePageQueryVO, - ITeamPageQueryVO, - ITeamVO, - IUserPageQueryVO, - IUserVO, - TeamUserPageQueryVO, -} from '@/typings/team'; +import { IConnectionDetails, IPageParams, IPageResponse } from '@/typings'; +import { IDataSourceAccessObjectVO, IDataSourceVO, ITeamAndUserVO, ITeamVO, ITeamWithDataSourceVO, ITeamWithUserVO, IUserVO, IUserWithDataSourceVO, IUserWithTeamVO, RoleType } from '@/typings/team'; +// =============================== DataSource ============================ /** - * 获取共享链接列表 + * 链接-获取共享链接列表 */ -const getDataSourceList = createRequest>( - '/api/admin/data_source/page', +const getDataSourceList = createRequest>('/api/admin/data_source/page', { + method: 'get', +}); + +/** + * 链接-创建链接 + */ +const createDataSource = createRequest('/api/admin/data_source/create', { + method: 'post', +}); +/** + * 链接-更新链接 + */ +const updateDataSource = createRequest('/api/admin/data_source/update', { + method: 'post', +}); +/** + * 链接-删除链接 + */ +const deleteDataSource = createRequest<{ id: number }, boolean>('/api/admin/data_source/:id', { + method: 'delete', +}); +/** + * 链接-获取链接包含的团队/用户列表 + */ +const getUserAndTeamListFromDataSource = createRequest< + IPageParams & { dataSourceId: number }, + IPageResponse +>('/api/admin/data_source/access/page', { + method: 'get', +}); + +/** + * 链接-添加团队/人员权限到共享链接 + */ +const updateUserAndTeamListFromDataSource = createRequest< + { dataSourceId: number; accessObjectList: Array<{ id: number; type: RoleType }> }, number +>('/api/admin/data_source/access/batch_create', { + method: 'post', +}); + +/** + * 链接-删除团队/人员权限到共享链接 + */ +const deleteUserOrTeamFromDataSource = createRequest<{ id: number }, boolean>('/api/admin/data_source/access/:id', { + method: 'delete', +}); + + +// ====================== User ====================== + +/** 用户-用户管理列表查询 */ +const getUserManagementList = createRequest>('/api/admin/user/page', { + method: 'get', +}); + +/** 创建用户 */ +const createUser = createRequest('/api/admin/user/create', { + method: 'post', +}); +/** 更新用户 */ +const updateUser = createRequest('/api/admin/user/update', { + method: 'post', +}); +/** 删除用户 */ +const deleteUser = createRequest<{ id: number }, boolean>('/api/admin/user/:id', { + method: 'delete', +}); + +/** 用户-用户管理中获取所属团队列表 */ +const getTeamListFromUser = createRequest>( + '/api/admin/user/team/page', { method: 'get', }, ); +/** 用户-用户管理中更新所属团队 */ +const updateTeamListFromUser = createRequest<{ userId: number; teamIdList: number[] }, number>( + '/api/admin/user/team/batch_create', + { + method: 'post', + } +); -/** 用户管理列表查询 */ -const getUserManagementList = createRequest>('/api/admin/user/page', { +/** 用户-用户管理中删除所属团队 */ +const deleteTeamListFromUser = createRequest<{ id: number }, boolean>('/api/admin/user/team/:id', { + method: 'delete', +}); + +/** 用户-用户管理中添加链接 */ +const getDataSourceListFromUser = createRequest< + IPageParams & { userId: number }, + IPageResponse +>('/api/admin/user/data_source/page', { method: 'get', }); -/** 创建用户 */ -const createUser = createRequest('/api/admin/user/create', { - method: 'post', +/** 用户-用户管理中更新链接 */ +const updateDataSourceListFromUser = createRequest< + { userId: number; dataSourceIdList: number[] }, number>( + '/api/admin/user/data_source/batch_create', { + method: 'post', + }); + +/** 用户-用户管理中删除链接 */ +const deleteDataSourceFromUser = createRequest<{ id: number }, boolean>('/api/admin/user/data_source/:id', { + method: 'delete', }); + +// ======================== 团队 ====================== + /** 团队-团队管理列表查询 */ -const getTeamManagementList = createRequest>('/api/admin/team/page', { +const getTeamManagementList = createRequest>('/api/admin/team/page', { method: 'get', }); /** 团队-创建团队 */ -const createTeam = createRequest('/api/admin/team/create', { +const createTeam = createRequest('/api/admin/team/create', { method: 'post', }); -const updateTeam = createRequest('/api/admin/team/update', { +/** 团队-更新团队 */ +const updateTeam = createRequest('/api/admin/team/update', { method: 'post', }); -const deleteTeam = createRequest<{ id: number }, {}>('/api/admin/team/:id', { +/** 团队-删除团队 */ +const deleteTeam = createRequest<{ id: number }, boolean>('/api/admin/team/:id', { method: 'delete', }); /** 团队-团队管理中获取包含用户列表 */ -const getUserListFromTeam = createRequest>( +const getUserListFromTeam = createRequest>( '/api/admin/team/user/page', { method: 'get', }, ); -const deleteUserFromTeam = createRequest<{ id: number }, {}>('/api/admin/team/user/:id', { +/** 团队-团队管理中更新包含用户列表 */ +const updateUserListFromTeam = createRequest<{ teamId: number; userIdList: number[] }, number>( + '/api/admin/team/user/batch_create', + { + method: 'post', + }, +); +/** 团队-团队管理中删除包含用户列表 */ +const deleteUserFromTeam = createRequest<{ id: number }, boolean>('/api/admin/team/user/:id', { + method: 'delete', +}); + + +/** 用户-用户管理中添加归属链接 */ +const getDataSourceListFromTeam = createRequest< + IPageParams & { userId: number }, + IPageResponse +>('/api/admin/team/data_source/page', { + method: 'get', +}); + +/** 用户-用户管理中更新归属链接 */ +const updateDataSourceListFromTeam = createRequest< + { userId: number; dataSourceIdList: number[] }, number +>('/api/admin/team/data_source/batch_create', { + method: 'post', +}); + +/** 用户-用户管理中删除所属团队 */ +const deleteDataSourceFromTeam = createRequest<{ id: number }, boolean>('/api/admin/team/data_source/:id', { method: 'delete', }); // ======================= 通用列表 ===================== /** 通用-获取user列表 */ -const getCommonUserList = createRequest<{ searchKey: string }, IUserPageQueryVO[]>('/api/admin/common/user/list', { +const getCommonUserList = createRequest<{ searchKey: string }, IUserVO[]>('/api/admin/common/user/list', { method: 'get', }); +/** 通用-获取team列表 */ +const getCommonTeamList = createRequest<{ searchKey: string }, ITeamVO[]>('/api/admin/common/team/list', { + method: 'get', +}); +/** 通用-获取DataSource列表 */ +const getCommonDataSourceList = createRequest<{ searchKey: string }, IDataSourceVO[]>( + '/api/admin/common/data_source/list', + { + method: 'get', + }, +); +/** 通用-获取user和team列表 */ +const getCommonUserAndTeamList = createRequest<{ searchKey: string }, ITeamAndUserVO[]>( + '/api/admin/common/team_user/list', + { + method: 'get', + }, +); export { + // dataSource getDataSourceList, + createDataSource, + updateDataSource, + deleteDataSource, + getUserAndTeamListFromDataSource, + updateUserAndTeamListFromDataSource, + deleteUserOrTeamFromDataSource, + // user getUserManagementList, createUser, + updateUser, + deleteUser, + getTeamListFromUser, + updateTeamListFromUser, + deleteTeamListFromUser, + getDataSourceListFromUser, + updateDataSourceListFromUser, + deleteDataSourceFromUser, + // team getTeamManagementList, createTeam, updateTeam, deleteTeam, getUserListFromTeam, + updateUserListFromTeam, deleteUserFromTeam, + getDataSourceListFromTeam, + updateDataSourceListFromTeam, + deleteDataSourceFromTeam, + // common getCommonUserList, + getCommonTeamList, + getCommonDataSourceList, + getCommonUserAndTeamList, }; diff --git a/chat2db-client/src/typings/team.ts b/chat2db-client/src/typings/team.ts index c0b06228c..6d79ea2e2 100644 --- a/chat2db-client/src/typings/team.ts +++ b/chat2db-client/src/typings/team.ts @@ -1,10 +1,25 @@ -// ===================== Universal ================== +// ===================== Common ================== export enum ManagementType { DATASOURCE = 'DATASOURCE', TEAM = 'TEAM', USER = 'USER', } +export enum AffiliationType { + 'USER_TEAM' = 'USER_TEAM', + 'USER_DATASOURCE' = 'USER_DATASOURCE', + 'TEAM_USER' = 'TEAM_USER', + 'TEAM_DATASOURCE' = 'TEAM_DATASOURCE', + 'DATASOURCE_USER/TEAM' = 'DATASOURCE_USER/TEAM' +} + +export enum SearchType { + DATASOURCE = 'DATASOURCE', + TEAM = 'TEAM', + USER = 'USER', + 'USER/TEAM' = 'USER/TEAM' +} + export enum StatusType { INVALID = 'INVALID', VALID = 'VALID', @@ -15,9 +30,16 @@ export enum RoleType { USER = 'USER', } +export enum MemberType { + TEAM = 'TEAM', + USER = 'USER' +} + + + // ===================== DataSource ================== -export interface IDataSourcePageQueryVO { +export interface IDataSourceVO { /** * 连接别名 */ @@ -25,7 +47,7 @@ export interface IDataSourcePageQueryVO { /** * 环境 */ - environment?: ISimpleEnvironmentVO; + environment?: IEnvironmentVO; /** * 环境id */ @@ -40,7 +62,7 @@ export interface IDataSourcePageQueryVO { url?: string; } -export interface ISimpleEnvironmentVO { +export interface IEnvironmentVO { /** * 主键 */ @@ -56,40 +78,58 @@ export interface ISimpleEnvironmentVO { /** * 样式类型 */ - style?: StyleType; -} -/** - * 样式类型 - */ -export enum StyleType { - Release = 'RELEASE', - Test = 'TEST', + style?: string; } -// ===================== User ====================== -export interface IUserPageQueryVO { + +export interface IDataSourceAccessVO { + /** + * 授权对象 + */ + accessObject: IDataSourceAccessObjectVO; + /** + * 授权id,根据类型区分是用户还是团队 + */ + accessObjectId: number; + /** + * 授权类型 + */ + accessObjectType: RoleType; /** * 主键 */ id: number; +} + + +export interface IDataSourceAccessObjectVO { /** - * 昵称 + * The name of the code that belongs to the authorization type, such as user account, team + * code */ - nickName: string; + code?: string; /** - * 用户状态 + * 授权id,根据类型区分是用户还是团队 */ - status?: StatusType; + id?: number; /** - * 用户名 + * Code that belongs to the authorization type, such as user name, team name */ - userName: string; + name?: string; + /** + * 授权类型 + */ + type?: RoleType; } -/** - * UserCreateRequest - */ +// ===================== User ====================== + export interface IUserVO { + /** + * 主键 + */ + id: number; + /** * 邮箱 */ @@ -116,27 +156,37 @@ export interface IUserVO { userName: string; } -// ===================== Team ===================== -export interface ITeamPageQueryVO { +export interface IUserWithTeamVO { /** - * 团队编码 + * 主键 */ - code: string; + id?: number; /** - * 主键 + * 团队 */ - id: number; + team?: ITeamVO; /** - * 团队名称 + * user id */ - name: string; + userId?: number; +} + +export interface IUserWithDataSourceVO { + id?: number; /** - * 团队状态 + * Data Source */ - status: StatusType; + dataSource?: IDataSourceVO; + /** + * user id + */ + userId?: number; } + +// ===================== Team ===================== + export interface ITeamVO { id?: number; /** @@ -157,8 +207,31 @@ export interface ITeamVO { status: StatusType; } -export interface TeamUserPageQueryVO { +export interface ITeamWithUserVO { id: number; teamId: number; user: IUserVO; } + +export interface ITeamWithDataSourceVO { + /** + * Data Source + */ + dataSource?: IDataSourceVO; + /** + * 主键 + */ + id: number; + /** + * team id + */ + teamId?: number; +} + +// ===================== USER/TEAM ===================== +export interface ITeamAndUserVO { + code?: string; + id?: number; + name?: string; + type?: MemberType +} diff --git a/chat2db-client/src/utils/check.ts b/chat2db-client/src/utils/check.ts new file mode 100644 index 000000000..766331e81 --- /dev/null +++ b/chat2db-client/src/utils/check.ts @@ -0,0 +1,32 @@ +const toString = Object.prototype.toString + + +const isType = + (type: string | string[]) => + (obj: unknown): obj is T => + getType(obj) === `[object ${type}]` +export const getType = (obj: any) => toString.call(obj) +export const isArr = Array.isArray; +export const isValid = (val: any) => val !== null && val !== undefined; +export const isFn = (val: any): val is Function => typeof val === 'function' +export const isPlainObj = isType('Object') +export const isStr = isType('String') +export const isBool = isType('Boolean') +export const isNum = isType('Number') +export const isMap = (val: any): val is Map => + val && val instanceof Map +export const isSet = (val: any): val is Set => val && val instanceof Set +export const isWeakMap = (val: any): val is WeakMap => + val && val instanceof WeakMap +export const isWeakSet = (val: any): val is WeakSet => + val && val instanceof WeakSet +export const isNumberLike = (index: any): index is number => + isNum(index) || /^\d+$/.test(index) +export const isObj = (val: unknown): val is object => typeof val === 'object' +export const isRegExp = isType('RegExp') +export const isReactElement = (obj: any): boolean => + obj && obj['$$typeof'] && obj['_owner'] +export const isHTMLElement = (target: any): target is EventTarget => { + return Object.prototype.toString.call(target).indexOf('HTML') > -1 +} + From 154d0101d4747195cfd239b4eea2b0012149ca17 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 17:18:31 +0800 Subject: [PATCH 0676/1069] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=95=B0=E6=8D=AE=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mapper/DataSourceAccessCustomMapper.xml | 8 +++--- .../team/TeamDataSourceAdminController.java | 4 +-- .../team/TeamUserAdminController.java | 4 +-- .../TeamDataSourcesAdminConverter.java | 9 ++++--- .../converter/TeamUserAdminConverter.java | 4 +-- .../request/TeamPageCommonQueryRequest.java | 25 +++++++++++++++++++ .../user/UserDataSourceAdminController.java | 4 +-- .../user/UserTeamAdminController.java | 4 +-- .../UserDataSourcesAdminConverter.java | 4 +-- .../converter/UserTeamAdminConverter.java | 4 +-- ...t.java => UserPageCommonQueryRequest.java} | 2 +- 11 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java rename chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/{UserTeamPageCommonQueryRequest.java => UserPageCommonQueryRequest.java} (84%) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml index fe538e014..789adc39c 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceAccessCustomMapper.xml @@ -4,11 +4,11 @@ - + From da83c21ec900af45b6818421597237754fcd0bdc Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 17:31:25 +0800 Subject: [PATCH 0678/1069] Return Query ID --- .../api/controller/user/vo/UserDataSourcePageQueryVO.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java index 7aed582c4..0281b318e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java @@ -2,6 +2,7 @@ package ai.chat2db.server.admin.api.controller.user.vo; import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; +import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -12,6 +13,12 @@ @Data public class UserDataSourcePageQueryVO { + /** + * 主键 + */ + @NotNull + private Long id; + /** * user id */ From cadeb32087cf502c8fe5d99f4e353981c22e0019 Mon Sep 17 00:00:00 2001 From: robinji0 <137188352+robinji0@users.noreply.github.com> Date: Sat, 26 Aug 2023 17:34:25 +0800 Subject: [PATCH 0679/1069] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ea6cc5097..075ae3002 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,8 @@ Follow our WeChat public account. +discord server + ## ❤️ Acknowledgements Thanks to all the students who contributed to Chat2DB~ From 11a36a8c125e7a110e666b8ecbee8bf5410634d3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 26 Aug 2023 17:35:25 +0800 Subject: [PATCH 0680/1069] fix:Alternate environment interface --- chat2db-client/src/components/ConnectionEdit/index.tsx | 2 +- chat2db-client/src/service/connection.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 518ab6b61..a0b3e0bb0 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -64,7 +64,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde connectionService.getEnvList().then((res) => { setEnvList(res?.map(t => { return { - value: t.code, + value: t.id, label: t.name } })); diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index 3c34b280e..59f16cb9d 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -76,7 +76,14 @@ const downloadDriver = createRequest<{ dbType: string }, void>('/api/jdbc/driver const saveDriver = createRequest('/api/jdbc/driver/save', { errorLevel: false, method: 'post' }); -const getEnvList = createRequest('/api/env/list', { errorLevel: false}); +export interface IEnv { + id: string; + name: string; + shortName: string; + style: string; +} + +const getEnvList = createRequest('/api/common/environment/list_all', { errorLevel: false}); export default { getEnvList, From dab5374e26a423087bd6c676a4f550dc4cbcc661 Mon Sep 17 00:00:00 2001 From: robinji0 <137188352+robinji0@users.noreply.github.com> Date: Sat, 26 Aug 2023 17:36:39 +0800 Subject: [PATCH 0681/1069] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 075ae3002..ba1562ccb 100644 --- a/README.md +++ b/README.md @@ -192,7 +192,7 @@ Follow our WeChat public account. -discord server + Click and join discord server ## ❤️ Acknowledgements From 5dca689adebb6acd186f5a4192cb3b8adf5cb33b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 26 Aug 2023 17:43:04 +0800 Subject: [PATCH 0682/1069] fix:Example Modify the name of an environment field --- .../src/components/ConnectionEdit/config/dataSource.ts | 2 +- chat2db-client/src/components/ConnectionEdit/index.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 25c747ef6..1cf5ad093 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -148,7 +148,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ inputType: InputType.SELECT, labelNameCN: '环境', labelNameEN: 'Env', - name: 'env', + name: 'environmentId', required: true, selects: [], styles: { diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index a0b3e0bb0..095fc85b1 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -79,6 +79,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde data.baseInfo.items.forEach((t: IFormItem) => { if (t.name === 'env' && envList?.length) { t.selects = envList; + t.defaultValue = envList[0].value; } }) return data; From 7da19c05ed3c755c65e9b391646bb26dc85efb6b Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" Date: Sat, 26 Aug 2023 17:44:37 +0800 Subject: [PATCH 0683/1069] fix: bug fix --- .../src/pages/main/team/datasource-management/index.tsx | 5 +---- .../src/pages/main/team/universal-drawer/index.tsx | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) 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 f65e0d20d..ab5eb15ef 100644 --- a/chat2db-client/src/pages/main/team/datasource-management/index.tsx +++ b/chat2db-client/src/pages/main/team/datasource-management/index.tsx @@ -135,10 +135,7 @@ function DataSourceManagement() { if (JSON.stringify(connectionInfo.current) === '{}') { return; } - connectionInfo.current = { - ...data, - environmentId: 2, - } + connectionInfo.current = data; const isUpdate = isValid(connectionInfo?.current?.id); const requestApi = isUpdate ? updateDataSource : createDataSource; diff --git a/chat2db-client/src/pages/main/team/universal-drawer/index.tsx b/chat2db-client/src/pages/main/team/universal-drawer/index.tsx index 6d57ad341..2631f9c8a 100644 --- a/chat2db-client/src/pages/main/team/universal-drawer/index.tsx +++ b/chat2db-client/src/pages/main/team/universal-drawer/index.tsx @@ -181,7 +181,7 @@ function UniversalDrawer(props: IProps) { type: AffiliationType.TEAM_DATASOURCE, searchType: SearchType.DATASOURCE, title: '归属链接', - byIdKey: 'userId', + byIdKey: 'teamId', queryListApi: getDataSourceListFromTeam, updateListApi: updateDataSourceListFromTeam, deleteApi: deleteDataSourceFromTeam, From 97158285528955be7644918bc604eec70d336540 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 26 Aug 2023 18:54:09 +0800 Subject: [PATCH 0684/1069] fix:Display environment --- chat2db-client/src/components/ConnectionEdit/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 095fc85b1..ebf858751 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -77,7 +77,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde return t.type === backfillData.type }); data.baseInfo.items.forEach((t: IFormItem) => { - if (t.name === 'env' && envList?.length) { + if (t.name === 'environmentId' && envList?.length) { t.selects = envList; t.defaultValue = envList[0].value; } From 5d83b92fdcfa3144d2b19ffc3d4ce205ed1c56cf Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 19:14:55 +0800 Subject: [PATCH 0685/1069] list can be empty --- .../datasource/request/DataSourceAccessBatchCreateRequest.java | 2 -- .../team/request/TeamDataSourceBatchCreateRequest.java | 2 -- .../api/controller/team/request/TeamUserBatchCreateRequest.java | 2 -- .../user/request/UserDataSourceBatchCreateRequest.java | 2 -- .../api/controller/user/request/UserTeamBatchCreateRequest.java | 2 -- 5 files changed, 10 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java index ed1f88440..14d179409 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java @@ -2,7 +2,6 @@ import java.util.List; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -30,6 +29,5 @@ public class DataSourceAccessBatchCreateRequest { * DataSource Access Object */ @NotNull - @NotEmpty private List accessObjectList; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java index 57eeaab98..58a139929 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java @@ -2,7 +2,6 @@ import java.util.List; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -31,6 +30,5 @@ public class TeamDataSourceBatchCreateRequest { * Data Source id list */ @NotNull - @NotEmpty private List dataSourceIdList; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java index 42f1a75fd..6c983bcf9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java @@ -2,7 +2,6 @@ import java.util.List; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -30,6 +29,5 @@ public class TeamUserBatchCreateRequest { * user id list */ @NotNull - @NotEmpty private List userIdList; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java index 65e70b022..b396bb722 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java @@ -2,7 +2,6 @@ import java.util.List; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -29,6 +28,5 @@ public class UserDataSourceBatchCreateRequest { * Data Source id list */ @NotNull - @NotEmpty private List dataSourceIdList; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java index 68c74ef6d..831bee3ac 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java @@ -2,7 +2,6 @@ import java.util.List; -import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; import lombok.Data; @@ -29,6 +28,5 @@ public class UserTeamBatchCreateRequest { * team id list */ @NotNull - @NotEmpty private List teamIdList; } From 7188d4b92de6ba3464e0c6be882fe2baf4325220 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 20:46:29 +0800 Subject: [PATCH 0686/1069] Add order by --- .../datasource/DataSourcePageQueryParam.java | 15 +++++- .../api/param/team/TeamPageQueryParam.java | 13 +++++ .../api/param/user/UserPageQueryParam.java | 14 ++++++ .../core/impl/DataSourceServiceImpl.java | 6 ++- .../domain/core/impl/TeamServiceImpl.java | 4 +- .../domain/core/impl/UserServiceImpl.java | 4 +- .../mapper/DataSourceCustomMapper.java | 2 +- .../mapper/DataSourceCustomMapper.xml | 3 ++ .../chat2db-server-tools-common/pom.xml | 4 ++ .../common/model/EasyLambdaQueryWrapper.java | 27 ++++++++++ .../tools/common/util/EasySqlUtils.java | 50 +++++++++++++++++++ .../datasource/DataSourceAdminController.java | 6 ++- .../controller/team/TeamAdminController.java | 8 ++- .../controller/user/UserAdminController.java | 8 ++- 14 files changed, 154 insertions(+), 10 deletions(-) create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasySqlUtils.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourcePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourcePageQueryParam.java index c54d41e2b..b2f960757 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourcePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourcePageQueryParam.java @@ -1,8 +1,9 @@ package ai.chat2db.server.domain.api.param.datasource; +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; - import lombok.Data; +import lombok.Getter; /** * @author moji @@ -23,4 +24,16 @@ public class DataSourcePageQueryParam extends PageQueryParam { * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; + + @Getter + public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { + ID_DESC(OrderBy.desc("id")), + ; + + final OrderBy orderBy; + + OrderCondition(OrderBy orderBy) { + this.orderBy = orderBy; + } + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java index 6cef62c44..59d50572d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/TeamPageQueryParam.java @@ -1,7 +1,9 @@ package ai.chat2db.server.domain.api.param.team; +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.Data; +import lombok.Getter; /** * page query @@ -16,4 +18,15 @@ public class TeamPageQueryParam extends PageQueryParam { */ private String searchKey; + @Getter + public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { + ID_DESC(OrderBy.desc("id")), + ; + + final OrderBy orderBy; + + OrderCondition(OrderBy orderBy) { + this.orderBy = orderBy; + } + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java index bfe3ffb28..b6e1d8b6a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user/UserPageQueryParam.java @@ -1,8 +1,10 @@ package ai.chat2db.server.domain.api.param.user; +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.experimental.SuperBuilder; @@ -21,4 +23,16 @@ public class UserPageQueryParam extends PageQueryParam { * searchKey */ private String searchKey; + + @Getter + public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { + ID_DESC(OrderBy.desc("id")), + ; + + final OrderBy orderBy; + + OrderCondition(OrderBy orderBy) { + this.orderBy = orderBy; + } + } } 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 b299b076d..953deb104 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 @@ -32,6 +32,7 @@ import ai.chat2db.server.tools.common.util.ContextUtils; 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.DriverConfig; import ai.chat2db.spi.model.DataSourceConnect; import ai.chat2db.spi.model.Database; @@ -187,9 +188,12 @@ public PageResult queryPage(DataSourcePageQueryParam param, DataSour @Override public PageResult queryPageWithPermission(DataSourcePageQueryParam param, DataSourceSelector selector) { LoginUser loginUser = ContextUtils.getLoginUser(); + IPage iPage = dataSourceCustomMapper.selectPageWithPermission( new Page<>(param.getPageNo(), param.getPageSize()), - BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey(),param.getKind()); + BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey(),param.getKind(), + EasySqlUtils.orderBy(param.getOrderByList())); + List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); fillData(dataSources, selector); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java index 34d4e12b0..8ca145162 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java @@ -19,6 +19,7 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -61,7 +62,7 @@ public ListResult listQuery(List idList) { @Override public PageResult pageQuery(TeamPageQueryParam param, TeamSelector selector) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(TeamDO::getCode, "%" + param.getSearchKey() + "%") .or() @@ -69,6 +70,7 @@ public PageResult pageQuery(TeamPageQueryParam param, TeamSelector selecto } Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); + queryWrapper.orderBy(param.getOrderByList()); IPage iPage = teamMapper.selectPage(page, queryWrapper); List list = teamConverter.do2dto(iPage.getRecords()); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java index 731c13f56..b6c011b56 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java @@ -20,6 +20,7 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.common.exception.DataAlreadyExistsBusinessException; import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.tools.common.util.EasyCollectionUtils; import cn.hutool.crypto.digest.DigestUtil; @@ -74,7 +75,7 @@ public ListResult listQuery(List idList) { @Override public PageResult pageQuery(UserPageQueryParam param, UserSelector selector) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); if (StringUtils.isNotBlank(param.getSearchKey())) { queryWrapper.and(wrapper -> wrapper.like(DbhubUserDO::getUserName, "%" + param.getSearchKey() + "%") .or() @@ -84,6 +85,7 @@ public PageResult pageQuery(UserPageQueryParam param, UserSelector selecto } // Default not to query desktop accounts queryWrapper.ne(DbhubUserDO::getId, RoleCodeEnum.DESKTOP.getDefaultUserId()); + queryWrapper.orderBy(param.getOrderByList()); Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = dbhubUserMapper.selectPage(page, queryWrapper); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java index d90a05fc6..05e772204 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DataSourceCustomMapper.java @@ -12,6 +12,6 @@ */ public interface DataSourceCustomMapper extends Mapper { IPage selectPageWithPermission(IPage page, @Param("admin") Boolean admin, - @Param("userId") Long userId, @Param("searchKey") String searchKey, @Param("kind") String kind); + @Param("userId") Long userId, @Param("searchKey") String searchKey, @Param("kind") String kind, @Param("orderBy") String orderBy); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml index 569248ab8..b909a6910 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml @@ -36,6 +36,9 @@ and ds.kind = #{kind} + + ${orderBy} + diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml index 011d43bc4..ff50c0826 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/pom.xml @@ -70,5 +70,9 @@ org.springframework spring-context-indexer + + com.baomidou + mybatis-plus-boot-starter + diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java new file mode 100644 index 000000000..c7618ead9 --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.tools.common.model; + +import java.util.List; + +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; +import ai.chat2db.server.tools.common.util.EasySqlUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import org.apache.commons.collections4.CollectionUtils; + +import static com.baomidou.mybatisplus.core.enums.SqlKeyword.ORDER_BY; + +/** + * Custom query wrapper + * + * @author Jiaju Zhuang + */ +public class EasyLambdaQueryWrapper extends LambdaQueryWrapper { + public void orderBy(List orderByList) { + if (CollectionUtils.isEmpty(orderByList)) { + return; + } + for (OrderBy orderBy : orderByList) { + appendSqlSegments(ORDER_BY, EasySqlUtils.columnToSqlSegment(orderBy.getOrderConditionName()), + EasySqlUtils.parseOrderBy(orderBy.getDirection())); + } + } +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasySqlUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasySqlUtils.java new file mode 100644 index 000000000..e71b5794d --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasySqlUtils.java @@ -0,0 +1,50 @@ +package ai.chat2db.server.tools.common.util; + +import java.util.Arrays; +import java.util.List; + +import ai.chat2db.server.tools.base.enums.OrderByDirectionEnum; +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; +import com.baomidou.mybatisplus.core.conditions.ISqlSegment; +import com.baomidou.mybatisplus.core.conditions.segments.ColumnSegment; +import com.baomidou.mybatisplus.core.conditions.segments.OrderBySegmentList; +import com.baomidou.mybatisplus.core.enums.SqlKeyword; +import org.apache.commons.collections4.CollectionUtils; + +import static com.baomidou.mybatisplus.core.enums.SqlKeyword.ASC; +import static com.baomidou.mybatisplus.core.enums.SqlKeyword.DESC; + +/** + * sql utils + * + * @author Jiaju Zhuang + */ +public class EasySqlUtils { + + public static String orderBy(List orderByList) { + if (CollectionUtils.isEmpty(orderByList)) { + return null; + } + OrderBySegmentList orderBySegmentList = new OrderBySegmentList(); + for (OrderBy orderBy : orderByList) { + orderBySegmentList.addAll( + Arrays.asList(SqlKeyword.ORDER_BY, columnToSqlSegment(orderBy.getOrderConditionName()), + parseOrderBy(orderBy.getDirection()))); + } + return orderBySegmentList.getSqlSegment(); + } + + /** + * 获取 columnName + */ + public static ColumnSegment columnToSqlSegment(String column) { + return () -> column; + } + + public static ISqlSegment parseOrderBy(OrderByDirectionEnum direction) { + if (direction == OrderByDirectionEnum.ASC) { + return ASC; + } + return DESC; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java index f12c30c74..e6e1ad78f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/DataSourceAdminController.java @@ -8,6 +8,8 @@ import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam.OrderCondition; import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; @@ -49,7 +51,9 @@ public class DataSourceAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return dataSourceService.queryPageWithPermission(dataSourceAdminConverter.request2param(request), DATA_SOURCE_SELECTOR) + DataSourcePageQueryParam param = dataSourceAdminConverter.request2param(request); + param.orderBy(OrderCondition.ID_DESC); + return dataSourceService.queryPageWithPermission(param, DATA_SOURCE_SELECTOR) .mapToWeb(dataSourceAdminConverter::dto2vo); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java index 7704b87ee..d718db121 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/team/TeamAdminController.java @@ -6,6 +6,8 @@ import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam.OrderCondition; import ai.chat2db.server.domain.api.param.team.TeamSelector; import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -28,7 +30,7 @@ @RequestMapping("/api/admin/team") @RestController public class TeamAdminController { - private static final TeamSelector TEAM_SELECTOR=TeamSelector.builder() + private static final TeamSelector TEAM_SELECTOR = TeamSelector.builder() .modifiedUser(Boolean.TRUE) .build(); @@ -46,7 +48,9 @@ public class TeamAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return teamService.pageQuery(teamAdminConverter.request2param(request), TEAM_SELECTOR) + TeamPageQueryParam param = teamAdminConverter.request2param(request); + param.orderBy(OrderCondition.ID_DESC); + return teamService.pageQuery(param, TEAM_SELECTOR) .mapToWeb(teamAdminConverter::dto2vo); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java index 5fd7d4c14..5f45bd79d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/user/UserAdminController.java @@ -6,6 +6,8 @@ import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam.OrderCondition; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; import ai.chat2db.server.domain.api.param.user.UserSelector; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -29,7 +31,7 @@ @RestController public class UserAdminController { - private static final UserSelector USER_SELECTOR= UserSelector.builder() + private static final UserSelector USER_SELECTOR = UserSelector.builder() .modifiedUser(Boolean.TRUE) .build(); @@ -47,7 +49,9 @@ public class UserAdminController { */ @GetMapping("/page") public WebPageResult page(@Valid CommonPageQueryRequest request) { - return userService.pageQuery(userAdminConverter.request2param(request), USER_SELECTOR) + UserPageQueryParam param = userAdminConverter.request2param(request); + param.orderBy(OrderCondition.ID_DESC); + return userService.pageQuery(param, USER_SELECTOR) .mapToWeb(userAdminConverter::dto2vo); } From 0a7d1bf3a0d9d59e219d141a4152c26ea3f16bde Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 26 Aug 2023 20:59:39 +0800 Subject: [PATCH 0687/1069] Query your own connection --- .../server/domain/api/param/OperationPageQueryParam.java | 5 +++++ .../server/domain/core/impl/OperationServiceImpl.java | 3 +++ .../controller/operation/saved/OperationSavedController.java | 2 ++ 3 files changed, 10 insertions(+) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java index 01e882161..a48d98166 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java @@ -46,4 +46,9 @@ public class OperationPageQueryParam extends PageQueryParam { * operation type */ private String operationType; + + /** + * 用户id + */ + private Long userId; } 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 e5a3d7f65..7c32b8e01 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 @@ -104,6 +104,9 @@ public PageResult queryPage(OperationPageQueryParam param) { if (StringUtils.isNotBlank(param.getOperationType())) { queryWrapper.eq("operation_type", param.getOperationType()); } + if (param.getUserId() != null) { + queryWrapper.eq("user_id", param.getUserId()); + } Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); 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 7ab64ce77..1e061ddc2 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 @@ -11,6 +11,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.operation.saved.converter.OperationWebConverter; import ai.chat2db.server.web.api.controller.operation.saved.request.BatchTabCloseRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationCreateRequest; @@ -54,6 +55,7 @@ public class OperationSavedController { @GetMapping("/list") public WebPageResult list(OperationQueryRequest request) { OperationPageQueryParam param = operationWebConverter.queryReq2param(request); + param.setUserId(ContextUtils.getUserId()); PageResult dtoPageResult = operationService.queryPage(param); List operationVOS = operationWebConverter.dto2vo(dtoPageResult.getData()); return WebPageResult.of(operationVOS, dtoPageResult.getTotal(), request.getPageNo(), request.getPageSize()); From 2d26f4180eb08bccb872b0fb36a4115d77e343fd Mon Sep 17 00:00:00 2001 From: "huanyueyaoqin@qq.com" Date: Sun, 27 Aug 2023 10:26:10 +0800 Subject: [PATCH 0688/1069] feat: Team page add i18n --- .../src/components/ConnectionEdit/index.tsx | 2 +- chat2db-client/src/i18n/en-us/common.ts | 6 ++- chat2db-client/src/i18n/en-us/index.ts | 4 +- chat2db-client/src/i18n/en-us/team.ts | 39 +++++++++++++++++ chat2db-client/src/i18n/zh-cn/common.ts | 6 ++- chat2db-client/src/i18n/zh-cn/index.ts | 4 +- chat2db-client/src/i18n/zh-cn/team.ts | 39 +++++++++++++++++ .../main/team/datasource-management/index.tsx | 26 ++++++------ chat2db-client/src/pages/main/team/index.tsx | 9 ++-- .../pages/main/team/team-management/index.tsx | 34 +++++++-------- .../pages/main/team/user-management/index.tsx | 42 +++++++++---------- 11 files changed, 151 insertions(+), 60 deletions(-) create mode 100644 chat2db-client/src/i18n/en-us/team.ts create mode 100644 chat2db-client/src/i18n/zh-cn/team.ts diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index ebf858751..230b16fe6 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -299,7 +299,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde loading={loadings.confirmButton} onClick={saveConnection.bind(null, backfillData.id ? submitType.UPDATE : submitType.SAVE)} > - {backfillData.id ? i18n('common.button.edit') : i18n('common.button.save')} + {backfillData.id ? i18n('common.button.modify') : i18n('common.button.save')} diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 8812789a9..fd9476798 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -3,6 +3,7 @@ export default { 'common.text.is': 'no', 'common.button.affirm': 'Affirm', 'common.button.edit': 'Edit', + 'common.button.modify': 'Modify', 'common.button.confirm': 'Confirm', 'common.button.cancel': 'Cancel', 'common.data.hour': '{1} {hour|hours}', @@ -75,5 +76,8 @@ export default { 'common.text.editorRightClick': 'Editor right click', 'common.form.error.required': 'This field is required!', 'common.form.error.email': 'The input is not a valid email!', - 'common.tips.delete.confirm': 'Are you sure to delete it?' + 'common.tips.delete.confirm': 'Are you sure to delete it?', + 'common.tips.updateSuccess': 'Update Successfully', + 'common.tips.createSuccess': 'Create Successfully', + 'common.text.action': 'Action' }; diff --git a/chat2db-client/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts index 256cc7a9a..deacf628b 100644 --- a/chat2db-client/src/i18n/en-us/index.ts +++ b/chat2db-client/src/i18n/en-us/index.ts @@ -5,6 +5,7 @@ import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; +import team from './team' export default { lang: 'en', @@ -14,5 +15,6 @@ export default { ...workspace, ...menu, ...dashboard, - ...chat + ...chat, + ...team }; diff --git a/chat2db-client/src/i18n/en-us/team.ts b/chat2db-client/src/i18n/en-us/team.ts new file mode 100644 index 000000000..d0e7906d6 --- /dev/null +++ b/chat2db-client/src/i18n/en-us/team.ts @@ -0,0 +1,39 @@ +export default { + 'team.tab.datasource': 'DataSource Management', + 'team.tab.user': 'User Management', + 'team.tab.team': 'Team Management', + 'team.datasource.alias': 'DataSource Name', + 'team.datasource.url': 'DataSource URL', + 'team.user.userName': 'UserName', + 'team.user.nickName': 'NickName', + 'team.user.status': 'Status', + 'team.action.rightManagement': 'Right Management', + 'team.action.editDatasource': 'Edit DataSource', + 'team.action.addDatasource': 'Add DataSource', + 'team.action.addUser': 'Add User', + 'team.action.editTeam': 'Edit Team', + 'team.action.addTeam': 'Add Team', + 'team.action.affiliation.user': 'Affiliation User', + 'team.action.affiliation.team': 'Affiliation Team', + 'team.action.affiliation.datasource': 'Affiliation DataSource', + 'team.input.search.placeholder': 'Please enter keywords to search', + + 'team.user.addForm.userName': 'UserName', + 'team.user.addForm.nickName': 'NickName', + 'team.user.addForm.email': 'Email', + 'team.user.addForm.password': 'Password', + 'team.user.addForm.roleCode': 'Role', + 'team.user.addForm.roleCode.admin': 'Admin', + 'team.user.addForm.roleCode.user': 'User', + 'team.user.addForm.status': 'Status', + 'team.user.addForm.status.valid': 'Valid', + 'team.user.addForm.status.invalid': 'Invalid', + + + 'team.team.addForm.code': 'Team Code', + 'team.team.addForm.name': 'Team Name', + 'team.team.addForm.status': 'Status', + 'team.team.addForm.status.valid': 'Valid', + 'team.team.addForm.status.invalid': 'Invalid', + 'team.team.addForm.description': 'Description', +} \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 6beb8eec2..4b1956b2c 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -3,6 +3,7 @@ export default { 'common.text.is': '是', 'common.button.affirm': '确认', 'common.button.edit': '编辑', + 'common.button.modify': '修改', 'common.button.confirm': '确认', 'common.button.cancel': '取消', 'common.data.hour': '{1}小时', @@ -73,5 +74,8 @@ export default { 'common.text.editorRightClick': '编辑器右键', 'common.form.error.required': '此项必填!', 'common.form.error.email': '不是正确的邮箱格式', - 'common.tips.delete.confirm': '确认要删除吗?' + 'common.tips.delete.confirm': '确认要删除吗?', + 'common.tips.updateSuccess': '更新成功', + 'common.tips.createSuccess': '创建成功', + 'common.text.action': '操作' }; diff --git a/chat2db-client/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts index 867621af0..4f46261f7 100644 --- a/chat2db-client/src/i18n/zh-cn/index.ts +++ b/chat2db-client/src/i18n/zh-cn/index.ts @@ -6,6 +6,7 @@ import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; +import team from './team' export default { lang: LangType.ZH_CN, @@ -16,5 +17,6 @@ export default { ...menu, ...connection, ...dashboard, - ...chat + ...chat, + ...team }; diff --git a/chat2db-client/src/i18n/zh-cn/team.ts b/chat2db-client/src/i18n/zh-cn/team.ts new file mode 100644 index 000000000..e15f1b41e --- /dev/null +++ b/chat2db-client/src/i18n/zh-cn/team.ts @@ -0,0 +1,39 @@ +export default { + 'team.tab.datasource': '链接管理', + 'team.tab.user': '用户管理', + 'team.tab.team': '团队管理', + 'team.datasource.alias': '链接名称', + 'team.datasource.url': '链接地址', + 'team.user.userName': '用户名', + 'team.user.nickName': '昵称', + 'team.user.status': '状态', + 'team.action.rightManagement': '权限管理', + 'team.action.editDatasource': '编辑链接', + 'team.action.addDatasource': '添加链接', + 'team.action.editUser': '编辑用户', + 'team.action.addUser': '添加用户', + 'team.action.editTeam': '编辑团队', + 'team.action.addTeam': '添加团队', + 'team.action.affiliation.user': '包含用户', + 'team.action.affiliation.team': '所属团队', + 'team.action.affiliation.datasource': '归属链接', + 'team.input.search.placeholder': '输入关键字进行搜索', + + 'team.user.addForm.userName': '用户名', + 'team.user.addForm.nickName': '昵称', + 'team.user.addForm.email': '邮箱', + 'team.user.addForm.password': '密码', + 'team.user.addForm.roleCode': '角色', + 'team.user.addForm.roleCode.admin': '管理员', + 'team.user.addForm.roleCode.user': '用户', + 'team.user.addForm.status': '状态', + 'team.user.addForm.status.valid': '有效', + 'team.user.addForm.status.invalid': '无效', + + 'team.team.addForm.code': '团队编码', + 'team.team.addForm.name': '团队名', + 'team.team.addForm.status': '状态', + 'team.team.addForm.status.valid': '有效', + 'team.team.addForm.status.invalid': '无效', + 'team.team.addForm.description': '描述', +} \ No newline at end of file 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 ab5eb15ef..dec117284 100644 --- a/chat2db-client/src/pages/main/team/datasource-management/index.tsx +++ b/chat2db-client/src/pages/main/team/datasource-management/index.tsx @@ -9,8 +9,8 @@ import i18n from '@/i18n'; import { isValid } from '@/utils/check'; import CreateConnection from '@/blocks/CreateConnection'; import UniversalDrawer from '../universal-drawer'; -import styles from './index.less'; import { isNumber } from 'lodash'; +import styles from './index.less'; function DataSourceManagement() { const [dataSource, setDataSource] = useState([]); @@ -34,19 +34,19 @@ function DataSourceManagement() { const columns = useMemo( () => [ { - title: '链接名称', + title: i18n('team.datasource.alias'), dataIndex: 'alias', key: 'alias', }, { - title: '链接地址', + title: i18n('team.datasource.url'), dataIndex: 'url', key: 'url', }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', - width: 180, + width: 300, render: (_: any, record: IDataSourceVO) => ( <> { if (isNumber(id)) { await deleteDataSource({ id }) - message.success('删除成功'); + message.success(i18n('common.text.successfullyDelete')); queryDataSourceList(); } }; @@ -141,7 +141,7 @@ function DataSourceManagement() { const requestApi = isUpdate ? updateDataSource : createDataSource; try { await requestApi({ ...connectionInfo.current }) - message.success(isUpdate ? '更新成功' : '创建成功') + message.success(isUpdate ? i18n('common.tips.updateSuccess') : i18n('common.tips.createSuccess')) setShowCreateConnection(false) queryDataSourceList() } catch { @@ -153,13 +153,13 @@ function DataSourceManagement() {
} />
setShowCreateConnection(false)} @@ -189,7 +189,7 @@ function DataSourceManagement() { }) }} /> - + ); } diff --git a/chat2db-client/src/pages/main/team/index.tsx b/chat2db-client/src/pages/main/team/index.tsx index 12f611a97..84b94d173 100644 --- a/chat2db-client/src/pages/main/team/index.tsx +++ b/chat2db-client/src/pages/main/team/index.tsx @@ -1,27 +1,28 @@ import React, { useMemo, useState } from 'react'; import { ApiOutlined, UserOutlined, TeamOutlined } from '@ant-design/icons'; import { Tabs } from 'antd'; -import styles from './index.less'; import DataSourceManagement from './datasource-management'; import UserManagement from './user-management'; import TeamManagement from './team-management'; +import i18n from '@/i18n'; +import styles from './index.less'; const Team = () => { const [activeKey, setActiveKey] = useState('0'); const tabList = useMemo( () => [ { - label: '共享链接管理', + label: i18n('team.tab.datasource'), icon: , children: , }, { - label: '用户管理', + label: i18n('team.tab.user'), icon: , children: , }, { - label: '团队管理', + label: i18n('team.tab.team'), icon: , children: , }, diff --git a/chat2db-client/src/pages/main/team/team-management/index.tsx b/chat2db-client/src/pages/main/team/team-management/index.tsx index 7d429bcb3..b8ad6b291 100644 --- a/chat2db-client/src/pages/main/team/team-management/index.tsx +++ b/chat2db-client/src/pages/main/team/team-management/index.tsx @@ -35,23 +35,23 @@ function TeamManagement() { const columns = useMemo( () => [ { - title: '团队编码', + title: i18n('team.team.addForm.code'), dataIndex: 'code', key: 'code', }, { - title: '团队名', + title: i18n('team.team.addForm.name'), dataIndex: 'name', key: 'name', }, { - title: '状态', + title: i18n('team.team.addForm.status'), dataIndex: 'status', key: 'status', render: (status: StatusType) => {status}, }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 260, render: (_: any, record: ITeamVO) => ( @@ -70,7 +70,7 @@ function TeamManagement() { }); }} > - 包含用户 + {i18n('team.action.affiliation.user')} { if (id !== undefined) { await deleteTeam({ id }); - message.success('删除成功'); + message.success(i18n('common.text.successfullyDelete')); queryTeamList(); } }; @@ -159,13 +159,13 @@ function TeamManagement() {
} />
{ form @@ -207,19 +207,19 @@ function TeamManagement() { status: StatusType.VALID, }} > - + - + - + - 有效 - 无效 + {i18n('team.team.addForm.status.valid')} + {i18n('team.team.addForm.status.invalid')} - + diff --git a/chat2db-client/src/pages/main/team/user-management/index.tsx b/chat2db-client/src/pages/main/team/user-management/index.tsx index abf85c3ed..0a22501ac 100644 --- a/chat2db-client/src/pages/main/team/user-management/index.tsx +++ b/chat2db-client/src/pages/main/team/user-management/index.tsx @@ -35,23 +35,23 @@ function UserManagement() { const columns = useMemo( () => [ { - title: '用户名', + title: i18n('team.user.userName'), dataIndex: 'userName', key: 'userName', }, { - title: '昵称', + title: i18n('team.user.nickName'), dataIndex: 'nickName', key: 'nickName', }, { - title: '状态', + title: i18n('team.user.status'), dataIndex: 'status', key: 'status', render: (status: StatusType) => {status}, }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 260, render: (_: any, record: IUserVO) => ( @@ -69,7 +69,7 @@ function UserManagement() { id: record.id, }) }}> - 所属团队 + {i18n('team.action.affiliation.team')} { await deleteUser({ id }) - message.success('删除成功') + message.success(i18n('common.text.successfullyDelete')) queryUserList() } @@ -155,8 +155,8 @@ function UserManagement() {
} /> @@ -164,7 +164,7 @@ function UserManagement() { form.resetFields(); setIsModalVisible(true) }}> - 添加用户 + {i18n('team.action.addUser')}
{ form @@ -209,31 +209,31 @@ function UserManagement() { status: StatusType.VALID, }} > - + - + - - + - + - 管理员 - 用户 + {i18n('team.user.addForm.roleCode.admin')} + {i18n('team.user.addForm.roleCode.user')} - + - 有效 - 无效 + {i18n('team.user.addForm.status.valid')} + {i18n('team.user.addForm.status.invalid')} From 4048f9d3818c643ed4f23571fb88f4bbaf7c5fef Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 27 Aug 2023 14:28:04 +0800 Subject: [PATCH 0689/1069] style: add colorBgSubtle --- chat2db-client/src/blocks/Setting/index.less | 2 +- .../components/Console/ChatInput/index.less | 4 +-- .../SearchResult/TableBox/index.less | 6 ++-- .../SearchResult/TableBox/index.tsx | 2 +- chat2db-client/src/components/Tabs/index.less | 6 ++-- .../src/pages/main/connection/index.less | 3 +- .../main/dashboard/chart-item/index.less | 14 ++++----- .../src/pages/main/dashboard/index.less | 2 +- chat2db-client/src/pages/main/index.less | 2 +- .../components/WorkspaceHeader/index.less | 30 +++++++++---------- .../components/WorkspaceLeft/index.less | 2 +- chat2db-client/src/styles/antd.less | 18 +++++------ .../src/theme/abandon/demo/dark.less | 2 +- chat2db-client/src/theme/background/dark.ts | 2 +- .../src/theme/background/darkDimmed.ts | 6 ++-- chat2db-client/src/theme/background/light.ts | 2 +- chat2db-client/src/theme/custom/light.less | 4 +-- 17 files changed, 50 insertions(+), 57 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index fa1270082..947bfa0ba 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -44,7 +44,7 @@ .menus { width: 200px; - background-color: var(--color-bg-elevated); + // background-color: var(--color-bg-subtle); padding: 20px; position: sticky; top: 0; diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 9752d5b19..e0c0d493a 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -47,13 +47,13 @@ border-radius: 4px 12px; &:hover { - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); } } .remainBlock { border-radius: 16px; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); padding: 4px 12px; &:hover { diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 81b462ca3..563a346e2 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -48,7 +48,7 @@ justify-content: space-between; align-items: center; border-bottom: 1px solid var(--color-border-secondary); - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); padding: 0 16px; height: 30px; } @@ -77,7 +77,7 @@ justify-content: start; align-items: center; border-top: 1px solid var(--color-border-secondary); - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); & > span { margin-right: 16px; @@ -114,7 +114,7 @@ display: none; align-items: center; // position: absolute; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); // top: 0; // right: 0; // bottom: 0; diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 62c888380..9de2c9698 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -36,7 +36,7 @@ interface IViewTableCellData { const SupportBaseTable: any = styled(BaseTable)` &.supportBaseTable { --bgcolor: var(--color-bg-base); - --header-bgcolor: var(--color-bg-elevated); + --header-bgcolor: var(--color-bg-subtle); --hover-bgcolor: var(--color-hover-bg); --header-hover-bgcolor: var(--color-hover-bg); --highlight-bgcolor: var(--color-hover-bg); diff --git a/chat2db-client/src/components/Tabs/index.less b/chat2db-client/src/components/Tabs/index.less index 5b94854bd..34b2e6af8 100644 --- a/chat2db-client/src/components/Tabs/index.less +++ b/chat2db-client/src/components/Tabs/index.less @@ -1,7 +1,7 @@ @import '../../styles/var.less'; .tab-focus() { - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); // 添加内阴影 border-bottom: 1px solid var(--color-primary); .icon { @@ -11,7 +11,7 @@ .tab-focus-line() { color: var(--color-primary); - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); .icon { display: flex; } @@ -155,7 +155,7 @@ outline: none; font-size: 12px; font-weight: 400; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); input:focus { outline: none; } diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 50ff97497..f1590b0c4 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -13,7 +13,7 @@ flex-direction: column; width: 220px; overflow: hidden; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); border-right: 1px solid var(--color-border-secondary); border-top: 0px; border-bottom: 0px; @@ -100,7 +100,6 @@ justify-content: center; align-items: center; position: relative; - } .dataBaseList { 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 ae2f2ed1f..e3337986a 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -16,14 +16,13 @@ font-size: 14px; } -.title {} +.title { +} .edit { cursor: pointer; - } - .left_overlay_add { position: absolute; top: 0; @@ -33,7 +32,6 @@ cursor: pointer; } - .right_overlay_add { position: absolute; top: 0; @@ -68,7 +66,6 @@ opacity: 1; } - .add_chart_icon { opacity: 0; width: 16px; @@ -97,7 +94,6 @@ filter: var(--filter-color-gray-100); } - .emptyChartBlock { width: 100%; padding: 20px 0; @@ -115,14 +111,14 @@ .emptyDataText { font-size: 12px; - color: rgba(0, 0, 0, 0.40); + color: rgba(0, 0, 0, 0.4); line-height: 14px; margin-bottom: 40px; } .editBlock { border-radius: var(--border-radius-l-g); - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); max-height: 600px; } @@ -168,4 +164,4 @@ .editorOptionBlock { padding: 12px; -} \ No newline at end of file +} diff --git a/chat2db-client/src/pages/main/dashboard/index.less b/chat2db-client/src/pages/main/dashboard/index.less index 4f03ab657..3b9695dc5 100644 --- a/chat2db-client/src/pages/main/dashboard/index.less +++ b/chat2db-client/src/pages/main/dashboard/index.less @@ -19,7 +19,7 @@ display: flex; flex-direction: column; height: 100%; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); padding: 10px 8px 0px; box-sizing: border-box; min-width: 200px; diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 0692ee6b7..93a82eae0 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -15,7 +15,7 @@ justify-content: space-between; align-items: center; width: 68px; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); border-right: 1px solid var(--color-border-secondary); user-select: none; overflow: hidden; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less index be6232326..bba98145f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -1,18 +1,17 @@ @import '../../../../../styles/var.less'; -.workspaceHeader{ +.workspaceHeader { flex-shrink: 0; display: flex; align-items: center; padding-left: 10px; font-size: 14px; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); border-bottom: 1px solid var(--color-border-secondary); font-weight: bold; - } -.refreshBox{ +.refreshBox { margin-left: 10px; overflow: hidden; height: 18px; @@ -23,49 +22,48 @@ font-weight: normal; cursor: pointer; color: var(--color-text-secondary); - &:hover{ + &:hover { color: var(--color-primary); } - i{ + i { font-size: 14px; } - .spin{ + .spin { transform: scale(0.6); } } -.crumbsItem{ +.crumbsItem { display: flex; align-items: center; height: 36px; cursor: pointer; - &:hover{ + &:hover { color: var(--color-primary); } - .typeIcon{ + .typeIcon { margin-right: 6px; } - - .arrow{ + + .arrow { font-size: 10px; margin: 0px 10px; transform: translateY(1px); } } -.refreshIcon{ +.refreshIcon { margin-left: 20px; cursor: pointer; } -.noConnectionModal{ +.noConnectionModal { display: flex; justify-content: space-between; - .mainText{ - + .mainText { } } 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 7e52af89d..429c4dfb2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -4,7 +4,7 @@ display: flex; flex-direction: column; height: 100%; - background-color: var(--color-bg-elevated); + background-color: var(--color-bg-subtle); padding: 10px 20px 0px; box-sizing: border-box; min-width: 200px; diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index 6c6235e77..16146d61d 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -3,17 +3,17 @@ .ant-modal-header { border-bottom: 0px; } - .ant-modal-content { - background-color: var(--color-bg-elevated) !important; + // .ant-modal-content { + // background-color: var(--color-bg-elevated) !important; - .ant-modal-confirm-title { - color: var(--color-text) !important; - } + // .ant-modal-confirm-title { + // color: var(--color-text) !important; + // } - .ant-modal-confirm-content { - color: var(--color-text) !important; - } - } + // .ant-modal-confirm-content { + // color: var(--color-text) !important; + // } + // } .ant-modal-footer { border-top: 0px; } diff --git a/chat2db-client/src/theme/abandon/demo/dark.less b/chat2db-client/src/theme/abandon/demo/dark.less index d7a565cc8..68fc8fdce 100644 --- a/chat2db-client/src/theme/abandon/demo/dark.less +++ b/chat2db-client/src/theme/abandon/demo/dark.less @@ -305,7 +305,7 @@ html[primary-color='polar-blue'] { --lime-9: #e4f88b; --lime9: #e4f88b; --lime-10: #f0fab5; - --lime10: #f0fab5; + --lime10: #1c2128; --color-text: rgba(255, 255, 255, 0.85); --color-text-secondary: rgba(255, 255, 255, 0.65); --color-text-tertiary: rgba(255, 255, 255, 0.45); diff --git a/chat2db-client/src/theme/background/dark.ts b/chat2db-client/src/theme/background/dark.ts index 9f44314cc..b83651baa 100644 --- a/chat2db-client/src/theme/background/dark.ts +++ b/chat2db-client/src/theme/background/dark.ts @@ -44,7 +44,7 @@ const antDarkTheme = { colorBgBase: '#0a0b0c', colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#0a0b0c', - colorBgElevated: '#131418', + colorBgSubtle: '#131418', colorBorder: 'rgba(54, 55, 58,0.4)', colorBorderSecondary: 'rgba(54, 55, 58,0.4)', }, diff --git a/chat2db-client/src/theme/background/darkDimmed.ts b/chat2db-client/src/theme/background/darkDimmed.ts index c637e55da..491a564f9 100644 --- a/chat2db-client/src/theme/background/darkDimmed.ts +++ b/chat2db-client/src/theme/background/darkDimmed.ts @@ -41,10 +41,10 @@ const antdLightTheme = { token: { ...commonToken, colorTextBase: 'rgb(241, 241, 244)', - colorBgBase: 'rgb(28, 33, 40)', + colorBgBase: '#1c2128', colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', - colorBgContainer: 'rgb(28, 33, 40)', - colorBgElevated: 'rgb(34, 39, 46)', + colorBgContainer: '#1c2128', + colorBgSubtle: '#22272e', colorBorder: 'rgba(55, 62, 71, 0.4)', colorBorderSecondary: 'rgba(55, 62, 71, 0.4)', controlItemBgActive: 'rgba(241, 241, 244, 0.08);', diff --git a/chat2db-client/src/theme/background/light.ts b/chat2db-client/src/theme/background/light.ts index 1c4b7d522..07e0719a5 100644 --- a/chat2db-client/src/theme/background/light.ts +++ b/chat2db-client/src/theme/background/light.ts @@ -44,7 +44,7 @@ const antdLightTheme = { colorBgBase: '#fff', colorHoverBg: 'rgba(0, 0, 0, 0.03)', colorBgContainer: '#fff', - colorBgElevated: '#f6f8fa', + colorBgSubtle: '#f6f8fa', colorBorder: 'rgba(211, 211, 212, 0.4)', colorBorderSecondary: 'rgba(211, 211, 212, 0.4)', }, diff --git a/chat2db-client/src/theme/custom/light.less b/chat2db-client/src/theme/custom/light.less index 9c0639584..8acd5c605 100644 --- a/chat2db-client/src/theme/custom/light.less +++ b/chat2db-client/src/theme/custom/light.less @@ -1,3 +1,3 @@ html[theme='light'] { - --custom-color-icon: #383D4B; -} \ No newline at end of file + --custom-color-icon: #383d4b; +} From 58f59c5aa12f91558757adb2eecdccbd386a35c0 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 27 Aug 2023 15:15:10 +0800 Subject: [PATCH 0690/1069] style:There is a delay in switching between theme colors due to some animation. background animation is disabled here --- chat2db-client/src/styles/antd.less | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index 16146d61d..69c4b262e 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -1,8 +1,22 @@ :root { :global { + // 切换主题色时有一些动画导致切换延迟,这里禁用background动画 + .ant-input, + .ant-input-password { + transition: all 0.2s, background-color 0s; + } + .ant-btn { + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), background-color 0s; + } + + .ant-select-single:not(.ant-select-customize-input) .ant-select-selector { + transition: all 0.2s cubic-bezier(0.645, 0.045, 0.355, 1), background-color 0s; + } + .ant-modal-header { border-bottom: 0px; } + // .ant-modal-content { // background-color: var(--color-bg-elevated) !important; From 91725fcac1002edc591cf1e78df2303b414fcc04 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 29 Aug 2023 21:52:53 +0800 Subject: [PATCH 0691/1069] fix dm function bug --- .../src/main/java/ai/chat2db/plugin/dm/DMMetaData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 009d12e14..f6555f64d 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 @@ -34,7 +34,7 @@ public String tableDDL(Connection connection, String databaseName, String schema } private static String ROUTINES_SQL - = "SELECT OWNER, NAME, TEXT FROM ALL_SOURCE WHERE TYPE = '%s' AND NAME = '%s' AND NAME = '%s' ORDER BY LINE"; + = "SELECT OWNER, NAME, TEXT FROM ALL_SOURCE WHERE TYPE = '%s' AND OWNER = '%s' AND NAME = '%s' ORDER BY LINE"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, From aca6c7d165e286fedd37f887e39fb17c15f73007 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 29 Aug 2023 23:38:24 +0800 Subject: [PATCH 0692/1069] Fix bug with invalid schema specification --- .../domain/core/impl/DatabaseServiceImpl.java | 34 ++- .../tools/common/util/EasyIntegerUtils.java | 25 +++ .../controller/rdb/request/ColumnRequest.java | 2 +- .../web/api/controller/rdb/vo/ColumnVO.java | 2 +- .../chat2db/spi/jdbc/DefaultMetaService.java | 19 +- .../java/ai/chat2db/spi/model/Database.java | 2 + .../java/ai/chat2db/spi/model/Function.java | 7 + .../java/ai/chat2db/spi/model/Procedure.java | 9 + .../java/ai/chat2db/spi/model/Schema.java | 3 + .../main/java/ai/chat2db/spi/model/Table.java | 8 + .../ai/chat2db/spi/model/TableColumn.java | 23 +- .../chat2db/spi/model/TableIndexColumn.java | 14 +- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 207 ++++++------------ .../ai/chat2db/spi/util/ResultSetUtils.java | 144 ++++++------ .../java/ai/chat2db/spi/util/SqlUtils.java | 11 +- 15 files changed, 269 insertions(+), 241 deletions(-) create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index bdf1cf8b2..589bbc7fc 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -87,12 +87,42 @@ public ListResult querySchema(SchemaQueryParam param) { (key) -> param.isRefresh(), (key) -> { Connection connection = param.getConnection() == null ? Chat2DBContext.getConnection() : param.getConnection(); - MetaData metaData = Chat2DBContext.getMetaData(); - return metaData.schemas(connection, param.getDataBaseName()); + return getSchemaList(param.getDataBaseName(), connection); }); return ListResult.of(schemas); } + + private List getSchemaList(String databaseName, Connection connection) { + MetaData metaData = Chat2DBContext.getMetaData(); + List schemas = metaData.schemas(connection,databaseName); + sortSchema(schemas,connection); + return schemas; + } + + private void sortSchema(List schemas,Connection connection) { + if (CollectionUtils.isEmpty(schemas)) { + return; + } + String ulr = null; + try { + ulr = connection.getMetaData().getURL(); + } catch (SQLException e) { + log.error("get url error", e); + } + // If the database name contains the name of the current database, the current database is placed in the first place + int num = -1; + for (int i = 0; i < schemas.size(); i++) { + if (StringUtils.isNotBlank(ulr) && ulr.contains(schemas.get(i).getName())) { + num = i; + break; + } + } + if (num != -1 && num != 0) { + Collections.swap(schemas, num, 0); + } + } + @Override public DataResult queryDatabaseSchema(MetaDataQueryParam param) { MetaSchema metaSchema = new MetaSchema(); diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java new file mode 100644 index 000000000..1e64099df --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/EasyIntegerUtils.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.tools.common.util; + +public class EasyIntegerUtils { + + /** + * 判断2个布尔值是否相同 + * + * @param b1 + * @param b2 + * @param defaultValue 默认值 ,假设b1 b2为空的情况下 取哪个默认值 + * @return + */ + public static boolean equals(Integer b1, Integer b2, Integer defaultValue) { + if (b1 == b2) { + return true; + } + if (b1 == null) { + b1 = defaultValue; + } + if (b2 == null) { + b2 = defaultValue; + } + return b1 == b2; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ColumnRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ColumnRequest.java index d0c30cd49..308faf17e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ColumnRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/ColumnRequest.java @@ -35,7 +35,7 @@ public class ColumnRequest { /** * 是否为空 */ - private Boolean nullable; + private Integer nullable; /** * 是否主键 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java index a6f4b90b2..c8757b6f3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java @@ -38,7 +38,7 @@ public class ColumnVO { /** * 是否为空 */ - private Boolean nullable; + private Integer nullable; /** * 是否主键 diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index e37b91894..002af04c0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -3,7 +3,6 @@ import java.sql.Connection; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.model.Database; @@ -15,7 +14,6 @@ import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.Trigger; import ai.chat2db.spi.sql.SQLExecutor; -import org.apache.commons.beanutils.BeanUtils; /** * @author jipengfei @@ -24,25 +22,12 @@ public class DefaultMetaService implements MetaData { @Override public List databases(Connection connection) { - List dataBases = SQLExecutor.getInstance().databases(connection); - return dataBases.stream().map(str -> Database.builder().name(str).build()).collect(Collectors.toList()); - + return SQLExecutor.getInstance().databases(connection); } @Override public List schemas(Connection connection,String databaseName) { - List> maps = SQLExecutor.getInstance().schemas(connection, databaseName, null); - return maps.stream().map(map -> map2Schema(map)).collect(Collectors.toList()); - - } - - private Schema map2Schema(Map map) { - Schema schema = new Schema(); - try { - BeanUtils.populate(schema, map); - } catch (Exception e) { - } - return schema; + return SQLExecutor.getInstance().schemas(connection, databaseName, null); } @Override diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java index ffc434f9e..02e23ef1f 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java @@ -4,6 +4,7 @@ import java.util.List; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -23,6 +24,7 @@ public class Database implements Serializable { /** * 数据库名字 */ + @JsonAlias({"TABLE_CAT"}) private String name; /** diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java index 13876e75f..b111ed8f1 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Function.java @@ -1,6 +1,7 @@ package ai.chat2db.spi.model; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -26,16 +27,22 @@ public class Function { //SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. This is a user specified, or DBMS generated, name that may be different then the FUNCTION_NAME for example with overload functions // + @JsonAlias({"FUNCTION_CAT"}) private String databaseName; + @JsonAlias({"FUNCTION_SCHEM"}) private String schemaName; + @JsonAlias({"FUNCTION_NAME"}) private String functionName; + @JsonAlias({"REMARKS"}) private String remarks; + @JsonAlias({"FUNCTION_TYPE"}) private Short functionType; + @JsonAlias({"SPECIFIC_NAME"}) private String specificName; private String functionBody; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java index 97c4986f8..3c48489d7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Procedure.java @@ -1,6 +1,7 @@ package ai.chat2db.spi.model; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -26,16 +27,24 @@ public class Procedure { //SPECIFIC_NAME String => the name which uniquely identifies this procedure within its schema. This is a user specified, or DBMS generated, name that may be different then the PROCEDURE_NAME for example with overload procedures // + @JsonAlias({"PROCEDURE_CAT"}) private String databaseName; + @JsonAlias({"PROCEDURE_SCHEM"}) + private String schemaName; + @JsonAlias({"PROCEDURE_NAME"}) private String procedureName; + @JsonAlias({"REMARKS"}) private String remarks; + @JsonAlias({"PROCEDURE_TYPE"}) + private Short procedureType; + @JsonAlias({"SPECIFIC_NAME"}) private String specificName; private String procedureBody; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java index c72e3dfb0..a54b927ac 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java @@ -4,6 +4,7 @@ import java.io.Serializable; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -23,9 +24,11 @@ public class Schema implements Serializable { /** * databaseName */ + @JsonAlias({"TABLE_CATALOG"}) private String databaseName; /** * 数据名字 */ + @JsonAlias({"TABLE_SCHEM"}) private String name; } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java index 188c5d821..bbd3dcb2d 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java @@ -3,6 +3,7 @@ import java.util.List; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -22,16 +23,21 @@ public class Table { /** * 表名 */ + @JsonAlias({"TABLE_NAME"}) private String name; /** * 描述 */ + @JsonAlias({"REMARKS"}) + private String comment; /** * DB 名 */ + @JsonAlias({"TABLE_SCHEM"}) + private String schemaName; /** @@ -52,11 +58,13 @@ public class Table { /** * 数据库名 */ + @JsonAlias("TABLE_CAT") private String databaseName; /** * 表类型 */ + @JsonAlias("TABLE_TYPE") private String type; /** 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 530654bad..7d58d1d5c 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 @@ -1,5 +1,6 @@ package ai.chat2db.spi.model; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -24,6 +25,7 @@ public class TableColumn { /** * 列名 */ + @JsonAlias({"COLUMN_NAME"}) private String name; /** @@ -35,18 +37,24 @@ public class TableColumn { * 列的类型 * 比如 varchar(100) ,double(10,6) */ + + @JsonAlias({"TYPE_NAME"}) private String columnType; /** * 列的数据类型 * 比如 varchar ,double */ + + @JsonAlias({"DATA_TYPE"}) private Integer dataType; /** * 默认值 */ + + @JsonAlias({"COLUMN_DEF"}) private String defaultValue; /** @@ -58,6 +66,7 @@ public class TableColumn { /** * 注释 */ + @JsonAlias({"REMARKS"}) private String comment; /** @@ -68,11 +77,13 @@ public class TableColumn { /** * 空间名 */ + @JsonAlias({"TABLE_SCHEM"}) private String schemaName; /** * 数据库名 */ + @JsonAlias({"TABLE_CAT"}) private String databaseName; /** @@ -83,6 +94,8 @@ public class TableColumn { /** * column size. */ + + @JsonAlias({"COLUMN_SIZE"}) private Integer columnSize; /** @@ -93,11 +106,15 @@ public class TableColumn { /** * the number of fractional digits. Null is returned for data types where DECIMAL_DIGITS is not applicable. */ + + @JsonAlias({"DECIMAL_DIGITS"}) private Integer decimalDigits; /** * Radix (typically either 10 or 2) */ + + @JsonAlias({"NUM_PREC_RADIX"}) private Integer numPrecRadix; /** @@ -127,12 +144,16 @@ public class TableColumn { /** * index of column in table (starting at 1) */ + + @JsonAlias({"ORDINAL_POSITION"}) private Integer ordinalPosition; /** * ISO rules are used to determine the nullability for a column. */ - private Boolean nullable; + + @JsonAlias({"NULLABLE"}) + private Integer nullable; /** * String => Indicates whether this is a generated column diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java index 810f8e37b..091e9cd2d 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java @@ -1,6 +1,7 @@ package ai.chat2db.spi.model; +import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -20,11 +21,13 @@ public class TableIndexColumn { /** * 索引名称 */ + @JsonAlias({"INDEX_NAME"}) private String indexName; /** * 表名 */ + @JsonAlias ({"TABLE_NAME"}) private String tableName; /** @@ -42,17 +45,18 @@ public class TableIndexColumn { /** * 列名 */ + @JsonAlias({"COLUMN_NAME"}) private String columnName; /** * 顺序 */ + @JsonAlias({"ORDINAL_POSITION"}) private Short ordinalPosition; /** * 排序 * - * @see CollationEnum */ private String collation; @@ -60,41 +64,49 @@ public class TableIndexColumn { /** * 索引所属schema */ + @JsonAlias({"TABLE_SCHEM"}) private String schemaName; /** * 数据库名 */ + @JsonAlias({"TABLE_CAT"}) private String databaseName; /** * 是否唯一 */ + @JsonAlias({"NON_UNIQUE"}) private Boolean nonUnique; /** * index catalog (may be null); null when TYPE is tableIndexStatistic */ + @JsonAlias({"INDEX_QUALIFIER"}) private String indexQualifier; /** * ASC_OR_DESC String => column sort sequence, "A" => ascending, "D" => descending, may be null if sort sequence is not supported; null when TYPE is tableIndexStatistic */ + @JsonAlias({"ASC_OR_DESC"}) private String ascOrDesc; /** * CARDINALITY long => When TYPE is tableIndexStatistic, then this is the number of rows in the table; otherwise, it is the number of unique values in the index. */ + @JsonAlias({"CARDINALITY"}) private Long cardinality; /** * When TYPE is tableIndexStatistic then this is the number of pages used for the table, otherwise it is the number of pages used for the current index. */ + @JsonAlias({"PAGES"}) private Long pages; /** * Filter condition, if any. (may be null) */ + @JsonAlias({"FILTER_CONDITION"}) private String filterCondition; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index a109cf402..0bcd0e224 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -5,22 +5,14 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.common.util.I18nUtils; -import ai.chat2db.spi.model.ExecuteResult; -import ai.chat2db.spi.model.Header; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.spi.model.TableIndexColumn; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.util.ResultSetUtils; import cn.hutool.core.date.TimeInterval; import com.google.common.collect.Lists; @@ -29,18 +21,10 @@ import org.springframework.jdbc.support.JdbcUtils; import org.springframework.util.Assert; -import static ai.chat2db.spi.util.ResultSetUtils.buildColumn; -import static ai.chat2db.spi.util.ResultSetUtils.buildFunction; -import static ai.chat2db.spi.util.ResultSetUtils.buildProcedure; -import static ai.chat2db.spi.util.ResultSetUtils.buildTable; -import static ai.chat2db.spi.util.ResultSetUtils.buildTableIndexColumn; /** * Dbhub 统一数据库连接管理 - * TODO 长时间不用连接可以关闭,待优化 - * * @author jipengfei - * @version : DbhubDataSource.java */ @Slf4j public class SQLExecutor { @@ -56,9 +40,6 @@ public static SQLExecutor getInstance() { return INSTANCE; } - //public Connection connection throws SQLException { - // return Chat2DBContext.connection; - //} public void close() { } @@ -72,7 +53,7 @@ public void close() { * @return */ - public R executeSql(Connection connection, String sql, Function function) { + public R executeSql(Connection connection, String sql, Function function) { if (StringUtils.isBlank(sql)) { return null; } @@ -111,12 +92,12 @@ public R execute(Connection connection, String sql, ResultSetFunction fun } public void executeSql(Connection connection, String sql, Consumer> headerConsumer, - Consumer> rowConsumer) { + Consumer> rowConsumer) { executeSql(connection, sql, headerConsumer, rowConsumer, true); } public void executeSql(Connection connection, String sql, Consumer> headerConsumer, - Consumer> rowConsumer, boolean limitSize) { + Consumer> rowConsumer, boolean limitSize) { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); try (Statement stmt = connection.createStatement();) { @@ -134,10 +115,10 @@ public void executeSql(Connection connection, String sql, Consumer> List
headerList = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() - .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) - .build()); + .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) + .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) + .build()); } headerConsumer.accept(headerList); @@ -199,10 +180,10 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li executeResult.setHeaderList(headerList); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() - .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) - .build()); + .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) + .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) + .build()); } // 获取数据信息 @@ -249,58 +230,43 @@ public ExecuteResult execute(Connection connection, String sql) throws SQLExcept * @param connection * @return */ - public List databases(Connection connection) { - List tables = Lists.newArrayList(); + public List databases(Connection connection) { try (ResultSet resultSet = connection.getMetaData().getCatalogs();) { - if (resultSet != null) { - while (resultSet.next()) { - tables.add(resultSet.getString("TABLE_CAT")); - } - } + return ResultSetUtils.toObjectList(resultSet, Database.class); } catch (SQLException e) { throw new RuntimeException(e); } - return tables; } /** - * 获取所有的schema - * - * @param connection - * @param databaseName - * @param schemaName - * @return + * Retrieves the schema names available in this database. The results are ordered by TABLE_CATALOG and TABLE_SCHEM. + * The schema columns are: + * TABLE_SCHEM String => schema name + * TABLE_CATALOG String => catalog name (may be null) + * Params: + * catalog – a catalog name; must match the catalog name as it is stored in the database;"" retrieves those without a catalog; null means catalog name should not be used to narrow down the search. schemaPattern – a schema name; must match the schema name as it is stored in the database; null means schema name should not be used to narrow down the search. + * Returns: + * a ResultSet object in which each row is a schema description + * Throws: + * SQLException – if a database access error occurs + * Since: + * 1.6 + * See Also: + * getSearchStringEscape */ - public List> schemas(Connection connection, String databaseName, String schemaName) { - List> schemaList = Lists.newArrayList(); + public List schemas(Connection connection, String databaseName, String schemaName) { if (StringUtils.isEmpty(databaseName) && StringUtils.isEmpty(schemaName)) { try (ResultSet resultSet = connection.getMetaData().getSchemas()) { - if (resultSet != null) { - while (resultSet.next()) { - Map map = new HashMap<>(); - map.put("name", resultSet.getString("TABLE_SCHEM")); - map.put("databaseName", resultSet.getString("TABLE_CATALOG")); - schemaList.add(map); - } - } + return ResultSetUtils.toObjectList(resultSet, Schema.class); } catch (SQLException e) { throw new RuntimeException("Get schemas error", e); } - return schemaList; } try (ResultSet resultSet = connection.getMetaData().getSchemas(databaseName, schemaName)) { - if (resultSet != null) { - while (resultSet.next()) { - Map map = new HashMap<>(); - map.put("name", resultSet.getString("TABLE_SCHEM")); - map.put("databaseName", resultSet.getString("TABLE_CATALOG")); - schemaList.add(map); - } - } + return ResultSetUtils.toObjectList(resultSet, Schema.class); } catch (SQLException e) { throw new RuntimeException("Get schemas error", e); } - return schemaList; } /** @@ -314,24 +280,13 @@ public List> schemas(Connection connection, String databaseN * @return */ public List
tables(Connection connection, String databaseName, String schemaName, String tableName, - String types[]) { - List
tables = Lists.newArrayList(); - int n = 0; + String types[]) { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, - types)) { - if (resultSet != null) { - while (resultSet.next()) { - n++; - tables.add(buildTable(resultSet)); - if (n >= 1000) {// 最多只取1000条 - break; - } - } - } + types)) { + return ResultSetUtils.toObjectList(resultSet, Table.class); } catch (SQLException e) { throw new RuntimeException(e); } - return tables; } /** @@ -345,99 +300,81 @@ public List
tables(Connection connection, String databaseName, String sch * @return */ public List columns(Connection connection, String databaseName, String schemaName, String tableName, - String columnName) { - List tableColumns = Lists.newArrayList(); + String columnName) { try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, - columnName)) { - if (resultSet != null) { - while (resultSet.next()) { - tableColumns.add(buildColumn(resultSet)); - } - } + columnName)) { + return ResultSetUtils.toObjectList(resultSet, TableColumn.class); } catch (Exception e) { throw new RuntimeException(e); } - return tableColumns; } /** - * 获取所有的数据库表索引 + * get all table index info * - * @param connection - * @param databaseName - * @param schemaName - * @param tableName - * @return + * @param connection connection + * @param databaseName databaseName of the index + * @param schemaName schemaName of the index + * @param tableName tableName of the index + * @return List table index list */ public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, - false, - false)) { - List tableIndexColumns = Lists.newArrayList(); - - while (resultSet != null && resultSet.next()) { - tableIndexColumns.add(buildTableIndexColumn(resultSet)); - } - + false, + false)) { + List tableIndexColumns = ResultSetUtils.toObjectList(resultSet, TableIndexColumn.class); tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( - Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() - .stream().forEach(entry -> { - TableIndex tableIndex = new TableIndex(); - TableIndexColumn column = entry.getValue().get(0); - tableIndex.setName(entry.getKey()); - tableIndex.setTableName(column.getTableName()); - tableIndex.setSchemaName(column.getSchemaName()); - tableIndex.setDatabaseName(column.getDatabaseName()); - tableIndex.setUnique(!column.getNonUnique()); - tableIndex.setColumnList(entry.getValue()); - tableIndices.add(tableIndex); - }); + Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() + .stream().forEach(entry -> { + TableIndex tableIndex = new TableIndex(); + TableIndexColumn column = entry.getValue().get(0); + tableIndex.setName(entry.getKey()); + tableIndex.setTableName(column.getTableName()); + tableIndex.setSchemaName(column.getSchemaName()); + tableIndex.setDatabaseName(column.getDatabaseName()); + tableIndex.setUnique(!column.getNonUnique()); + tableIndex.setColumnList(entry.getValue()); + tableIndices.add(tableIndex); + }); } catch (SQLException e) { throw new RuntimeException(e); } return tableIndices; } + /** - * 获取所有的函数 + * Get all functions available in a catalog. * - * @param connection - * @param databaseName - * @param schemaName - * @return + * @param connection connection + * @param databaseName databaseName of the function + * @param schemaName schemaName of the function + * @return List */ public List functions(Connection connection, String databaseName, - String schemaName) { - List functions = Lists.newArrayList(); + String schemaName) { try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { - while (resultSet != null && resultSet.next()) { - functions.add(buildFunction(resultSet)); - } + return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Function.class); } catch (Exception e) { throw new RuntimeException(e); } - return functions; } /** - * 获取所有的存储过程 + * procedure list * - * @param connection - * @param databaseName - * @param schemaName - * @return + * @param connection connection + * @param databaseName databaseName + * @param schemaName schemaName + * @return List */ public List procedures(Connection connection, String databaseName, String schemaName) { - List procedures = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getProcedures(databaseName, schemaName, null)) { - while (resultSet != null && resultSet.next()) { - procedures.add(buildProcedure(resultSet)); - } + return ResultSetUtils.toObjectList(resultSet, Procedure.class); } catch (Exception e) { throw new RuntimeException(e); } - return procedures; } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java index f26a446d0..76c1fce91 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/ResultSetUtils.java @@ -4,8 +4,16 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; -import ai.chat2db.spi.model.*; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.google.common.collect.Lists; + +import static ai.chat2db.spi.util.JdbcUtils.getResultSetValue; /** * @author jipengfei @@ -13,99 +21,79 @@ */ public class ResultSetUtils { - public static ai.chat2db.spi.model.Function buildFunction(ResultSet resultSet) { - ai.chat2db.spi.model.Function function - = new ai.chat2db.spi.model.Function(); + + public static T toObject(ResultSet rs, Class clazz) { try { - function.setDatabaseName(getString(resultSet, "FUNCTION_CAT")); - function.setSchemaName(getString(resultSet, "FUNCTION_SCHEM")); - function.setFunctionName(getString(resultSet, "FUNCTION_NAME")); - function.setRemarks(getString(resultSet, "REMARKS")); - function.setFunctionType(resultSet.getShort("FUNCTION_TYPE")); - function.setSpecificName(getString(resultSet, "SPECIFIC_NAME")); + if (rs == null || clazz == null) { + return null; + } + ResultSetMetaData resultSetMetaData = rs.getMetaData(); + int col = resultSetMetaData.getColumnCount(); + List headerList = getRsHeader(rs); + Map map = new HashMap<>(); + for (int i = 1; i <= col; i++) { + map.put(headerList.get(i), getResultSetValue(rs, i, true)); + } + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + //mapper.configure(DeserializationFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + + return mapper.convertValue(map, clazz); } catch (SQLException e) { throw new RuntimeException(e); } - return function; } - public static Procedure buildProcedure(ResultSet resultSet) { - Procedure procedure = new Procedure(); + private static List getRsHeader(ResultSet rs) { try { - procedure.setDatabaseName(getString(resultSet, "PROCEDURE_CAT")); - procedure.setSchemaName(getString(resultSet, "PROCEDURE_SCHEM")); - procedure.setProcedureName(getString(resultSet, "PROCEDURE_NAME")); - procedure.setRemarks(getString(resultSet, "REMARKS")); - procedure.setProcedureType(resultSet.getShort("PROCEDURE_TYPE")); - procedure.setSpecificName(getString(resultSet, "SPECIFIC_NAME")); + ResultSetMetaData resultSetMetaData = rs.getMetaData(); + int col = resultSetMetaData.getColumnCount(); + List headerList = Lists.newArrayListWithExpectedSize(col); + for (int i = 1; i <= col; i++) { + headerList.add(getColumnName(resultSetMetaData, i)); + } + return headerList; } catch (SQLException e) { throw new RuntimeException(e); } - return procedure; - } - - - public static TableIndexColumn buildTableIndexColumn(ResultSet resultSet) throws SQLException { - TableIndexColumn tableIndexColumn = new TableIndexColumn(); - tableIndexColumn.setColumnName(getString(resultSet, "COLUMN_NAME")); - tableIndexColumn.setIndexName(getString(resultSet, "INDEX_NAME")); - tableIndexColumn.setAscOrDesc(getString(resultSet, "ASC_OR_DESC")); - tableIndexColumn.setCardinality(resultSet.getLong("CARDINALITY")); - tableIndexColumn.setPages(resultSet.getLong("PAGES")); - tableIndexColumn.setFilterCondition(getString(resultSet, "FILTER_CONDITION")); - tableIndexColumn.setIndexQualifier(getString(resultSet, "INDEX_QUALIFIER")); - // tableIndexColumn.setIndexType(resultSet.getShort("TYPE")); - tableIndexColumn.setNonUnique(resultSet.getBoolean("NON_UNIQUE")); - tableIndexColumn.setOrdinalPosition(resultSet.getShort("ORDINAL_POSITION")); - tableIndexColumn.setDatabaseName(getString(resultSet, "TABLE_CAT")); - tableIndexColumn.setSchemaName(getString(resultSet, "TABLE_SCHEM")); - tableIndexColumn.setTableName(getString(resultSet, "TABLE_NAME")); - return tableIndexColumn; - } - - public static TableColumn buildColumn(ResultSet resultSet) throws SQLException { - TableColumn tableColumn = new TableColumn(); - tableColumn.setDatabaseName(getString(resultSet, "TABLE_CAT")); - tableColumn.setSchemaName(getString(resultSet, "TABLE_SCHEM")); - tableColumn.setTableName(getString(resultSet, "TABLE_NAME")); - tableColumn.setName(getString(resultSet, "COLUMN_NAME")); - tableColumn.setComment(getString(resultSet, "REMARKS")); - tableColumn.setDefaultValue(getString(resultSet, "COLUMN_DEF")); - tableColumn.setColumnType(getString(resultSet, "TYPE_NAME")); - tableColumn.setColumnSize(resultSet.getInt("COLUMN_SIZE")); - tableColumn.setDataType(resultSet.getInt("DATA_TYPE")); - tableColumn.setNullable(resultSet.getInt("NULLABLE") == 1); - tableColumn.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); - //tableColumn.setAutoIncrement("YES".equals(getString,resultSet,"IS_AUTOINCREMENT"))); - //tableColumn.setGeneratedColumn("YES".equals(getString,resultSet,"IS_GENERATEDCOLUMN"))); - tableColumn.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); - tableColumn.setDecimalDigits(resultSet.getInt("DECIMAL_DIGITS")); - tableColumn.setNumPrecRadix(resultSet.getInt("NUM_PREC_RADIX")); - tableColumn.setCharOctetLength(resultSet.getInt("CHAR_OCTET_LENGTH")); - return tableColumn; } - public static Table buildTable(ResultSet resultSet) throws SQLException { - Table table = new Table(); - table.setName(getString(resultSet, "TABLE_NAME")); - table.setComment(getString(resultSet, "REMARKS")); - table.setDatabaseName(getString(resultSet, "TABLE_CAT")); - table.setSchemaName(getString(resultSet, "TABLE_SCHEM")); - table.setType(getString(resultSet, "TABLE_TYPE")); - return table; - } - - private static String getString(ResultSet resultSet, String name) { - if (resultSet == null) { - return null; - } + /** + * + * @param rs + * @param clazz + * @return + * @param + */ + public static List toObjectList(ResultSet rs, Class clazz) { try { - return resultSet.getString(name); - } catch (Exception e) { - return null; + if (rs == null || clazz == null) { + return Lists.newArrayList(); + } + List list = Lists.newArrayList(); + ResultSetMetaData rsMetaData = rs.getMetaData(); + int col = rsMetaData.getColumnCount(); + List headerList = getRsHeader(rs); + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + while (rs.next()) { + Map map = new HashMap<>(); + for (int i = 1; i <= col; i++) { + map.put(headerList.get(i-1), rs.getObject(i)); + } + T obj = mapper.convertValue(map, clazz); + + list.add(obj); + } + return list; + } catch (SQLException e) { + throw new RuntimeException(e); } } + public static String getColumnName(ResultSetMetaData resultSetMetaData, int column) throws SQLException { String columnLabel = resultSetMetaData.getColumnLabel(column); if (columnLabel != null) { diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index bf58ca346..851dc75b6 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -8,6 +8,7 @@ import java.util.Set; import java.util.stream.Collectors; +import ai.chat2db.server.tools.common.util.EasyIntegerUtils; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; @@ -85,7 +86,7 @@ public static List buildSql(Table oldTable, Table newTable) { mySqlCreateTableStatement.addColumn(sqlColumnDefinition); sqlColumnDefinition.setName(tableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(tableColumn.getColumnType())); - if (BooleanUtils.isNotFalse(tableColumn.getNullable())) { + if (tableColumn.getNullable()==1) { sqlColumnDefinition.addConstraint(new SQLNullConstraint()); } else { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); @@ -221,7 +222,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab sqlAlterTableAddColumn.addColumn(sqlColumnDefinition); sqlColumnDefinition.setName(newTableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (BooleanUtils.isNotTrue(newTableColumn.getNullable())) { + if (newTableColumn.getNullable()!=1) { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); } if (!Objects.isNull(newTableColumn.getDefaultValue())) { @@ -236,7 +237,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab // 代表可能修改字段 或者没变 boolean hasChange = !StringUtils.equals(oldTableColumn.getName(), newTableColumn.getName()) || !StringUtils.equals(oldTableColumn.getColumnType(), newTableColumn.getColumnType()) - || !EasyBooleanUtils.equals(oldTableColumn.getNullable(), newTableColumn.getNullable(), Boolean.TRUE) + || !EasyIntegerUtils.equals(oldTableColumn.getNullable(), newTableColumn.getNullable(), 1) || !StringUtils.equals(oldTableColumn.getDefaultValue(), newTableColumn.getDefaultValue()) || !EasyBooleanUtils.equals(oldTableColumn.getAutoIncrement(), newTableColumn.getAutoIncrement(), Boolean.FALSE) @@ -256,7 +257,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab mySqlAlterTableChangeColumn.setNewColumnDefinition(sqlColumnDefinition); sqlColumnDefinition.setName(newTableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (BooleanUtils.isNotTrue(newTableColumn.getNullable())) { + if (newTableColumn.getNullable()!=1) { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); } if (!Objects.isNull(newTableColumn.getDefaultValue())) { @@ -274,7 +275,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab mySqlAlterTableModifyColumn.setNewColumnDefinition(sqlColumnDefinition); sqlColumnDefinition.setName(newTableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (BooleanUtils.isNotTrue(newTableColumn.getNullable())) { + if (newTableColumn.getNullable()!=1) { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); } if (!Objects.isNull(newTableColumn.getDefaultValue())) { From 706bb75cbf05b26af3a1a47178c5cf478c20bf74 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 2 Sep 2023 14:51:48 +0800 Subject: [PATCH 0693/1069] Complete operation addition, deletion, modification, and check --- .../server/domain/api/model/Operation.java | 5 + .../OperationLogCreateParam.java | 2 +- .../OperationLogPageQueryParam.java | 2 +- .../OperationPageQueryParam.java | 2 +- .../param/operation/OperationQueryParam.java | 27 ++++ .../{ => operation}/OperationSavedParam.java | 2 +- .../{ => operation}/OperationUpdateParam.java | 2 +- .../api/service/OperationLogService.java | 4 +- .../domain/api/service/OperationService.java | 30 +++-- .../core/converter/OperationConverter.java | 4 +- .../core/converter/OperationLogConverter.java | 2 +- .../core/impl/OperationLogServiceImpl.java | 4 +- .../core/impl/OperationServiceImpl.java | 46 ++++++- .../V2_1_0__\350\241\245\345\205\205.sql" | 5 + .../common/model/EasyLambdaQueryWrapper.java | 124 +++++++++++++++++- .../operation/log/OperationLogController.java | 4 +- .../converter/OperationLogWebConverter.java | 4 +- .../saved/OperationSavedController.java | 24 ++-- .../converter/OperationWebConverter.java | 8 +- .../saved/request/OperationUpdateRequest.java | 5 + 20 files changed, 259 insertions(+), 47 deletions(-) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => operation}/OperationLogCreateParam.java (90%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => operation}/OperationLogPageQueryParam.java (87%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => operation}/OperationPageQueryParam.java (94%) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationQueryParam.java rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => operation}/OperationSavedParam.java (94%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => operation}/OperationUpdateParam.java (94%) create mode 100644 "chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java index c141932b4..9ce73efde 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Operation.java @@ -79,4 +79,9 @@ public class Operation { * operation type */ private String operationType; + + /** + * 用户id + */ + private Long userId; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationLogCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogCreateParam.java similarity index 90% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationLogCreateParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogCreateParam.java index d7c11371a..ed80c6ece 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationLogCreateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogCreateParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.operation; import lombok.Data; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationLogPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java similarity index 87% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationLogPageQueryParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java index 2b1d45e7c..f5092bae1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationLogPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.operation; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java similarity index 94% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java index a48d98166..4de4d19a1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationPageQueryParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.operation; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationQueryParam.java new file mode 100644 index 000000000..fbcee3508 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationQueryParam.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.domain.api.param.operation; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +/** + * query + * + * @author Jiaju Zhuang + */ +@Data +@NoArgsConstructor +public class OperationQueryParam { + + /** + * 主键 + */ + @NonNull + private Long id; + + /** + * 用户id + */ + @NonNull + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationSavedParam.java similarity index 94% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationSavedParam.java index 910048700..907c9ceb9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationSavedParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationSavedParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.operation; import lombok.Data; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationUpdateParam.java similarity index 94% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationUpdateParam.java index 01b4cf2e4..6f06cc5d3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/OperationUpdateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationUpdateParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.operation; import jakarta.validation.constraints.NotNull; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java index 16e0ce91c..4872413b9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationLogService.java @@ -1,8 +1,8 @@ package ai.chat2db.server.domain.api.service; -import ai.chat2db.server.domain.api.param.OperationLogPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.model.OperationLog; -import ai.chat2db.server.domain.api.param.OperationLogCreateParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java index 262f6839c..4715ec3f2 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/OperationService.java @@ -1,14 +1,14 @@ package ai.chat2db.server.domain.api.service; -import jakarta.validation.constraints.NotNull; - import ai.chat2db.server.domain.api.model.Operation; -import ai.chat2db.server.domain.api.param.OperationPageQueryParam; -import ai.chat2db.server.domain.api.param.OperationSavedParam; -import ai.chat2db.server.domain.api.param.OperationUpdateParam; +import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; +import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import jakarta.validation.constraints.NotNull; /** * 用户保存ddl @@ -25,7 +25,7 @@ public interface OperationService { * @param param * @return */ - DataResult create(OperationSavedParam param); + DataResult createWithPermission(OperationSavedParam param); /** * 更新用户的ddl @@ -33,7 +33,7 @@ public interface OperationService { * @param param * @return */ - ActionResult update(OperationUpdateParam param); + ActionResult updateWithPermission(OperationUpdateParam param); /** * 根据id查询 @@ -43,13 +43,27 @@ public interface OperationService { */ DataResult find(@NotNull Long id); + /** + * 根据id查询 + * + * @param id + * @return + */ + DataResult queryExistent(@NotNull Long id); + /** + * 查询一条数据 + * + * @param param + * @return + */ + DataResult queryExistent(@NotNull OperationQueryParam param); /** * 删除 * * @param id * @return */ - ActionResult delete(@NotNull Long id); + ActionResult deleteWithPermission(@NotNull Long id); /** * 查询用户执行的ddl记录 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationConverter.java index b0ac25200..c439b707d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationConverter.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Operation; -import ai.chat2db.server.domain.api.param.OperationSavedParam; -import ai.chat2db.server.domain.api.param.OperationUpdateParam; +import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; +import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.repository.entity.OperationSavedDO; import org.mapstruct.Mapper; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationLogConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationLogConverter.java index e0da68755..9a0de55ba 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationLogConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/OperationLogConverter.java @@ -3,7 +3,7 @@ import java.util.List; import ai.chat2db.server.domain.api.model.OperationLog; -import ai.chat2db.server.domain.api.param.OperationLogCreateParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; import ai.chat2db.server.domain.repository.entity.OperationLogDO; import org.mapstruct.Mapper; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java index 1aeba4463..c3cbc1edd 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java @@ -8,8 +8,8 @@ import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.OperationLog; -import ai.chat2db.server.domain.api.param.OperationLogCreateParam; -import ai.chat2db.server.domain.api.param.OperationLogPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.OperationLogService; import ai.chat2db.server.domain.core.converter.OperationLogConverter; 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 7c32b8e01..135508df4 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 @@ -9,19 +9,23 @@ import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.model.Operation; -import ai.chat2db.server.domain.api.param.OperationPageQueryParam; -import ai.chat2db.server.domain.api.param.OperationSavedParam; -import ai.chat2db.server.domain.api.param.OperationUpdateParam; +import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; +import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.api.service.OperationService; import ai.chat2db.server.domain.core.converter.OperationConverter; +import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.entity.OperationSavedDO; import ai.chat2db.server.domain.repository.mapper.OperationSavedMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; - +import ai.chat2db.server.tools.common.exception.DataNotFoundException; +import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; +import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -50,16 +54,20 @@ public class OperationServiceImpl implements OperationService { private DataSourceService dataSourceService; @Override - public DataResult create(OperationSavedParam param) { + public DataResult createWithPermission(OperationSavedParam param) { OperationSavedDO userSavedDdlDO = operationConverter.param2do(param); userSavedDdlDO.setGmtCreate(LocalDateTime.now()); userSavedDdlDO.setGmtModified(LocalDateTime.now()); + userSavedDdlDO.setUserId(ContextUtils.getUserId()); operationSavedMapper.insert(userSavedDdlDO); return DataResult.of(userSavedDdlDO.getId()); } @Override - public ActionResult update(OperationUpdateParam param) { + public ActionResult updateWithPermission(OperationUpdateParam param) { + Operation data = queryExistent(param.getId()).getData(); + PermissionUtils.checkOperationPermission(data.getUserId()); + OperationSavedDO userSavedDdlDO = operationConverter.param2do(param); userSavedDdlDO.setGmtModified(LocalDateTime.now()); operationSavedMapper.updateById(userSavedDdlDO); @@ -78,7 +86,31 @@ public DataResult find(Long id) { } @Override - public ActionResult delete(Long id) { + public DataResult queryExistent(Long id) { + DataResult dataResult = find(id); + if (dataResult.getData() == null) { + throw new DataNotFoundException(); + } + return dataResult; + } + + @Override + public DataResult queryExistent(OperationQueryParam param) { + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); + queryWrapper.eqWhenPresent(OperationSavedDO::getId, param.getId()) + .eqWhenPresent(OperationSavedDO::getUserId, param.getUserId()); + IPage page = operationSavedMapper.selectPage(new Page<>(1, 1), queryWrapper); + if (CollectionUtils.isEmpty(page.getRecords())) { + throw new DataNotFoundException(); + } + return DataResult.of(operationConverter.do2dto(page.getRecords().get(0))); + } + + @Override + public ActionResult deleteWithPermission(Long id) { + Operation data = queryExistent(id).getData(); + PermissionUtils.checkOperationPermission(data.getUserId()); + operationSavedMapper.deleteById(id); return ActionResult.isSuccess(); } diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" new file mode 100644 index 000000000..a31ddc231 --- /dev/null +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" @@ -0,0 +1,5 @@ +ALTER TABLE `operation_saved` + modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; + +update operation_saved +set user_id= 1; \ No newline at end of file diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java index c7618ead9..b780fc6bf 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java @@ -1,12 +1,24 @@ package ai.chat2db.server.tools.common.model; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; import ai.chat2db.server.tools.base.wrapper.param.OrderBy; import ai.chat2db.server.tools.common.util.EasySqlUtils; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper; +import com.baomidou.mybatisplus.core.conditions.SharedString; +import com.baomidou.mybatisplus.core.conditions.query.Query; +import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments; +import com.baomidou.mybatisplus.core.metadata.TableFieldInfo; +import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; +import com.baomidou.mybatisplus.core.toolkit.Assert; +import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import org.apache.commons.collections4.CollectionUtils; +import static com.baomidou.mybatisplus.core.enums.SqlKeyword.EQ; import static com.baomidou.mybatisplus.core.enums.SqlKeyword.ORDER_BY; /** @@ -14,7 +26,9 @@ * * @author Jiaju Zhuang */ -public class EasyLambdaQueryWrapper extends LambdaQueryWrapper { +public class EasyLambdaQueryWrapper extends AbstractLambdaWrapper> + implements Query, T, SFunction> { + public void orderBy(List orderByList) { if (CollectionUtils.isEmpty(orderByList)) { return; @@ -24,4 +38,110 @@ public void orderBy(List orderByList) { EasySqlUtils.parseOrderBy(orderBy.getDirection())); } } + + public EasyLambdaQueryWrapper eqWhenPresent(SFunction column, Object val) { + if (val != null) { + addCondition(true, column, EQ, val); + } + return typedThis; + } + + // The following are the methods that come with the system + /** + * 查询字段 + */ + private SharedString sqlSelect = new SharedString(); + + public EasyLambdaQueryWrapper() { + this((T)null); + } + + public EasyLambdaQueryWrapper(T entity) { + super.setEntity(entity); + super.initNeed(); + } + + public EasyLambdaQueryWrapper(Class entityClass) { + super.setEntityClass(entityClass); + super.initNeed(); + } + + EasyLambdaQueryWrapper(T entity, Class entityClass, SharedString sqlSelect, AtomicInteger paramNameSeq, + Map paramNameValuePairs, MergeSegments mergeSegments, SharedString paramAlias, + SharedString lastSql, SharedString sqlComment, SharedString sqlFirst) { + super.setEntity(entity); + super.setEntityClass(entityClass); + this.paramNameSeq = paramNameSeq; + this.paramNameValuePairs = paramNameValuePairs; + this.expression = mergeSegments; + this.sqlSelect = sqlSelect; + this.paramAlias = paramAlias; + this.lastSql = lastSql; + this.sqlComment = sqlComment; + this.sqlFirst = sqlFirst; + } + + /** + * SELECT 部分 SQL 设置 + * + * @param columns 查询字段 + */ + @SafeVarargs + @Override + public final EasyLambdaQueryWrapper select(SFunction... columns) { + return select(Arrays.asList(columns)); + } + + public EasyLambdaQueryWrapper select(List> columns) { + if (com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isNotEmpty(columns)) { + this.sqlSelect.setStringValue(columnsToString(false, columns)); + } + return typedThis; + } + + /** + * 过滤查询的字段信息(主键除外!) + *

例1: 只要 java 字段名以 "test" 开头的 -> select(i -> i.getProperty().startsWith("test"))

+ *

例2: 只要 java 字段属性是 CharSequence 类型的 -> select(TableFieldInfo::isCharSequence)

+ *

例3: 只要 java 字段没有填充策略的 -> select(i -> i.getFieldFill() == FieldFill.DEFAULT)

+ *

例4: 要全部字段 -> select(i -> true)

+ *

例5: 只要主键字段 -> select(i -> false)

+ * + * @param predicate 过滤方式 + * @return this + */ + @Override + public EasyLambdaQueryWrapper select(Class entityClass, Predicate predicate) { + if (entityClass == null) { + entityClass = getEntityClass(); + } else { + setEntityClass(entityClass); + } + Assert.notNull(entityClass, "entityClass can not be null"); + this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(entityClass).chooseSelect(predicate)); + return typedThis; + } + + @Override + public String getSqlSelect() { + return sqlSelect.getStringValue(); + } + + /** + * 用于生成嵌套 sql + *

故 sqlSelect 不向下传递

+ */ + @Override + protected EasyLambdaQueryWrapper instance() { + return new EasyLambdaQueryWrapper<>(getEntity(), getEntityClass(), null, paramNameSeq, paramNameValuePairs, + new MergeSegments(), paramAlias, SharedString.emptyString(), SharedString.emptyString(), + SharedString.emptyString()); + } + + @Override + public void clear() { + super.clear(); + sqlSelect.toNull(); + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java index 78687d466..ce0edefe1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.OperationLog; -import ai.chat2db.server.domain.api.param.OperationLogCreateParam; -import ai.chat2db.server.domain.api.param.OperationLogPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.domain.api.service.OperationLogService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/converter/OperationLogWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/converter/OperationLogWebConverter.java index c3167ba3d..c8ebcda96 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/converter/OperationLogWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/converter/OperationLogWebConverter.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.OperationLog; -import ai.chat2db.server.domain.api.param.OperationLogCreateParam; -import ai.chat2db.server.domain.api.param.OperationLogPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogCreateParam; +import ai.chat2db.server.domain.api.param.operation.OperationLogPageQueryParam; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogCreateRequest; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogQueryRequest; import ai.chat2db.server.web.api.controller.operation.log.vo.OperationLogVO; 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 1e061ddc2..c63d41a9e 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 @@ -3,9 +3,10 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Operation; -import ai.chat2db.server.domain.api.param.OperationPageQueryParam; -import ai.chat2db.server.domain.api.param.OperationSavedParam; -import ai.chat2db.server.domain.api.param.OperationUpdateParam; +import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; +import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.domain.api.service.OperationService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -54,7 +55,7 @@ public class OperationSavedController { */ @GetMapping("/list") public WebPageResult list(OperationQueryRequest request) { - OperationPageQueryParam param = operationWebConverter.queryReq2param(request); + OperationPageQueryParam param = operationWebConverter.queryReq2param(request,ContextUtils.getUserId()); param.setUserId(ContextUtils.getUserId()); PageResult dtoPageResult = operationService.queryPage(param); List operationVOS = operationWebConverter.dto2vo(dtoPageResult.getData()); @@ -69,8 +70,11 @@ public WebPageResult list(OperationQueryRequest request) { */ @GetMapping("/{id}") public DataResult get(@PathVariable("id") Long id) { - DataResult dtoPageResult = operationService.find(id); - return DataResult.of(operationWebConverter.dto2vo(dtoPageResult.getData())); + OperationQueryParam param = new OperationQueryParam(); + param.setId(id); + param.setUserId(ContextUtils.getUserId()); + return operationService.queryExistent(param) + .map(operationWebConverter::dto2vo); } /** @@ -83,7 +87,7 @@ public DataResult get(@PathVariable("id") Long id) { public DataResult create(@RequestBody OperationCreateRequest request) { OperationSavedParam param = operationWebConverter.req2param(request); param.setTabOpened("y"); - return operationService.create(param); + return operationService.createWithPermission(param); } /** @@ -95,7 +99,7 @@ public DataResult create(@RequestBody OperationCreateRequest request) { @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody OperationUpdateRequest request) { OperationUpdateParam param = operationWebConverter.updateReq2param(request); - return operationService.update(param); + return operationService.updateWithPermission(param); } /** @@ -113,7 +117,7 @@ public ActionResult batchTabClose(@RequestBody BatchTabCloseRequest request) { OperationUpdateParam param = new OperationUpdateParam(); param.setId(id); param.setTabOpened("n"); - operationService.update(param); + operationService.updateWithPermission(param); }); return ActionResult.isSuccess(); } @@ -126,6 +130,6 @@ public ActionResult batchTabClose(@RequestBody BatchTabCloseRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { - return operationService.delete(id); + return operationService.deleteWithPermission(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/converter/OperationWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/converter/OperationWebConverter.java index 9bcea66df..a3e1f03c6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/converter/OperationWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/saved/converter/OperationWebConverter.java @@ -3,9 +3,9 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Operation; -import ai.chat2db.server.domain.api.param.OperationPageQueryParam; -import ai.chat2db.server.domain.api.param.OperationSavedParam; -import ai.chat2db.server.domain.api.param.OperationUpdateParam; +import ai.chat2db.server.domain.api.param.operation.OperationPageQueryParam; +import ai.chat2db.server.domain.api.param.operation.OperationSavedParam; +import ai.chat2db.server.domain.api.param.operation.OperationUpdateParam; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationCreateRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationQueryRequest; import ai.chat2db.server.web.api.controller.operation.saved.request.OperationUpdateRequest; @@ -45,7 +45,7 @@ public abstract class OperationWebConverter { * @param request * @return */ - public abstract OperationPageQueryParam queryReq2param(OperationQueryRequest request); + public abstract OperationPageQueryParam queryReq2param(OperationQueryRequest request, Long userId); /** * 模型转换 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 c53a61d52..67a681e6f 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 @@ -43,4 +43,9 @@ public class OperationUpdateRequest { * operation type */ private String operationType; + + /** + * 用户id + */ + private Long userId; } From 1916d766678725ac151f5e0227a8197efbcec5f7 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 2 Sep 2023 16:12:03 +0800 Subject: [PATCH 0694/1069] Complete team additions, deletions, modifications, and checks --- .../{param => chart}/ChartCreateParam.java | 2 +- .../domain/api/chart/ChartListQueryParam.java | 30 ++++++++ .../{param => chart}/ChartPageQueryParam.java | 2 +- .../domain/api/chart/ChartQueryParam.java | 28 ++++++++ .../{param => chart}/ChartUpdateParam.java | 2 +- .../{ => dashboard}/DashboardCreateParam.java | 2 +- .../DashboardPageQueryParam.java | 7 +- .../param/dashboard/DashboardQueryParam.java | 28 ++++++++ .../{ => dashboard}/DashboardUpdateParam.java | 2 +- .../domain/api/service/ChartService.java | 36 ++++++++-- .../domain/api/service/DashboardService.java | 32 ++++++--- .../domain/core/converter/ChartConverter.java | 4 +- .../core/converter/DashboardConverter.java | 4 +- .../domain/core/impl/ChartServiceImpl.java | 67 +++++++++++++++--- .../core/impl/DashboardServiceImpl.java | 52 +++++++++++--- .../domain/core/util/PermissionUtils.java | 17 +++++ .../ai/chat2db/server/start/Application.java | 2 +- .../V2_1_0__\350\241\245\345\205\205.sql" | 13 +++- .../common/model/EasyLambdaQueryWrapper.java | 8 +++ .../controller/config/ConfigController.java | 68 +++++++++++-------- .../controller/dashboard/ChartController.java | 34 +++++----- .../dashboard/DashboardController.java | 23 ++++--- .../converter/ChartWebConverter.java | 4 +- .../converter/DashboardWebConverter.java | 4 +- .../converter/DataSourceWebConverter.java | 13 ++-- 25 files changed, 376 insertions(+), 108 deletions(-) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/{param => chart}/ChartCreateParam.java (95%) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartListQueryParam.java rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/{param => chart}/ChartPageQueryParam.java (90%) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartQueryParam.java rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/{param => chart}/ChartUpdateParam.java (95%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => dashboard}/DashboardCreateParam.java (93%) rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => dashboard}/DashboardPageQueryParam.java (76%) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardQueryParam.java rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/{ => dashboard}/DashboardUpdateParam.java (94%) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartCreateParam.java similarity index 95% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartCreateParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartCreateParam.java index 58ee8dea8..a02aba770 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartCreateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartCreateParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.chart; import java.time.LocalDateTime; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartListQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartListQueryParam.java new file mode 100644 index 000000000..3c40a31b4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartListQueryParam.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.domain.api.chart; + +import java.util.List; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +/** + * query + * + * @author Jiaju Zhuang + */ +@Data +@NoArgsConstructor +public class ChartListQueryParam { + + /** + * 主键 + */ + @NonNull + private List idList; + + /** + * 用户id + */ + @NonNull + private Long userId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartPageQueryParam.java similarity index 90% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartPageQueryParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartPageQueryParam.java index dba8847d6..589efab29 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartPageQueryParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.chart; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartQueryParam.java new file mode 100644 index 000000000..6de1cfb54 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartQueryParam.java @@ -0,0 +1,28 @@ +package ai.chat2db.server.domain.api.chart; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +/** + * query + * + * @author Jiaju Zhuang + */ +@Data +@NoArgsConstructor +public class ChartQueryParam { + + /** + * 主键 + */ + @NonNull + private Long id; + + /** + * 用户id + */ + @NonNull + private Long userId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartUpdateParam.java similarity index 95% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartUpdateParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartUpdateParam.java index 05ba036fe..2bbaad49f 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/ChartUpdateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/chart/ChartUpdateParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.chart; import java.time.LocalDateTime; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardCreateParam.java similarity index 93% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardCreateParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardCreateParam.java index 7c90eabf7..6e4acd505 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardCreateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardCreateParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.dashboard; import java.time.LocalDateTime; import java.util.List; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardPageQueryParam.java similarity index 76% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardPageQueryParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardPageQueryParam.java index d911d85cb..8e6288959 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardPageQueryParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.dashboard; import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; @@ -17,4 +17,9 @@ public class DashboardPageQueryParam extends PageQueryParam { */ private String searchKey; + /** + * 用户id + */ + private Long userId; + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardQueryParam.java new file mode 100644 index 000000000..236b3be8a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardQueryParam.java @@ -0,0 +1,28 @@ +package ai.chat2db.server.domain.api.param.dashboard; + +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.NonNull; + +/** + * query + * + * @author Jiaju Zhuang + */ +@Data +@NoArgsConstructor +public class DashboardQueryParam { + + /** + * 主键 + */ + @NonNull + private Long id; + + /** + * 用户id + */ + @NonNull + private Long userId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardUpdateParam.java similarity index 94% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardUpdateParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardUpdateParam.java index 6e85cc50c..51c187bb6 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/DashboardUpdateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardUpdateParam.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.domain.api.param; +package ai.chat2db.server.domain.api.param.dashboard; import java.time.LocalDateTime; import java.util.List; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java index 370e8141b..c831cbc5a 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/ChartService.java @@ -2,9 +2,11 @@ import java.util.List; +import ai.chat2db.server.domain.api.chart.ChartCreateParam; +import ai.chat2db.server.domain.api.chart.ChartListQueryParam; +import ai.chat2db.server.domain.api.chart.ChartQueryParam; +import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.model.Chart; -import ai.chat2db.server.domain.api.param.ChartCreateParam; -import ai.chat2db.server.domain.api.param.ChartUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -23,7 +25,7 @@ public interface ChartService { * @param param * @return */ - DataResult create(ChartCreateParam param); + DataResult createWithPermission(ChartCreateParam param); /** * 更新报表 @@ -31,7 +33,7 @@ public interface ChartService { * @param param * @return */ - ActionResult update(ChartUpdateParam param); + ActionResult updateWithPermission(ChartUpdateParam param); /** * 根据id查询 @@ -41,6 +43,30 @@ public interface ChartService { */ DataResult find(@NotNull Long id); + /** + * 查询一条数据 + * + * @param param + * @return + */ + DataResult queryExistent(@NotNull ChartQueryParam param); + + /** + * 查询一条数据 + * + * @param id + * @return + */ + DataResult queryExistent(@NotNull Long id); + + /** + * 查询多条数据 + * + * @param param + * @return + */ + ListResult listQuery(@NotNull ChartListQueryParam param); + /** * 通过ID查询图表列表 * @@ -55,6 +81,6 @@ public interface ChartService { * @param id * @return */ - ActionResult delete(@NotNull Long id); + ActionResult deleteWithPermission(@NotNull Long id); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java index 5fac12375..07a765c25 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java @@ -1,14 +1,14 @@ package ai.chat2db.server.domain.api.service; -import jakarta.validation.constraints.NotNull; - import ai.chat2db.server.domain.api.model.Dashboard; -import ai.chat2db.server.domain.api.param.DashboardPageQueryParam; -import ai.chat2db.server.domain.api.param.DashboardCreateParam; -import ai.chat2db.server.domain.api.param.DashboardUpdateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import jakarta.validation.constraints.NotNull; /** * @author moji @@ -23,7 +23,7 @@ public interface DashboardService { * @param param * @return */ - DataResult create(DashboardCreateParam param); + DataResult createWithPermission(DashboardCreateParam param); /** * 更新报表 @@ -31,7 +31,7 @@ public interface DashboardService { * @param param * @return */ - ActionResult update(DashboardUpdateParam param); + ActionResult updateWithPermission(DashboardUpdateParam param); /** * 根据id查询 @@ -41,13 +41,29 @@ public interface DashboardService { */ DataResult find(@NotNull Long id); + /** + * 查询一条数据 + * + * @param param + * @return + */ + DataResult queryExistent(@NotNull DashboardQueryParam param); + + /** + * 查询一条数据 + * + * @param id + * @return + */ + DataResult queryExistent(@NotNull Long id); + /** * 删除 * * @param id * @return */ - ActionResult delete(@NotNull Long id); + ActionResult deleteWithPermission(@NotNull Long id); /** * 查询报表列表 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ChartConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ChartConverter.java index e0e2b004a..8db1b6c19 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ChartConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/ChartConverter.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Chart; -import ai.chat2db.server.domain.api.param.ChartCreateParam; -import ai.chat2db.server.domain.api.param.ChartUpdateParam; +import ai.chat2db.server.domain.api.chart.ChartCreateParam; +import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.repository.entity.ChartDO; import org.mapstruct.Mapper; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DashboardConverter.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DashboardConverter.java index eafc0abe9..e82f35634 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DashboardConverter.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/converter/DashboardConverter.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Dashboard; -import ai.chat2db.server.domain.api.param.DashboardCreateParam; -import ai.chat2db.server.domain.api.param.DashboardUpdateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.repository.entity.DashboardDO; import org.mapstruct.Mapper; diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java index 02932015d..3b5b5a474 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/ChartServiceImpl.java @@ -3,17 +3,19 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; +import ai.chat2db.server.domain.api.chart.ChartCreateParam; +import ai.chat2db.server.domain.api.chart.ChartListQueryParam; +import ai.chat2db.server.domain.api.chart.ChartQueryParam; +import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.model.Chart; import ai.chat2db.server.domain.api.model.DataSource; -import ai.chat2db.server.domain.api.param.ChartCreateParam; -import ai.chat2db.server.domain.api.param.ChartUpdateParam; import ai.chat2db.server.domain.api.service.ChartService; import ai.chat2db.server.domain.api.service.DataSourceService; import ai.chat2db.server.domain.core.converter.ChartConverter; +import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.entity.ChartDO; import ai.chat2db.server.domain.repository.entity.DashboardChartRelationDO; import ai.chat2db.server.domain.repository.mapper.ChartMapper; @@ -22,8 +24,11 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; - +import ai.chat2db.server.tools.common.exception.DataNotFoundException; +import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -50,7 +55,7 @@ public class ChartServiceImpl implements ChartService { private ChartConverter chartConverter; @Override - public DataResult create(ChartCreateParam param) { + public DataResult createWithPermission(ChartCreateParam param) { param.setGmtCreate(LocalDateTime.now()); param.setGmtModified(LocalDateTime.now()); param.setDeleted(YesOrNoEnum.NO.getLetter()); @@ -60,7 +65,10 @@ public DataResult create(ChartCreateParam param) { } @Override - public ActionResult update(ChartUpdateParam param) { + public ActionResult updateWithPermission(ChartUpdateParam param) { + Chart data = queryExistent(param.getId()).getData(); + PermissionUtils.checkOperationPermission(data.getUserId()); + param.setGmtModified(LocalDateTime.now()); ChartDO chartDO = chartConverter.updateParam2do(param); chartMapper.updateById(chartDO); @@ -79,11 +87,50 @@ public DataResult find(Long id) { } @Override - public ActionResult delete(Long id) { - ChartDO chartDO = chartMapper.selectById(id); - if (Objects.isNull(chartDO)) { - return ActionResult.isSuccess(); + public DataResult queryExistent(ChartQueryParam param) { + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); + queryWrapper + .eq(ChartDO::getDeleted, YesOrNoEnum.NO.getLetter()) + .eqWhenPresent(ChartDO::getId, param.getId()) + .eqWhenPresent(ChartDO::getUserId, param.getUserId()); + IPage page = chartMapper.selectPage(new Page<>(1, 1), queryWrapper); + if (CollectionUtils.isEmpty(page.getRecords())) { + throw new DataNotFoundException(); + } + Chart data = chartConverter.do2model(page.getRecords().get(0)); + setDataSourceInfo(Lists.newArrayList(data)); + return DataResult.of(data); + } + + @Override + public DataResult queryExistent(Long id) { + DataResult dataResult = find(id); + if (dataResult.getData() == null) { + throw new DataNotFoundException(); } + return dataResult; + } + + @Override + public ListResult listQuery(ChartListQueryParam param) { + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); + queryWrapper + .eq(ChartDO::getDeleted, YesOrNoEnum.NO.getLetter()) + .inWhenPresent(ChartDO::getId, param.getIdList()) + .eqWhenPresent(ChartDO::getUserId, param.getUserId()); + List queryList = chartMapper.selectList(queryWrapper); + List list = chartConverter.do2model(queryList); + setDataSourceInfo(list); + return ListResult.of(list); + } + + @Override + public ActionResult deleteWithPermission(Long id) { + Chart data = queryExistent(id).getData(); + PermissionUtils.checkOperationPermission(data.getUserId()); + + ChartDO chartDO = new ChartDO(); + chartDO.setId(id); chartDO.setDeleted(YesOrNoEnum.YES.getLetter()); chartMapper.updateById(chartDO); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java index 880fef138..552a65504 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java @@ -5,11 +5,13 @@ import java.util.Objects; import ai.chat2db.server.domain.api.model.Dashboard; -import ai.chat2db.server.domain.api.param.DashboardCreateParam; -import ai.chat2db.server.domain.api.param.DashboardPageQueryParam; -import ai.chat2db.server.domain.api.param.DashboardUpdateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.api.service.DashboardService; import ai.chat2db.server.domain.core.converter.DashboardConverter; +import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.domain.repository.entity.DashboardChartRelationDO; import ai.chat2db.server.domain.repository.entity.DashboardDO; import ai.chat2db.server.domain.repository.mapper.DashboardChartRelationMapper; @@ -18,7 +20,9 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; - +import ai.chat2db.server.tools.common.exception.DataNotFoundException; +import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; +import ai.chat2db.server.tools.common.util.ContextUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; @@ -45,10 +49,11 @@ public class DashboardServiceImpl implements DashboardService { private DashboardConverter dashboardConverter; @Override - public DataResult create(DashboardCreateParam param) { + public DataResult createWithPermission(DashboardCreateParam param) { param.setGmtCreate(LocalDateTime.now()); param.setGmtModified(LocalDateTime.now()); param.setDeleted(YesOrNoEnum.NO.getLetter()); + param.setUserId(ContextUtils.getUserId()); DashboardDO dashboardDO = dashboardConverter.param2do(param); dashboardMapper.insert(dashboardDO); insertDashboardRelation(dashboardDO.getId(), param.getChartIds()); @@ -56,7 +61,10 @@ public DataResult create(DashboardCreateParam param) { } @Override - public ActionResult update(DashboardUpdateParam param) { + public ActionResult updateWithPermission(DashboardUpdateParam param) { + Dashboard data = queryExistent(param.getId()).getData(); + PermissionUtils.checkOperationPermission(data.getUserId()); + param.setGmtModified(LocalDateTime.now()); DashboardDO dashboardDO = dashboardConverter.updateParam2do(param); dashboardMapper.updateById(dashboardDO); @@ -84,11 +92,35 @@ public DataResult find(Long id) { } @Override - public ActionResult delete(Long id) { - DashboardDO dashboardDO = dashboardMapper.selectById(id); - if (Objects.isNull(dashboardDO)) { - return ActionResult.isSuccess(); + public DataResult queryExistent(DashboardQueryParam param) { + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); + queryWrapper + .eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()) + .eqWhenPresent(DashboardDO::getId, param.getId()) + .eqWhenPresent(DashboardDO::getUserId, param.getUserId()); + IPage page = dashboardMapper.selectPage(new Page<>(1, 1), queryWrapper); + if (CollectionUtils.isEmpty(page.getRecords())) { + throw new DataNotFoundException(); + } + return DataResult.of(dashboardConverter.do2model(page.getRecords().get(0))); + } + + @Override + public DataResult queryExistent(Long id) { + DataResult dataResult = find(id); + if (dataResult.getData() == null) { + throw new DataNotFoundException(); } + return dataResult; + } + + @Override + public ActionResult deleteWithPermission(Long id) { + Dashboard data = queryExistent(id).getData(); + PermissionUtils.checkOperationPermission(data.getUserId()); + + DashboardDO dashboardDO = new DashboardDO(); + dashboardDO.setId(id); dashboardDO.setDeleted(YesOrNoEnum.YES.getLetter()); dashboardMapper.updateById(dashboardDO); deleteDashboardRelation(id); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java index d14357c52..05bb2f33d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/util/PermissionUtils.java @@ -56,4 +56,21 @@ public static boolean checkBaseQueryPermission(Long createUserId) { // Administrators can edit anything return loginUser.getAdmin(); } + + /** + * Verify if it is an administrator + * + * @return + */ + public static void checkDeskTopOrAdmin() { + LoginUser loginUser = ContextUtils.getLoginUser(); + // Representative is desktop mode + if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { + return; + } + if (loginUser.getAdmin()) { + return; + } + throw new PermissionDeniedBusinessException(); + } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 7f8cfbc98..92dab365d 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -55,7 +55,7 @@ public static void main(String[] args) { // The user did not specify the jws key if (StringUtils.isBlank(jwtSecretKey)) { if (StringUtils.isBlank(configJson.getJwtSecretKey())) { - configJson.setJwtSecretKey(UUID.fastUUID().toString().replaceAll("-", "")); + configJson.setJwtSecretKey(UUID.fastUUID().toString(true)); ConfigUtils.setConfig(configJson); } // Ensure that the jwt Secret Key for each application is unique diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" index a31ddc231..d7db80e58 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" @@ -2,4 +2,15 @@ ALTER TABLE `operation_saved` modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; update operation_saved -set user_id= 1; \ No newline at end of file +set user_id= 1; + +ALTER TABLE `dashboard` + modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +update dashboard +set user_id= 1; + + +ALTER TABLE `chart` + modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +update chart +set user_id= 1; diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java index b780fc6bf..e658435fe 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/EasyLambdaQueryWrapper.java @@ -1,6 +1,7 @@ package ai.chat2db.server.tools.common.model; import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -46,6 +47,13 @@ public EasyLambdaQueryWrapper eqWhenPresent(SFunction column, Object va return typedThis; } + public EasyLambdaQueryWrapper inWhenPresent(SFunction column, Collection coll) { + if (coll != null) { + return in(true, column, coll); + } + return typedThis; + } + // The following are the methods that come with the system /** * 查询字段 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index c50c5e41b..bc57e53d1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -5,21 +5,19 @@ import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.AIConfig; -import ai.chat2db.server.domain.api.model.ChatGptConfig; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; +import ai.chat2db.server.domain.core.util.PermissionUtils; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; +import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.controller.config.request.AIConfigCreateRequest; -import ai.chat2db.server.web.api.controller.config.request.AISystemConfigRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; import ai.chat2db.server.web.api.util.OpenAIClient; -import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; - import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -27,7 +25,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -53,7 +50,6 @@ public ActionResult systemConfig(@RequestBody SystemConfigRequest request) { return ActionResult.isSuccess(); } - /** * 保存ChatGPT相关配置 * @@ -62,6 +58,8 @@ public ActionResult systemConfig(@RequestBody SystemConfigRequest request) { */ @PostMapping("/system_config/ai") public ActionResult addChatGptSystemConfig(@RequestBody AIConfigCreateRequest request) { + PermissionUtils.checkDeskTopOrAdmin(); + String sqlSource = request.getAiSqlSource(); AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); if (Objects.isNull(aiSqlSourceEnum)) { @@ -73,16 +71,16 @@ public ActionResult addChatGptSystemConfig(@RequestBody AIConfigCreateRequest re configService.createOrUpdate(param); switch (Objects.requireNonNull(aiSqlSourceEnum)) { - case OPENAI : + case OPENAI: saveOpenAIConfig(request); break; case CHAT2DBAI: saveChat2dbAIConfig(request); break; - case RESTAI : + case RESTAI: saveRestAIConfig(request); break; - case AZUREAI : + case AZUREAI: saveAzureAIConfig(request); break; } @@ -132,7 +130,7 @@ private void saveOpenAIConfig(AIConfigCreateRequest request) { */ private void saveRestAIConfig(AIConfigCreateRequest request) { SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content( - request.getApiHost()).build(); + request.getApiHost()).build(); configService.createOrUpdate(restParam); SystemConfigParam methodParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_STREAM_OUT).content( request.getStream().toString()).build(); @@ -146,14 +144,17 @@ private void saveRestAIConfig(AIConfigCreateRequest request) { * @param request */ private void saveAzureAIConfig(AIConfigCreateRequest request) { - SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY).content( - request.getApiKey()).build(); + SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY) + .content( + request.getApiKey()).build(); configService.createOrUpdate(apikeyParam); - SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT).content( - request.getApiHost()).build(); + SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT) + .content( + request.getApiHost()).build(); configService.createOrUpdate(endpointParam); - SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID).content( - request.getModel()).build(); + SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID) + .content( + request.getModel()).build(); configService.createOrUpdate(modelParam); AzureOpenAIClient.refresh(); } @@ -186,21 +187,24 @@ public DataResult getChatAiSystemConfig(String aiSqlSource) { } config.setAiSqlSource(aiSqlSource); switch (Objects.requireNonNull(aiSqlSourceEnum)) { - case OPENAI : + case OPENAI: DataResult apiKey = configService.find(OpenAIClient.OPENAI_KEY); DataResult apiHost = configService.find(OpenAIClient.OPENAI_HOST); DataResult httpProxyHost = configService.find(OpenAIClient.PROXY_HOST); DataResult httpProxyPort = configService.find(OpenAIClient.PROXY_PORT); config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : ""); config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : ""); - config.setHttpProxyHost(Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : ""); - config.setHttpProxyPort(Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : ""); + config.setHttpProxyHost( + Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : ""); + config.setHttpProxyPort( + Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : ""); break; case CHAT2DBAI: DataResult chat2dbApiKey = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY); DataResult chat2dbApiHost = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_HOST); config.setApiKey(Objects.nonNull(chat2dbApiKey.getData()) ? chat2dbApiKey.getData().getContent() : ""); - config.setApiHost(Objects.nonNull(chat2dbApiHost.getData()) ? chat2dbApiHost.getData().getContent() : ""); + config.setApiHost( + Objects.nonNull(chat2dbApiHost.getData()) ? chat2dbApiHost.getData().getContent() : ""); break; case AZUREAI: DataResult azureApiKey = configService.find(AzureOpenAIClient.AZURE_CHATGPT_API_KEY); @@ -303,13 +307,16 @@ public DataResult getChatAiSystemConfig(String aiSqlSource) { // * @param request // */ //private void saveAzureAIConfig(AISystemConfigRequest request) { - // SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY).content( + // SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY) + // .content( // request.getAzureApiKey()).build(); // configService.createOrUpdate(apikeyParam); - // SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT).content( + // SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient + // .AZURE_CHATGPT_ENDPOINT).content( // request.getAzureEndpoint()).build(); // configService.createOrUpdate(endpointParam); - // SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID).content( + // SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient + // .AZURE_CHATGPT_DEPLOYMENT_ID).content( // request.getAzureDeploymentId()).build(); // configService.createOrUpdate(modelParam); // AzureOpenAIClient.refresh(); @@ -334,7 +341,8 @@ public DataResult getChatAiSystemConfig(String aiSqlSource) { // DataResult azureDeployId = configService.find(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID); // ChatGptConfig config = new ChatGptConfig(); // - // String sqlSource = Objects.nonNull(aiSqlSource.getData()) ? aiSqlSource.getData().getContent() : AiSqlSourceEnum.CHAT2DBAI.getCode(); + // String sqlSource = Objects.nonNull(aiSqlSource.getData()) ? aiSqlSource.getData().getContent() : + // AiSqlSourceEnum.CHAT2DBAI.getCode(); // AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); // if (Objects.isNull(aiSqlSourceEnum)) { // aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI; @@ -358,11 +366,15 @@ public DataResult getChatAiSystemConfig(String aiSqlSource) { // config.setRestAiUrl(Objects.nonNull(restAiUrl.getData()) ? restAiUrl.getData().getContent() : null); // config.setRestAiStream(Objects.nonNull(restAiHttpMethod.getData()) ? Boolean.valueOf( // restAiHttpMethod.getData().getContent()) : Boolean.TRUE); - // config.setHttpProxyHost(Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : null); - // config.setHttpProxyPort(Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : null); + // config.setHttpProxyHost(Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : + // null); + // config.setHttpProxyPort(Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : + // null); // config.setAzureApiKey(Objects.nonNull(azureApiKey.getData()) ? azureApiKey.getData().getContent() : null); - // config.setAzureEndpoint(Objects.nonNull(azureEndpoint.getData()) ? azureEndpoint.getData().getContent() : null); - // config.setAzureDeploymentId(Objects.nonNull(azureDeployId.getData()) ? azureDeployId.getData().getContent() : null); + // config.setAzureEndpoint(Objects.nonNull(azureEndpoint.getData()) ? azureEndpoint.getData().getContent() : + // null); + // config.setAzureDeploymentId(Objects.nonNull(azureDeployId.getData()) ? azureDeployId.getData().getContent() + // : null); // return DataResult.of(config); //} } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java index 42b719647..b393a38e7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java @@ -1,20 +1,19 @@ package ai.chat2db.server.web.api.controller.dashboard; -import java.util.List; - -import ai.chat2db.server.domain.api.model.Chart; -import ai.chat2db.server.domain.api.param.ChartCreateParam; -import ai.chat2db.server.domain.api.param.ChartUpdateParam; +import ai.chat2db.server.domain.api.chart.ChartCreateParam; +import ai.chat2db.server.domain.api.chart.ChartListQueryParam; +import ai.chat2db.server.domain.api.chart.ChartQueryParam; +import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.domain.api.service.ChartService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.dashboard.converter.ChartWebConverter; import ai.chat2db.server.web.api.controller.dashboard.request.ChartCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartQueryRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.ChartVO; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -42,7 +41,6 @@ public class ChartController { @Autowired private ChartWebConverter chartWebConverter; - /** * 根据id查询图表详情 * @@ -51,9 +49,11 @@ public class ChartController { */ @GetMapping("/{id}") public DataResult get(@PathVariable("id") Long id) { - Chart chart = chartService.find(id).getData(); - ChartVO chartVO = chartWebConverter.model2vo(chart); - return DataResult.of(chartVO); + ChartQueryParam param = new ChartQueryParam(); + param.setId(id); + param.setUserId(ContextUtils.getUserId()); + return chartService.queryExistent(param) + .map(chartWebConverter::model2vo); } /** @@ -64,9 +64,11 @@ public DataResult get(@PathVariable("id") Long id) { */ @GetMapping("/listByIds") public ListResult list(ChartQueryRequest request) { - List charts = chartService.queryByIds(request.getIds()).getData(); - List chartVOS = chartWebConverter.model2vo(charts); - return ListResult.of(chartVOS); + ChartListQueryParam param = new ChartListQueryParam(); + param.setIdList(request.getIds()); + param.setUserId(ContextUtils.getUserId()); + return chartService.listQuery(param) + .map(chartWebConverter::model2vo); } /** @@ -78,7 +80,7 @@ public ListResult list(ChartQueryRequest request) { @PostMapping("/create") public DataResult create(@RequestBody ChartCreateRequest request) { ChartCreateParam chartCreateParam = chartWebConverter.req2param(request); - return chartService.create(chartCreateParam); + return chartService.createWithPermission(chartCreateParam); } /** @@ -90,7 +92,7 @@ public DataResult create(@RequestBody ChartCreateRequest request) { @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody ChartUpdateRequest request) { ChartUpdateParam param = chartWebConverter.req2updateParam(request); - return chartService.update(param); + return chartService.updateWithPermission(param); } /** @@ -101,7 +103,7 @@ public ActionResult update(@RequestBody ChartUpdateRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { - return chartService.delete(id); + return chartService.deleteWithPermission(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java index 44e682bab..ecf48b9ac 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/DashboardController.java @@ -3,14 +3,16 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Dashboard; -import ai.chat2db.server.domain.api.param.DashboardCreateParam; -import ai.chat2db.server.domain.api.param.DashboardPageQueryParam; -import ai.chat2db.server.domain.api.param.DashboardUpdateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardPageQueryParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardQueryParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.domain.api.service.DashboardService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.dashboard.converter.DashboardWebConverter; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardUpdateRequest; @@ -51,6 +53,7 @@ public class DashboardController { */ @GetMapping("/list") public WebPageResult list(DashboardPageQueryParam request) { + request.setUserId(ContextUtils.getUserId()); PageResult result = dashboardService.queryPage(request); List dashboardVOS = dashboardWebConverter.model2vo(result.getData()); return WebPageResult.of(dashboardVOS, result.getTotal(), result.getPageNo(), result.getPageSize()); @@ -64,9 +67,11 @@ public WebPageResult list(DashboardPageQueryParam request) { */ @GetMapping("/{id}") public DataResult get(@PathVariable("id") Long id) { - Dashboard dashboard = dashboardService.find(id).getData(); - DashboardVO dashboardVO = dashboardWebConverter.model2vo(dashboard); - return DataResult.of(dashboardVO); + DashboardQueryParam param = new DashboardQueryParam(); + param.setId(id); + param.setUserId(ContextUtils.getUserId()); + return dashboardService.queryExistent(param) + .map(dashboardWebConverter::model2vo); } /** @@ -78,7 +83,7 @@ public DataResult get(@PathVariable("id") Long id) { @PostMapping("/create") public DataResult create(@RequestBody DashboardCreateRequest request) { DashboardCreateParam param = dashboardWebConverter.req2param(request); - return dashboardService.create(param); + return dashboardService.createWithPermission(param); } /** @@ -90,7 +95,7 @@ public DataResult create(@RequestBody DashboardCreateRequest request) { @RequestMapping(value = "/update", method = {RequestMethod.POST, RequestMethod.PUT}) public ActionResult update(@RequestBody DashboardUpdateRequest request) { DashboardUpdateParam param = dashboardWebConverter.req2updateParam(request); - return dashboardService.update(param); + return dashboardService.updateWithPermission(param); } /** @@ -101,6 +106,6 @@ public ActionResult update(@RequestBody DashboardUpdateRequest request) { */ @DeleteMapping("/{id}") public ActionResult delete(@PathVariable("id") Long id) { - return dashboardService.delete(id); + return dashboardService.deleteWithPermission(id); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/ChartWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/ChartWebConverter.java index 5b5c93d96..6cca23e61 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/ChartWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/ChartWebConverter.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Chart; -import ai.chat2db.server.domain.api.param.ChartCreateParam; -import ai.chat2db.server.domain.api.param.ChartUpdateParam; +import ai.chat2db.server.domain.api.chart.ChartCreateParam; +import ai.chat2db.server.domain.api.chart.ChartUpdateParam; import ai.chat2db.server.web.api.controller.dashboard.request.ChartCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.ChartVO; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/DashboardWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/DashboardWebConverter.java index b60bd02da..fed7ae7b1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/DashboardWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/converter/DashboardWebConverter.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.server.domain.api.model.Dashboard; -import ai.chat2db.server.domain.api.param.DashboardCreateParam; -import ai.chat2db.server.domain.api.param.DashboardUpdateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardCreateParam; +import ai.chat2db.server.domain.api.param.dashboard.DashboardUpdateParam; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardCreateRequest; import ai.chat2db.server.web.api.controller.dashboard.request.DashboardUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.DashboardVO; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java index 252e49e70..26802232e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/converter/DataSourceWebConverter.java @@ -2,13 +2,13 @@ import java.util.List; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; import ai.chat2db.server.domain.api.model.DataSource; -import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; -import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; -import ai.chat2db.spi.model.Database; import ai.chat2db.server.domain.api.param.ConsoleCloseParam; import ai.chat2db.server.domain.api.param.ConsoleConnectParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourcePreConnectParam; import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleCloseRequest; import ai.chat2db.server.web.api.controller.data.source.request.ConsoleConnectRequest; @@ -18,7 +18,7 @@ import ai.chat2db.server.web.api.controller.data.source.request.DataSourceUpdateRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DataSourceVO; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; - +import ai.chat2db.spi.model.Database; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; @@ -28,7 +28,7 @@ * @version DataSourceWebConverter.java, v 0.1 2022年09月23日 16:45 moji Exp $ * @date 2022/09/23 */ -@Mapper(componentModel = "spring") +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) public abstract class DataSourceWebConverter { /** @@ -38,7 +38,8 @@ public abstract class DataSourceWebConverter { * @return */ @Mappings({ - @Mapping(source = "user", target = "userName") + @Mapping(source = "user", target = "userName"), + @Mapping(target = "kind", expression = "java(DataSourceKindEnum.PRIVATE.getCode())") }) public abstract DataSourceCreateParam createReq2param(DataSourceCreateRequest request); From 4801d0f9c1647031d78b121e128dee918051cb85 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 2 Sep 2023 16:13:01 +0800 Subject: [PATCH 0695/1069] Complete team additions, deletions, modifications, and checks --- ...0\346\210\267\346\235\203\351\231\220.sql" | 18 +++++++++++ .../V2_1_0__\350\241\245\345\205\205.sql" | 32 +++++++++---------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" index 1a24e0730..65380506e 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -107,3 +107,21 @@ CREATE TABLE IF NOT EXISTS `data_source_access` create INDEX idx_data_source_access_data_source_id on data_source_access (`data_source_id`); create INDEX idx_data_source_access_access_object_id on data_source_access (`access_object_type`, `access_object_id`); create UNIQUE INDEX uk_data_source_access on data_source_access (`data_source_id`,`access_object_type`, `access_object_id`); + +ALTER TABLE `operation_saved` + modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; + +update operation_saved +set user_id= 1; + +ALTER TABLE `dashboard` + modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +update dashboard +set user_id= 1; + + +ALTER TABLE `chart` + modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +update chart +set user_id= 1; + diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" index d7db80e58..8aeedb33b 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/temp/V2_1_0__\350\241\245\345\205\205.sql" @@ -1,16 +1,16 @@ -ALTER TABLE `operation_saved` - modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; - -update operation_saved -set user_id= 1; - -ALTER TABLE `dashboard` - modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; -update dashboard -set user_id= 1; - - -ALTER TABLE `chart` - modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; -update chart -set user_id= 1; +# ALTER TABLE `operation_saved` +# modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +# +# update operation_saved +# set user_id= 1; +# +# ALTER TABLE `dashboard` +# modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +# update dashboard +# set user_id= 1; +# +# +# ALTER TABLE `chart` +# modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; +# update chart +# set user_id= 1; From 98bfd3a9d2fe106dca0186882db9e2b70a4dc899 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 2 Sep 2023 17:08:26 +0800 Subject: [PATCH 0696/1069] Complete team additions, deletions, modifications, and checks --- .../server/domain/api/model/Dashboard.java | 6 ++-- .../TeamUserComprehensivePageQueryParam.java | 8 +---- .../core/impl/DashboardServiceImpl.java | 12 +++---- .../domain/core/impl/TeamUserServiceImpl.java | 2 +- .../domain/repository/entity/DashboardDO.java | 10 +++--- .../repository/mapper/DashboardMapper.java | 4 +-- .../mapper/TeamUserCustomMapper.java | 3 +- .../mapper/DataSourceCustomMapper.xml | 14 +++++--- .../resources/mapper/TeamUserCustomMapper.xml | 8 ++--- .../config/Chat2dbWebMvcConfigurer.java | 18 +--------- ...0\346\210\267\346\235\203\351\231\220.sql" | 8 ++--- .../test/mybatis/MybatisGeneratorTest.java | 2 +- .../data/service/TableOperationsTest.java | 36 ++++++++++--------- .../common/model/EasyLambdaQueryWrapper.java | 7 ++++ .../tools/common/util/EasySqlUtils.java | 7 ++++ .../controller/dashboard/vo/DashboardVO.java | 6 ++-- 16 files changed, 75 insertions(+), 76 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Dashboard.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Dashboard.java index dc0be7037..13ceff6a3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Dashboard.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Dashboard.java @@ -1,6 +1,6 @@ package ai.chat2db.server.domain.api.model; -import java.time.LocalDateTime; +import java.util.Date; import java.util.List; import lombok.Data; @@ -21,12 +21,12 @@ public class Dashboard { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 报表名称 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java index 26f47e4e4..747ce57ca 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team/user/TeamUserComprehensivePageQueryParam.java @@ -20,13 +20,7 @@ public class TeamUserComprehensivePageQueryParam extends PageQueryParam { * 用户id */ private Long userId; - - /** - * 团队角色code - * - * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum - */ - private String teamRoleCode; + /** * Query keywords for team diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java index 552a65504..4825dc833 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java @@ -23,11 +23,11 @@ import ai.chat2db.server.tools.common.exception.DataNotFoundException; import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.tools.common.util.EasySqlUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -164,11 +164,11 @@ private void insertDashboardRelation(Long dashboardId, List chartIds) { @Override public PageResult queryPage(DashboardPageQueryParam param) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - if (StringUtils.isNotBlank(param.getSearchKey())) { - queryWrapper.like(DashboardDO::getName, param.getSearchKey()); - } - queryWrapper.eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()); + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); + queryWrapper + .eq(DashboardDO::getDeleted, YesOrNoEnum.NO.getLetter()) + .likeWhenPresent(DashboardDO::getName, EasySqlUtils.buildLikeRightFuzzy(param.getSearchKey())) + .eqWhenPresent(DashboardDO::getUserId, param.getUserId()); Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java index 43ceb1ba5..990313413 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamUserServiceImpl.java @@ -72,7 +72,7 @@ public PageResult comprehensivePageQuery(TeamUserComprehensivePageQuer Page page = new Page<>(param.getPageNo(), param.getPageSize()); page.setSearchCount(param.getEnableReturnCount()); IPage iPage = teamUserCustomMapper.comprehensivePageQuery(page, param.getTeamId(), - param.getUserId(), param.getTeamRoleCode(), param.getTeamSearchKey(), param.getUserSearchKey()); + param.getUserId(), param.getTeamSearchKey(), param.getUserSearchKey()); List list = teamUserConverter.do2dto(iPage.getRecords()); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DashboardDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DashboardDO.java index ad525efda..5d4cd4df0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DashboardDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/DashboardDO.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -13,8 +13,8 @@ * 自定义报表表 *

* - * @author ali-dbhub - * @since 2023-06-09 + * @author chat2db + * @since 2023-09-02 */ @Getter @Setter @@ -32,12 +32,12 @@ public class DashboardDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 报表名称 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DashboardMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DashboardMapper.java index 87fd3bf17..e53cf1abe 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DashboardMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/DashboardMapper.java @@ -8,8 +8,8 @@ * 自定义报表表 Mapper 接口 *

* - * @author ali-dbhub - * @since 2023-06-09 + * @author chat2db + * @since 2023-09-02 */ public interface DashboardMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java index dd77f43a1..a218e4ea5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TeamUserCustomMapper.java @@ -13,6 +13,5 @@ public interface TeamUserCustomMapper extends Mapper { IPage comprehensivePageQuery(IPage page, @Param("teamId") Long teamId, - @Param("userId") Long userId, @Param("teamRoleCode") String teamRoleCode, - @Param("teamSearchKey") String teamSearchKey, @Param("userSearchKey") String userSearchKey); + @Param("userId") Long userId, @Param("teamSearchKey") String teamSearchKey, @Param("userSearchKey") String userSearchKey); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml index b909a6910..cd4e5b6c5 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml @@ -17,15 +17,21 @@ and (ds.USER_ID = #{userId} - or exists(select 1 from DATA_SOURCE_ACCESS dsa where dsa.ACCESS_OBJECT_TYPE = 'USER' and - dsa.ACCESS_OBJECT_ID = #{userId}) - or exists(select 1 + or exists( + select 1 from DATA_SOURCE_ACCESS dsa where dsa.ACCESS_OBJECT_TYPE = 'USER' + and dsa.ACCESS_OBJECT_ID = #{userId} + and dsa.DATA_SOURCE_ID = ds.ID + ) + or exists ( + select 1 from DATA_SOURCE_ACCESS dsa LEFT JOIN TEAM_USER tu on tu.ID = dsa.ACCESS_OBJECT_ID and dsa.ACCESS_OBJECT_TYPE = 'TEAM' left join TEAM t on t.id = tu.TEAM_ID - where tu.USER_ID = #{userId}) + where tu.USER_ID = #{userId} + and dsa.DATA_SOURCE_ID = ds.ID and t.STATUS = 'VALID' ) + ) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml index 797964f7d..c81e38777 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TeamUserCustomMapper.xml @@ -4,7 +4,7 @@
tables(Connection connection, String databaseName, String schemaName, String tableName, - String types[]) { + String types[]) { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, - types)) { + types)) { return ResultSetUtils.toObjectList(resultSet, Table.class); } catch (SQLException e) { throw new RuntimeException(e); @@ -300,9 +317,9 @@ public List
tables(Connection connection, String databaseName, String sch * @return */ public List columns(Connection connection, String databaseName, String schemaName, String tableName, - String columnName) { + String columnName) { try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, - columnName)) { + columnName)) { return ResultSetUtils.toObjectList(resultSet, TableColumn.class); } catch (Exception e) { throw new RuntimeException(e); @@ -312,48 +329,47 @@ public List columns(Connection connection, String databaseName, Str /** * get all table index info * - * @param connection connection + * @param connection connection * @param databaseName databaseName of the index - * @param schemaName schemaName of the index - * @param tableName tableName of the index + * @param schemaName schemaName of the index + * @param tableName tableName of the index * @return List table index list */ public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, - false, - false)) { + false, + false)) { List tableIndexColumns = ResultSetUtils.toObjectList(resultSet, TableIndexColumn.class); tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( - Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() - .stream().forEach(entry -> { - TableIndex tableIndex = new TableIndex(); - TableIndexColumn column = entry.getValue().get(0); - tableIndex.setName(entry.getKey()); - tableIndex.setTableName(column.getTableName()); - tableIndex.setSchemaName(column.getSchemaName()); - tableIndex.setDatabaseName(column.getDatabaseName()); - tableIndex.setUnique(!column.getNonUnique()); - tableIndex.setColumnList(entry.getValue()); - tableIndices.add(tableIndex); - }); + Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() + .stream().forEach(entry -> { + TableIndex tableIndex = new TableIndex(); + TableIndexColumn column = entry.getValue().get(0); + tableIndex.setName(entry.getKey()); + tableIndex.setTableName(column.getTableName()); + tableIndex.setSchemaName(column.getSchemaName()); + tableIndex.setDatabaseName(column.getDatabaseName()); + tableIndex.setUnique(!column.getNonUnique()); + tableIndex.setColumnList(entry.getValue()); + tableIndices.add(tableIndex); + }); } catch (SQLException e) { throw new RuntimeException(e); } return tableIndices; } - /** * Get all functions available in a catalog. * - * @param connection connection + * @param connection connection * @param databaseName databaseName of the function - * @param schemaName schemaName of the function + * @param schemaName schemaName of the function * @return List */ public List functions(Connection connection, String databaseName, - String schemaName) { + String schemaName) { try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Function.class); } catch (Exception e) { @@ -362,11 +378,11 @@ public List functions(Connection connection, Stri } /** - * procedure list + * procedure list * - * @param connection connection + * @param connection connection * @param databaseName databaseName - * @param schemaName schemaName + * @param schemaName schemaName * @return List */ public List procedures(Connection connection, String databaseName, String schemaName) { From 999de1bcdb74143037663d08f018c17f09613b8d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 3 Sep 2023 15:56:33 +0800 Subject: [PATCH 0699/1069] feat: edit table init --- chat2db-client/.vscode/settings.json | 2 + chat2db-client/package.json | 1 + .../DatabaseTableEditor/BaseInfo/index.less | 8 + .../DatabaseTableEditor/BaseInfo/index.tsx | 50 +++ .../DatabaseTableEditor/ColumnList/index.less | 17 + .../DatabaseTableEditor/ColumnList/index.tsx | 330 ++++++++++++++++++ .../DatabaseTableEditor/IncludeCol/index.less | 23 ++ .../DatabaseTableEditor/IncludeCol/index.tsx | 160 +++++++++ .../DatabaseTableEditor/IndexList/index.less | 27 ++ .../DatabaseTableEditor/IndexList/index.tsx | 289 +++++++++++++++ .../src/blocks/DatabaseTableEditor/index.less | 63 ++++ .../src/blocks/DatabaseTableEditor/index.tsx | 68 ++++ chat2db-client/src/i18n/en-us/workspace.ts | 1 + chat2db-client/src/i18n/zh-cn/workspace.ts | 1 + chat2db-client/src/pages/demo/index.tsx | 9 +- .../Tree/TreeNodeRightClick/index.tsx | 9 + .../workspace/components/Tree/treeConfig.tsx | 7 +- .../components/WorkspaceRight/index.tsx | 6 +- chat2db-client/src/styles/antd.less | 3 +- chat2db-client/src/theme/background/dark.ts | 1 + .../src/theme/background/darkDimmed.ts | 3 +- chat2db-client/src/theme/background/light.ts | 1 + chat2db-client/yarn.lock | 8 + 23 files changed, 1073 insertions(+), 14 deletions(-) create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/index.less create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/index.tsx diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 18101fc29..e8f829bd6 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -37,7 +37,9 @@ "echarts", "favicons", "findstr", + "fulltext", "gtag", + "hexi", "Iconfont", "indexs", "JDBC", diff --git a/chat2db-client/package.json b/chat2db-client/package.json index c07b110c8..062b806f4 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -25,6 +25,7 @@ "start:web": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" }, "dependencies": { + "@dnd-kit/modifiers": "^6.0.1", "ahooks": "^3.7.7", "ali-react-table": "^2.6.1", "antd": "^5.6.0", diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less new file mode 100644 index 000000000..1f40d282c --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less @@ -0,0 +1,8 @@ +@import '../../../styles/var.less'; + +.box { +} + +.formBox{ + width: 50%; +} \ No newline at end of file diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx new file mode 100644 index 000000000..61a348a54 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -0,0 +1,50 @@ +import React, { memo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { + Form, + Input, +} from 'antd'; + +interface IProps { + className?: string; +} + +export const basicInfo = { + data: {} +} + +export default memo(function BaseInfo({ className }) { + const [form] = Form.useForm(); + + function onChangeForm(type: string) { + basicInfo.data = { + ...form.getFieldsValue() + } + console.log(basicInfo) + } + + return
+
+
+ + { onChangeForm('name') }} /> + + + { onChangeForm('comment') }} /> + + +
+
+}) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less new file mode 100644 index 000000000..72f156809 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less @@ -0,0 +1,17 @@ +@import '../../../styles/var.less'; + +.box { + height: 500px; +} + +.columnListHeader { + margin: 0px -10px 20px; + button { + margin: 0px 10px; + } +} + +.editableCell { + height: 28px; + line-height: 28px; +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx new file mode 100644 index 000000000..9b86d02d2 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -0,0 +1,330 @@ +import React, { memo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { MenuOutlined } from '@ant-design/icons'; +import type { DragEndEvent } from '@dnd-kit/core'; +import { DndContext } from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd'; +import { v4 as uuidv4 } from 'uuid' + +interface IProps { + className?: string; +} + +// 数据库字段类型 枚举 +enum DatabaseFieldType { + // 数字 + Number = 'number', + // 字符串 + String = 'string', + // 日期 + Date = 'date', + // 布尔 + Boolean = 'boolean', + // 二进制 + Binary = 'binary', + // 对象 + Object = 'object', +} + +const databaseFieldTypeList = [DatabaseFieldType['Number'], DatabaseFieldType['String'], DatabaseFieldType['Date'], DatabaseFieldType['Boolean'], DatabaseFieldType['Binary'], DatabaseFieldType['Object']] + +interface Item { + key: string; + columnName: string; + length: number | null; + fieldType: DatabaseFieldType; +} + +const mockData: Item[] = [ + { + key: uuidv4(), + columnName: 'John Brown', + length: 32, + fieldType: DatabaseFieldType.Binary, + }, + { + key: uuidv4(), + columnName: 'Jim Green', + length: 42, + fieldType: DatabaseFieldType.Number, + }, + { + key: uuidv4(), + columnName: 'Joe Black', + length: 32, + fieldType: DatabaseFieldType.String, + }, +] + +interface RowProps extends React.HTMLAttributes { + 'data-row-key': string; +} + +const Row = ({ children, ...props }: RowProps) => { + const { + attributes, + listeners, + setNodeRef, + setActivatorNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: props['data-row-key'], + }); + + const style: React.CSSProperties = { + ...props.style, + transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), + transition, + ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), + }; + + return ( +
+ {React.Children.map(children, (child) => { + if ((child as React.ReactElement).key === 'sort') { + return React.cloneElement(child as React.ReactElement, { + children: ( + + ), + }); + } + return child; + })} + + ); +}; + +const ColumnList: React.FC = () => { + const [dataSource, setDataSource] = useState(mockData); + const [form] = Form.useForm(); + const [editingKey, setEditingKey] = useState(''); + + const isEditing = (record: Item) => record.key === editingKey; + + const edit = (record: Partial & { key: React.Key }) => { + form.setFieldsValue({ ...record }); + setEditingKey(record.key); + }; + + const columns = [ + { + key: 'sort', + width: '40px', + align: 'center' + }, + { + title: 'columnName', + dataIndex: 'columnName', + render: (text: string, record: Item) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
edit(record)} + > + {text} +
+ ); + } + }, + { + title: 'length', + dataIndex: 'length', + editable: true, + render: (text: string, record: Item) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
edit(record)} + > + {text} +
+ ); + } + }, + { + title: 'fieldType', + dataIndex: 'fieldType', + render: (text: string, record: Item) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
edit(record)} + > + {text} +
+ ); + } + }, + ]; + + const onDragEnd = ({ active, over }: DragEndEvent) => { + if (active.id !== over?.id) { + setDataSource((previous) => { + const activeIndex = previous.findIndex((i) => i.key === active.id); + const overIndex = previous.findIndex((i) => i.key === over?.id); + return arrayMove(previous, activeIndex, overIndex); + }); + } + }; + + const formChange = (value: any) => { + const newData = form.getFieldsValue(); + setDataSource(dataSource.map(i => { + if (i.key === editingKey) { + return { + ...i, + ...newData + } + } + return i + })) + } + + const addData = () => { + const newData = { + key: uuidv4(), + columnName: '', + length: null, + fieldType: DatabaseFieldType.String, + } + setDataSource([...dataSource, newData]) + edit(newData) + } + + const deleteData = () => { + setDataSource(dataSource.filter(i => i.key !== editingKey)) + } + + const moveData = (action: 'up' | 'down') => { + const index = dataSource.findIndex(i => i.key === editingKey) + if (index === -1) { + return + } + if (action === 'up') { + if (index === 0) { + return + } + const newData = [...dataSource] + newData[index] = dataSource[index - 1] + newData[index - 1] = dataSource[index] + setDataSource(newData) + } else { + if (index === dataSource.length - 1) { + return + } + const newData = [...dataSource] + newData[index] = dataSource[index + 1] + newData[index + 1] = dataSource[index] + setDataSource(newData) + } + } + + return ( +
+
+ + + + +
+
+ + i.key)} + strategy={verticalListSortingStrategy} + > +
+ + + + + ); +}; + +export default ColumnList; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less new file mode 100644 index 000000000..168247910 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less @@ -0,0 +1,23 @@ +@import '../../../styles/var.less'; + +.box { + max-height: 60vh; + overflow-y: auto; + overflow-x: hidden; +} + +.ant-input-number { + width: 100%; +} + +.indexListHeader { + margin: 0px -10px 10px; + button { + margin: 0px 10px; + } +} + +.editableCell { + height: 28px; + line-height: 28px; +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx new file mode 100644 index 000000000..f173ab3a8 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -0,0 +1,160 @@ +import React, { memo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { MenuOutlined } from '@ant-design/icons'; +import { CSS } from '@dnd-kit/utilities'; +import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal, message } from 'antd'; +import { v4 as uuidv4 } from 'uuid' + +interface IProps { + className?: string; +} + +//索引类型 +enum IndexesType { + // 普通索引 + Normal = 'normal', + // 唯一索引 + Unique = 'unique', + // 全文索引 + Fulltext = 'fulltext', + // 空间索引 + Spatial = 'spatial', +} + +const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']] + +interface Item { + key: string; + columnInformation: string; + prefixLength: number | null; +} + +const createInitialData = () => { + return { + key: uuidv4(), + columnInformation: '', + prefixLength: null, + } +} + +const InitialDataSource = [createInitialData()] + +interface RowProps extends React.HTMLAttributes { + 'data-row-key': string; +} + +export default memo(function IndexList(props) { + const { className } = props; + const [dataSource, setDataSource] = useState(InitialDataSource); + const [form] = Form.useForm(); + const [editingKey, setEditingKey] = useState(dataSource[0]?.key); + + const isEditing = (record: Item) => record.key === editingKey; + + const edit = (record: Partial & { key: React.Key }) => { + form.setFieldsValue({ ...record }); + setEditingKey(record.key); + }; + + const addData = () => { + const newData = createInitialData() + setDataSource([...dataSource, newData]) + edit(newData) + } + + const deleteData = () => { + // if (dataSource.length === 1) { + // message.warning('至少保留一条数据') + // return + // } + + setDataSource(dataSource.filter(i => i.key !== editingKey)) + } + + const columns = [ + { + title: 'index', + dataIndex: 'index', + width: '10%', + render: (text: string, record: Item) => { + return dataSource.findIndex(i => i.key === record.key) + 1; + } + + }, + { + title: 'columnInformation', + dataIndex: 'columnInformation', + width: '45%', + render: (text: string, record: Item) => { + const editable = isEditing(record); + return editable ? ( + +
+ + +}) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less new file mode 100644 index 000000000..1e7e1eeeb --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less @@ -0,0 +1,27 @@ +@import '../../../styles/var.less'; + +.box { +} + +.indexListHeader { + margin: 0px -10px 20px; + button { + margin: 0px 10px; + } +} + +.editableCell { + height: 28px; + line-height: 28px; +} + +.columnInformation { + span { + margin-right: 8px; + color: var(--color-primary); + cursor: pointer; + &:hover { + color: var(--color-primary-hover); + } + } +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx new file mode 100644 index 000000000..c74049159 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -0,0 +1,289 @@ +import React, { memo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { MenuOutlined } from '@ant-design/icons'; +import type { DragEndEvent } from '@dnd-kit/core'; +import { DndContext } from '@dnd-kit/core'; +import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { + arrayMove, + SortableContext, + useSortable, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal } from 'antd'; +import { v4 as uuidv4 } from 'uuid'; +import IncludeCol from '../IncludeCol' + +interface IProps { + className?: string; +} + +//索引类型 +enum IndexesType { + // 普通索引 + Normal = 'normal', + // 唯一索引 + Unique = 'unique', + // 全文索引 + Fulltext = 'fulltext', + // 空间索引 + Spatial = 'spatial', +} + +const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']] + +interface Item { + key: string; + columnInformation: string[]; + indexName: string; + indexesType: IndexesType | null; +} + +const initialData: Item[] = [ + { + key: uuidv4(), + columnInformation: [], + indexName: '', + indexesType: null, + }, +] + +interface RowProps extends React.HTMLAttributes { + 'data-row-key': string; +} + +export default memo(function IndexList(props) { + const { className } = props; + const [dataSource, setDataSource] = useState(initialData); + const [form] = Form.useForm(); + const [editingKey, setEditingKey] = useState(dataSource[0]?.key); + const [includeColModalOpen, setIncludeColModalOpen] = useState(false); + + const isEditing = (record: Item) => record.key === editingKey; + + const edit = (record: Partial & { key: React.Key }) => { + form.setFieldsValue({ ...record }); + setEditingKey(record.key); + }; + + const addData = () => { + const newData = { + key: uuidv4(), + columnInformation: [], + indexName: '', + indexesType: null, + } + setDataSource([...dataSource, newData]) + edit(newData) + } + + const deleteData = () => { + setDataSource(dataSource.filter(i => i.key !== editingKey)) + } + + const moveData = (action: 'up' | 'down') => { + const index = dataSource.findIndex(i => i.key === editingKey) + if (index === -1) { + return + } + if (action === 'up') { + if (index === 0) { + return + } + const newData = [...dataSource] + newData[index] = dataSource[index - 1] + newData[index - 1] = dataSource[index] + setDataSource(newData) + } else { + if (index === dataSource.length - 1) { + return + } + const newData = [...dataSource] + newData[index] = dataSource[index + 1] + newData[index + 1] = dataSource[index] + setDataSource(newData) + } + } + + const formChange = (value: any) => { + const newData = form.getFieldsValue(); + setDataSource(dataSource.map(i => { + if (i.key === editingKey) { + return { + ...i, + ...newData + } + } + return i + })) + } + + const onDragEnd = ({ active, over }: DragEndEvent) => { + if (active.id !== over?.id) { + setDataSource((previous) => { + const activeIndex = previous.findIndex((i) => i.key === active.id); + const overIndex = previous.findIndex((i) => i.key === over?.id); + return arrayMove(previous, activeIndex, overIndex); + }); + } + }; + + const Row = ({ children, ...props }: RowProps) => { + const { + attributes, + listeners, + setNodeRef, + setActivatorNodeRef, + transform, + transition, + isDragging, + } = useSortable({ + id: props['data-row-key'], + }); + + const style: React.CSSProperties = { + ...props.style, + transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), + transition, + ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), + }; + + return ( + + {React.Children.map(children, (child) => { + if ((child as React.ReactElement).key === 'sort') { + return React.cloneElement(child as React.ReactElement, { + children: ( + + ), + }); + } + return child; + })} + + ); + }; + + const columns = [ + { + key: 'sort', + width: '60px', + }, + { + title: 'index', + width: '70px', + render: (text: string, record: Item) => { + return dataSource.findIndex(i => i.key === record.key) + 1 + } + }, + { + title: '索引名称', + dataIndex: 'indexName', + width: '30%', + render: (text: string, record: Item) => { + const editable = isEditing(record); + return editable ? ( + + + + ) :
edit(record)} + > + {text} +
+ } + }, + { + title: '索引类型', + dataIndex: 'indexesType', + width: '30%', + render: (text: string, record: Item) => { + const editable = isEditing(record); + return editable ? ( + + + + ) :
edit(record)} + > + {text} +
+ } + }, + { + title: '包含列', + dataIndex: 'columnInformation', + render: (text: string[], record: Item) => { + const editable = isEditing(record); + return editable ? ( +
+ { setIncludeColModalOpen(true) }}>编辑 + {text.join(',')} +
+ ) : ( +
edit(record)} + > + {text.join(',')} +
+ ); + } + }, + + ]; + + return
+
+ + + + +
+
+ + i.key)} + strategy={verticalListSortingStrategy} + > +
+ + + + { setIncludeColModalOpen(false) }} + maskClosable={false} + > + + + +}) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less new file mode 100644 index 000000000..85071c53f --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -0,0 +1,63 @@ +@import '../../styles/var.less'; + +.box { + height: 500px; + padding: 20px; + display: flex; + flex-direction: column; +} + +.tabList{ + display: flex; + border-bottom: 0; + margin-right: 20px; + font-size: 14px; + margin-bottom: 20px; +} + +.tabItem{ + position: relative; + height: 38px; + line-height: 38px; + padding:0px 18px; + border: 1px solid var(--color-border); + margin-right: 1px; + cursor: pointer; +} + +.currentTab{ + font-weight: 500; + color: var(--color-primary); + + &::before{ + content: ''; + position: absolute; + left: 0; + bottom: -1px; + right: 0px; + height: 2px; + background-color: var(--color-primary); + } +} + + + +.main{ + height: 500px; + overflow: hidden; + position: relative; +} + +.tab{ + z-index: 2; + position: absolute; + left: 0px; + top: 0px; + width: 100%; + height: 100%; +} + +.hidden{ + z-index: 1; + opacity: 0; +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx new file mode 100644 index 000000000..97bab9bd5 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -0,0 +1,68 @@ +import React, { memo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import IndexList from './IndexList'; +import ColumnList from './ColumnList'; +import BaseInfo from './BaseInfo'; + +interface IProps { + className?: string; +} + +interface ITabItem { + title: string; + key: string; + component: any; // TODO: 组件的Ts是什么 +} + +const tabList: ITabItem[] = [ + { + title: '基本信息', + key: 'basic', + component: + }, + { + title: '列信息', + key: 'column', + component: + }, + { + title: '索引信息', + key: 'index', + component: + }, +] + +export default memo(function DatabaseTableEditor(props) { + const { className } = props; + const [currentTab, setCurrentTab] = useState(tabList[1]); + + function changeTab(item: ITabItem) { + setCurrentTab(item) + } + + return
+
+ { + tabList.map((item, index) => { + return
+ {item.title} +
+ }) + } +
+
+ { + tabList.map(t => { + return
+ {t.component} +
+ }) + } +
+
+}) diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index cee30ecc3..6f05a02cc 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -4,6 +4,7 @@ export default { 'workspace.title.saved': 'Saved', 'workspace.menu.exportDDL': 'Export DDL', 'workspace.menu.deleteTable': 'Delete Table', + 'workspace.menu.editTable': 'Edit Table', 'workspace.menu.pin': 'Pin', 'workspace.menu.unPin': 'Unpin', 'workspace.menu.deleteTablePlaceHolder': 'Please enter the name of the table you want to delete', diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 9358dc2c3..88420177d 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -4,6 +4,7 @@ export default { 'workspace.title.saved': '保存记录', 'workspace.menu.exportDDL': '导出DDL', 'workspace.menu.deleteTable': '删除表', + 'workspace.menu.editTable': '编辑表', 'workspace.menu.pin': '置顶', 'workspace.menu.unPin': '取消置顶', 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index f41753b60..097048f22 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,14 +1,9 @@ import React, { memo } from 'react'; import i18n from '@/i18n'; -import { Button, Steps } from 'antd' -import { LoadingOutlined, SmileOutlined, SolutionOutlined, UserOutlined } from '@ant-design/icons'; -import styles from './index.less'; -import Setting from '@/blocks/Setting'; -import Tabs from '@/components/Tabs'; - +import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; export default function Demo() { return
- +
} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index af94505ec..3d4b76139 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -116,6 +116,15 @@ function TreeNodeRightClick(props: IProps) { } } }, + [OperationColumn.EditTable]: (data) => { + return { + text: i18n('workspace.menu.editTable'), + icon: '\ue602', + handle: () => { + setVerifyDialog(true); + } + } + }, [OperationColumn.EditSource]: (data) => { return { text: '编辑数据源', diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index 8fcb80047..b4a180adf 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -62,7 +62,8 @@ export enum OperationColumn { DeleteTable = 'deleteTable', ExportDDL = 'exportDDL', EditSource = 'editSource', - Top = 'top' + Top = 'top', + EditTable = 'editTable', } export interface ITreeConfigItem { @@ -104,7 +105,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { connectionService.getDBList(params).then(res => { - const data: ITreeNode[] = res.map((t:any)=> { + const data: ITreeNode[] = res.map((t: any) => { return { key: t.name, name: t.name, @@ -238,7 +239,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }) }, operationColumn: [ - OperationColumn.ExportDDL, OperationColumn.DeleteTable, OperationColumn.Top + OperationColumn.Top, OperationColumn.ExportDDL, OperationColumn.EditTable, OperationColumn.DeleteTable, ], }, 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 2ca5f9dd2..53cb74f79 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -10,6 +10,7 @@ import Tabs, { IOption } from '@/components/Tabs'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; import WorkspaceRightItem from '../WorkspaceRightItem'; +import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IAIState } from '@/models/ai'; import { handleLocalStorageSavedConsole } from '@/utils'; @@ -426,7 +427,8 @@ const WorkspaceRight = memo(function (props) { key={t.id} className={classnames(styles.consoleBox, { [styles.activeConsoleBox]: activeConsoleId === t.id })} > - + {/* (function (props) { workspaceModel={workspaceModel} aiModel={aiModel} dispatch={dispatch} - /> + /> */} ); })} diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index 69c4b262e..aa1b75b4c 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -1,6 +1,6 @@ :root { :global { - // 切换主题色时有一些动画导致切换延迟,这里禁用background动画 + // There is some animation when switching the theme color causing a delay in switching background .ant-input, .ant-input-password { transition: all 0.2s, background-color 0s; @@ -28,6 +28,7 @@ // color: var(--color-text) !important; // } // } + .ant-modal-footer { border-top: 0px; } diff --git a/chat2db-client/src/theme/background/dark.ts b/chat2db-client/src/theme/background/dark.ts index b83651baa..0bf31cedb 100644 --- a/chat2db-client/src/theme/background/dark.ts +++ b/chat2db-client/src/theme/background/dark.ts @@ -45,6 +45,7 @@ const antDarkTheme = { colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#0a0b0c', colorBgSubtle: '#131418', + colorBgElevated: '#0a0b0c', colorBorder: 'rgba(54, 55, 58,0.4)', colorBorderSecondary: 'rgba(54, 55, 58,0.4)', }, diff --git a/chat2db-client/src/theme/background/darkDimmed.ts b/chat2db-client/src/theme/background/darkDimmed.ts index 491a564f9..f10cf0d3d 100644 --- a/chat2db-client/src/theme/background/darkDimmed.ts +++ b/chat2db-client/src/theme/background/darkDimmed.ts @@ -45,9 +45,10 @@ const antdLightTheme = { colorHoverBg: 'hsla(0, 0%, 100%, 0.03)', colorBgContainer: '#1c2128', colorBgSubtle: '#22272e', + colorBgElevated: '#1c2128', colorBorder: 'rgba(55, 62, 71, 0.4)', colorBorderSecondary: 'rgba(55, 62, 71, 0.4)', - controlItemBgActive: 'rgba(241, 241, 244, 0.08);', + controlItemBgActive: 'rgba(241, 241, 244, 0.08)', // ...commonToken, // colorText: "rgb(241, 241, 244)", // colorBgBase: '#191a23', diff --git a/chat2db-client/src/theme/background/light.ts b/chat2db-client/src/theme/background/light.ts index 07e0719a5..8bd98f368 100644 --- a/chat2db-client/src/theme/background/light.ts +++ b/chat2db-client/src/theme/background/light.ts @@ -45,6 +45,7 @@ const antdLightTheme = { colorHoverBg: 'rgba(0, 0, 0, 0.03)', colorBgContainer: '#fff', colorBgSubtle: '#f6f8fa', + colorBgElevated: '#fff', colorBorder: 'rgba(211, 211, 212, 0.4)', colorBorderSecondary: 'rgba(211, 211, 212, 0.4)', }, diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index ad1eec950..ba96d4b45 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1527,6 +1527,14 @@ "@dnd-kit/utilities" "^3.2.1" tslib "^2.0.0" +"@dnd-kit/modifiers@^6.0.1": + version "6.0.1" + resolved "https://registry.npmmirror.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz#9e39b25fd6e323659604cc74488fe044d33188c8" + integrity sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A== + dependencies: + "@dnd-kit/utilities" "^3.2.1" + tslib "^2.0.0" + "@dnd-kit/sortable@^7.0.2": version "7.0.2" resolved "https://registry.npmmirror.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz#791d550872457f3f3c843e00d159b640f982011c" From d5da0be959e1d0a790382147dcb232a3e7ba35b8 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 3 Sep 2023 15:57:03 +0800 Subject: [PATCH 0700/1069] cSpell.words --- .vscode/settings.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index eace4f66d..3824d0703 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,7 @@ "echart", "echarts", "favicons", + "fulltext", "ghostoy", "iconfont", "jdbc", @@ -36,6 +37,7 @@ "ossutil", "pgsql", "pnpm", + "Popconfirm", "remaininguses", "RESTAI", "samuelmeuli", From 5fcce574708aa10c89d2827ad6142bea4c29ca60 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 3 Sep 2023 16:42:29 +0800 Subject: [PATCH 0701/1069] fix: Connection cannot be saved --- .../src/components/ConnectionEdit/index.tsx | 4 +- .../src/pages/main/connection/index.tsx | 12 +- chat2db-client/src/typings/index.ts | 3 +- .../datasource 2/DataSourceCloseParam.java | 26 +++++ .../datasource 2/DataSourceCreateParam.java | 104 +++++++++++++++++ .../DataSourcePageQueryParam.java | 39 +++++++ .../DataSourcePreConnectParam.java | 94 +++++++++++++++ .../datasource 2/DataSourceSelector.java | 23 ++++ .../datasource 2/DataSourceTestParam.java | 96 ++++++++++++++++ .../datasource 2/DataSourceUpdateParam.java | 104 +++++++++++++++++ .../datasource 2/DatabaseOperationParam.java | 22 ++++ .../datasource 2/DatabaseQueryAllParam.java | 41 +++++++ .../DataSourceAccessBatchCreatParam.java | 29 +++++ ...urceAccessComprehensivePageQueryParam.java | 40 +++++++ .../access/DataSourceAccessCreatParam.java | 39 +++++++ .../access/DataSourceAccessObjectParam.java | 41 +++++++ .../DataSourceAccessPageQueryParam.java | 34 ++++++ .../access/DataSourceAccessSelector.java | 34 ++++++ .../api/param/team 2/TeamCreateParam.java | 46 ++++++++ .../api/param/team 2/TeamPageQueryParam.java | 32 ++++++ .../domain/api/param/team 2/TeamSelector.java | 23 ++++ .../api/param/team 2/TeamUpdateParam.java | 42 +++++++ .../TeamUserComprehensivePageQueryParam.java | 40 +++++++ .../param/team 2/user/TeamUserCreatParam.java | 29 +++++ .../team 2/user/TeamUserPageQueryParam.java | 27 +++++ .../param/team 2/user/TeamUserSelector.java | 27 +++++ .../api/param/user 2/UserCreateParam.java | 54 +++++++++ .../api/param/user 2/UserPageQueryParam.java | 38 ++++++ .../domain/api/param/user 2/UserSelector.java | 23 ++++ .../api/param/user 2/UserUpdateParam.java | 54 +++++++++ .../server/start/test/dto 2/TestDTO.java | 22 ++++ .../server/tools/common/enums 2/ModeEnum.java | 34 ++++++ .../request/TeamUserPageQueryRequest.java | 27 +++++ .../controller/common/vo/TeamUserListVO.java | 50 ++++++++ .../DataSourceAccessAdminConverter.java | 50 ++++++++ .../converter/DataSourceAdminConverter.java | 76 ++++++++++++ .../DataSourceAccessBatchCreateRequest.java | 33 ++++++ .../DataSourceAccessObjectRequest.java | 41 +++++++ .../DataSourceAccessPageQueryRequest.java | 26 +++++ .../request/DataSourceCloneRequest.java | 19 +++ .../request/DataSourceCreateRequest.java | 106 +++++++++++++++++ .../request/DataSourceUpdateRequest.java | 108 ++++++++++++++++++ .../vo/DataSourceAccessObjectVO.java | 50 ++++++++ .../vo/DataSourceAccessPageQueryVO.java | 40 +++++++ .../datasource/vo/DataSourcePageQueryVO.java | 39 +++++++ .../datasource/vo/SimpleDataSourceVO.java | 39 +++++++ .../team/converter/TeamAdminConverter.java | 62 ++++++++++ .../TeamDataSourcesAdminConverter.java | 56 +++++++++ .../converter/TeamUserAdminConverter.java | 49 ++++++++ .../team/request/TeamCreateRequest.java | 38 ++++++ .../TeamDataSourceBatchCreateRequest.java | 34 ++++++ .../request/TeamPageCommonQueryRequest.java | 25 ++++ .../team/request/TeamUpdateRequest.java | 45 ++++++++ .../request/TeamUserBatchCreateRequest.java | 33 ++++++ .../api/controller/team/vo/SimpleTeamVO.java | 37 ++++++ .../team/vo/TeamDataSourcePageQueryVO.java | 31 +++++ .../controller/team/vo/TeamPageQueryVO.java | 57 +++++++++ .../team/vo/TeamUserPageQueryVO.java | 28 +++++ .../user/converter/UserAdminConverter.java | 57 +++++++++ .../UserDataSourcesAdminConverter.java | 56 +++++++++ .../converter/UserTeamAdminConverter.java | 38 ++++++ .../user/request/UserCreateRequest.java | 54 +++++++++ .../UserDataSourceBatchCreateRequest.java | 32 ++++++ .../request/UserPageCommonQueryRequest.java | 25 ++++ .../request/UserTeamBatchCreateRequest.java | 32 ++++++ .../user/request/UserUpdateRequest.java | 52 +++++++++ .../api/controller/user/vo/SimpleUserVO.java | 39 +++++++ .../user/vo/UserDataSourcePageQueryVO.java | 31 +++++ .../controller/user/vo/UserPageQueryVO.java | 73 ++++++++++++ .../user/vo/UserTeamPageQueryVO.java | 28 +++++ .../controller/CommonCommonController.java | 42 +++++++ .../converter/EnvironmentCommonConverter.java | 27 +++++ .../request/CommonPageQueryRequest.java | 20 ++++ .../request/CommonQueryRequest.java | 18 +++ .../controller/vo/SimpleEnvironmentVO.java | 48 ++++++++ .../api/controller/vo/SimpleUserVO.java | 41 +++++++ 76 files changed, 3181 insertions(+), 7 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java create mode 100644 chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 230b16fe6..7af68e3d7 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -53,7 +53,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde backfillDataLoading: false, sshTestLoading: false }); - const [envList, setEnvList] = useState<{value:string,label:string}[]>([]); + const [envList, setEnvList] = useState<{ value: string, label: string }[]>([]); useEffect(() => { @@ -200,7 +200,7 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde p.id = backfillData.id; } - if (type === submitType.SAVE || type === submitType.UPDATE) { + if ((type === submitType.SAVE || type === submitType.UPDATE) && submit) { submit?.(p); return } diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 1eefe3ad9..437953ab9 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -5,7 +5,7 @@ import ConnectionEdit from '@/components/ConnectionEdit'; import Iconfont from '@/components/Iconfont'; import connectionService from '@/service/connection'; import { DatabaseTypeCode, databaseMap, databaseTypeList } from '@/constants'; -import { IDatabase, IConnectionDetails } from '@/typings'; +import { IDatabase, IConnectionDetails, IEnvironmentVO } from '@/typings'; import { Button, Dropdown, Modal } from 'antd'; import { MoreOutlined } from '@ant-design/icons'; import styles from './index.less'; @@ -13,6 +13,7 @@ import { connect, history, Dispatch } from 'umi'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; + interface IMenu { key: number; label: string; @@ -20,16 +21,19 @@ interface IMenu { meta: IConnectionDetails; } +// interface IConnectionList { +// environment: 'dev' | 'test' | 'prod'; +// connectionList: IMenu[]; +// } + interface IProps { connectionModel: IConnectionModelType['state']; - workspaceModel: IWorkspaceModelType['state']; dispatch: any; } function Connections(props: IProps) { - const { connectionModel, workspaceModel, dispatch } = props; + const { connectionModel, dispatch } = props; const { connectionList } = connectionModel; - const { curWorkspaceParams } = workspaceModel; const volatileRef = useRef(); const [curConnection, setCurConnection] = useState>({}); diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index d2aad71e6..bc1fe32cb 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -5,4 +5,5 @@ export * from './database'; export * from './main'; export * from './theme'; export * from './tree'; -export * from './setting' \ No newline at end of file +export * from './setting' +export * from './team' \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java new file mode 100644 index 000000000..d5a8904c6 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java @@ -0,0 +1,26 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import jakarta.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * 数据源关闭 + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceCloseParam { + /** + * 对应数据库存储的来源id + */ + @NotNull + private Long dataSourceId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java new file mode 100644 index 000000000..5a8496aae --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java @@ -0,0 +1,104 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import java.util.List; + +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; + +import lombok.Data; + +/** + * @author moji + * @version DataSourceCreateParam.java, v 0.1 2022年09月23日 15:23 moji Exp $ + * @date 2022/09/23 + */ +@Data +public class DataSourceCreateParam { + + /** + * 别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 用户名 + */ + private String userName; + + /** + * 密码 + */ + private String password; + + /** + * 数据库类型 + */ + private String type; + + /** + * 环境类型 + */ + private String envType; + + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; + + /** + * 连接类型 + * + * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum + */ + private String kind; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java new file mode 100644 index 000000000..b2f960757 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java @@ -0,0 +1,39 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.Data; +import lombok.Getter; + +/** + * @author moji + * @version DataSourcePageQueryParam.java, v 0.1 2022年09月23日 15:27 moji Exp $ + * @date 2022/09/23 + */ +@Data +public class DataSourcePageQueryParam extends PageQueryParam { + + /** + * 搜索关键词 + */ + private String searchKey; + + /** + * 连接类型 + * + * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum + */ + private String kind; + + @Getter + public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { + ID_DESC(OrderBy.desc("id")), + ; + + final OrderBy orderBy; + + OrderCondition(OrderBy orderBy) { + this.orderBy = orderBy; + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java new file mode 100644 index 000000000..ee4db569e --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java @@ -0,0 +1,94 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import java.util.List; + +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourcePreConnectParam { + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + @NotNull + private String url; + + /** + * 连接用户 + */ + private String user; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 连接类型 + */ + @NotNull + private String type; + + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java new file mode 100644 index 000000000..00f411f6a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * @author moji + * @version DataSourceSelector.java, v 0.1 2022年09月23日 15:28 moji Exp $ + * @date 2022/09/23 + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceSelector { + + /** + * 环境id + */ + private Boolean environment; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java new file mode 100644 index 000000000..58bd235cb --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java @@ -0,0 +1,96 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import java.util.List; + +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * 数据源测试参数 + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceTestParam { + + /** + * 数据库类型 + * + * @see DbTypeEnum + */ + @NotNull + private String dbType; + + /** + * 请求连接 + */ + @NotNull + private String url; + + /** + * 用户名 + */ + private String username; + + /** + * 密码 + */ + private String password; + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java new file mode 100644 index 000000000..f565df8d8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java @@ -0,0 +1,104 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import java.util.List; + +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version DataSourceCreateParam.java, v 0.1 2022年09月23日 15:23 moji Exp $ + * @date 2022/09/23 + */ +@Data +public class DataSourceUpdateParam { + + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 用户名 + */ + private String userName; + + /** + * 密码 + */ + private String password; + + /** + * 数据库类型 + */ + private String type; + + /** + * 环境类型 + */ + private String envType; + + + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java new file mode 100644 index 000000000..bc3692814 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java @@ -0,0 +1,22 @@ + +package ai.chat2db.server.domain.api.param.datasource; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author jipengfei + * @version : DatabaseOperationParam.java + */ +@Data +@AllArgsConstructor +@Builder +@NoArgsConstructor +public class DatabaseOperationParam { + + private String databaseName; + + private String newDatabaseName; +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java new file mode 100644 index 000000000..2e2b27595 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java @@ -0,0 +1,41 @@ +package ai.chat2db.server.domain.api.param.datasource; + +import java.sql.Connection; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * 展示数据库信息 + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DatabaseQueryAllParam { + /** + * 对应数据库存储的来源id + */ + @NotNull + private Long dataSourceId; + + /** + * if true, refresh the cache + */ + private boolean refresh; + + /** + * Can be null, if null, use the default connection + */ + private Connection connection; + + /** + * Can be null, if null, use the default dbType + */ + private String dbType; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java new file mode 100644 index 000000000..7529557e2 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import java.util.List; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessBatchCreatParam extends PageQueryParam { + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DataSource Access Object + */ + @NotNull + @NotEmpty + private List accessObjectList; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java new file mode 100644 index 000000000..b3c94cae4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java @@ -0,0 +1,40 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.Data; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessComprehensivePageQueryParam extends PageQueryParam { + /** + * 数据源id + */ + private Long dataSourceId; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long accessObjectId; + + /** + * Query keywords for users or teams + */ + private String userOrTeamSearchKey; + + /** + * Query keywords for data source + */ + private String dataSourceSearchKey; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java new file mode 100644 index 000000000..133989782 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java @@ -0,0 +1,39 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessCreatParam { + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java new file mode 100644 index 000000000..20896eb47 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.domain.api.param.datasource.access; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectParam implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java new file mode 100644 index 000000000..d12310ae7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Data Source Access + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryParam extends PageQueryParam { + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java new file mode 100644 index 000000000..65c50c2ba --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.domain.api.param.datasource.access; + +import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * slecetor + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessSelector { + + /** + * 授权对象 + */ + private Boolean accessObject; + + /** + * 数据源 + */ + private Boolean dataSource; + + /** + * 数据源 + */ + private DataSourceSelector dataSourceSelector; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java new file mode 100644 index 000000000..f8a3f39e5 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java @@ -0,0 +1,46 @@ +package ai.chat2db.server.domain.api.param.team; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class TeamCreateParam { + /** + * 团队编码 + */ + @NotNull + private String code; + + /** + * 团队名称 + */ + @NotNull + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + @NotNull + private String status; + + + /** + * 角色编码 + * + * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum + */ + @NotNull + private String roleCode; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java new file mode 100644 index 000000000..59d50572d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java @@ -0,0 +1,32 @@ +package ai.chat2db.server.domain.api.param.team; + +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.Data; +import lombok.Getter; + +/** + * page query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamPageQueryParam extends PageQueryParam { + + /** + * searchKey + */ + private String searchKey; + + @Getter + public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { + ID_DESC(OrderBy.desc("id")), + ; + + final OrderBy orderBy; + + OrderCondition(OrderBy orderBy) { + this.orderBy = orderBy; + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java new file mode 100644 index 000000000..5be026ec1 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.team; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * select + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamSelector { + /** + * 修改人用户 + */ + private Boolean modifiedUser; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java new file mode 100644 index 000000000..047c05a27 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java @@ -0,0 +1,42 @@ +package ai.chat2db.server.domain.api.param.team; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * update + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUpdateParam { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 团队名称 + */ + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; + + /** + * 角色编码 + * + * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum + */ + private String roleCode; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java new file mode 100644 index 000000000..26f47e4e4 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java @@ -0,0 +1,40 @@ +package ai.chat2db.server.domain.api.param.team.user; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.Data; + +/** + * Team User + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserComprehensivePageQueryParam extends PageQueryParam { + + /** + * 团队id + */ + private Long teamId; + + /** + * 用户id + */ + private Long userId; + + /** + * 团队角色code + * + * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum + */ + private String teamRoleCode; + + /** + * Query keywords for team + */ + private String teamSearchKey; + + /** + * Query keywords for user + */ + private String userSearchKey; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java new file mode 100644 index 000000000..619fae389 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.domain.api.param.team.user; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Team User + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserCreatParam { + /** + * team id + */ + @NotNull + private Long teamId; + + /** + * user id + */ + private Long userId; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java new file mode 100644 index 000000000..80cc51160 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.domain.api.param.team.user; + +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Team User + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserPageQueryParam extends PageQueryParam { + + /** + * 团队id + */ + @NotNull + private Long teamId; + + /** + * 用户id + */ + @NotNull + private Long userId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java new file mode 100644 index 000000000..a14e74efb --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.domain.api.param.team.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * select + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserSelector { + /** + * 团队 + */ + private Boolean team; + + /** + * 用户 + */ + private Boolean user; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java new file mode 100644 index 000000000..eb75caea7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.domain.api.param.user; + +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class UserCreateParam { + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + @NotNull + private String roleCode; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java new file mode 100644 index 000000000..b6e1d8b6a --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java @@ -0,0 +1,38 @@ +package ai.chat2db.server.domain.api.param.user; + +import ai.chat2db.server.tools.base.wrapper.param.OrderBy; +import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * * page query + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UserPageQueryParam extends PageQueryParam { + + /** + * searchKey + */ + private String searchKey; + + @Getter + public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { + ID_DESC(OrderBy.desc("id")), + ; + + final OrderBy orderBy; + + OrderCondition(OrderBy orderBy) { + this.orderBy = orderBy; + } + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java new file mode 100644 index 000000000..0f25ce938 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.user; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * select + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UserSelector { + /** + * 修改人用户 + */ + private Boolean modifiedUser; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java new file mode 100644 index 000000000..1b6358cbe --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.domain.api.param.user; + +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class UserUpdateParam { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + private String roleCode; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java new file mode 100644 index 000000000..0646e0b68 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.start.test.dto; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TestDTO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + private String name; +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java new file mode 100644 index 000000000..c2ca67704 --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.tools.common.enums; + +import ai.chat2db.server.tools.base.enums.BaseEnum; +import lombok.Getter; + +/** + * model + * + * @author Jiaju Zhuang + */ +@Getter +public enum ModeEnum implements BaseEnum { + /** + * DESKTOP + */ + DESKTOP("DESKTOP"), + + /** + * WEB + */ + WEB("WEB"), + + ; + final String description; + + ModeEnum(String description) { + this.description = description; + } + + @Override + public String getCode() { + return this.name(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java new file mode 100644 index 000000000..53a5c01af --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java @@ -0,0 +1,27 @@ + +package ai.chat2db.server.admin.api.controller.common.request; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import lombok.Data; + +/** + * Common pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserPageQueryRequest extends PageQueryRequest { + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java new file mode 100644 index 000000000..7b2b55f25 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java @@ -0,0 +1,50 @@ + +package ai.chat2db.server.admin.api.controller.common.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserListVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * The name of the code that belongs to the authorization type, such as user account, team code + */ + private String code; + + /** + * Code that belongs to the authorization type, such as user name, team name + */ + private String name; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java new file mode 100644 index 000000000..f97e2098a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java @@ -0,0 +1,50 @@ +package ai.chat2db.server.admin.api.controller.datasource.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessPageQueryRequest; +import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourceAccessPageQueryVO; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSourceAccess; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) +public abstract class DataSourceAccessAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract DataSourceAccessComprehensivePageQueryParam request2param(DataSourceAccessPageQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract DataSourceAccessPageQueryVO dto2vo(DataSourceAccess dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java new file mode 100644 index 000000000..5236de606 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java @@ -0,0 +1,76 @@ +package ai.chat2db.server.admin.api.controller.datasource.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; +import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; +import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) +public abstract class DataSourceAdminConverter { + + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); + + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract DataSourcePageQueryVO dto2vo(DataSource dto); + + /** + * 参数转换 + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "user", target = "userName"), + @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), + }) + public abstract DataSourceCreateParam createReq2param(DataSourceCreateRequest request); + + /** + * 参数转换 + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "user", target = "userName") + }) + public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java new file mode 100644 index 000000000..14d179409 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java @@ -0,0 +1,33 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessBatchCreateRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DataSource Access Object + */ + @NotNull + private List accessObjectList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java new file mode 100644 index 000000000..e0bdf202e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectRequest implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java new file mode 100644 index 000000000..f833609b6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java @@ -0,0 +1,26 @@ + +package ai.chat2db.server.admin.api.controller.datasource.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Common pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryRequest extends PageQueryRequest { + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java new file mode 100644 index 000000000..54386e61b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + + +import lombok.Data; + +/** + * @author moji + * @version ConnectionCloneRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceCloneRequest { + + /** + * 主键id + */ + private Long id; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java new file mode 100644 index 000000000..b8606a5e3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java @@ -0,0 +1,106 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.util.List; + +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceCreateRequest { + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + @NotNull + private String url; + + /** + * 连接用户名 + */ + private String user; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 认证类型 + */ + private String authenticationType; + + /** + * 连接类型 + */ + @NotNull + private String type; + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + + /** + * jdbc版本 + */ + private String jdbc; + /** + * 扩展信息 + */ + private List extendInfo; + + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; + + /** + * 环境id + */ + @NotNull + private Long environmentId; + + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java new file mode 100644 index 000000000..ae779838c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java @@ -0,0 +1,108 @@ +package ai.chat2db.server.admin.api.controller.datasource.request; + +import java.util.List; + +import ai.chat2db.spi.config.DriverConfig; +import ai.chat2db.spi.model.KeyValue; +import ai.chat2db.spi.model.SSHInfo; +import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * @author moji + * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ + * @date 2022/09/16 + */ +@Data +public class DataSourceUpdateRequest { + + /** + * 主键id + */ + @NotNull + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 连接用户 + */ + private String user; + + /** + * 密码 + */ + private String password; + + /** + * 连接类型 + */ + private String type; + + /** + * 环境类型 + * + * @see EnvTypeEnum + */ + private String envType; + + /** + * host + */ + private String host; + + /** + * port + */ + private String port; + + /** + * ssh + */ + private SSHInfo ssh; + + /** + * ssh + */ + private SSLInfo ssl; + + /** + * sid + */ + private String sid; + + /** + * driver + */ + private String driver; + + /** + * jdbc版本 + */ + private String jdbc; + + /** + * 扩展信息 + */ + private List extendInfo; + + /** + * 驱动配置 + */ + private DriverConfig driverConfig; + + /** + * 环境id + */ + @NotNull + private Long environmentId; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java new file mode 100644 index 000000000..4d1ccb914 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java @@ -0,0 +1,50 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * DataSource Access Object + * It could be a user or a team + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DataSourceAccessObjectVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + private Long id; + + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + private String type; + + /** + * The name of the code that belongs to the authorization type, such as user account, team code + */ + private String code; + + /** + * Code that belongs to the authorization type, such as user name, team name + */ + private String name; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java new file mode 100644 index 000000000..6d2220dc6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java @@ -0,0 +1,40 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourceAccessPageQueryVO { + + /** + * 主键 + */ + @NotNull + private Long id; + /** + * 授权类型 + * + * @see AccessObjectTypeEnum + */ + @NotNull + private String accessObjectType; + + /** + * 授权id,根据类型区分是用户还是团队 + */ + @NotNull + private Long accessObjectId; + + /** + * 授权对象 + */ + @NotNull + private DataSourceAccessObjectVO accessObject; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java new file mode 100644 index 000000000..b77956d66 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java @@ -0,0 +1,39 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class DataSourcePageQueryVO { + + /** + * 主键id + */ + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 环境id + */ + private Long environmentId; + + /** + * 环境 + */ + private SimpleEnvironmentVO environment; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java new file mode 100644 index 000000000..8ae1f9820 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java @@ -0,0 +1,39 @@ + +package ai.chat2db.server.admin.api.controller.datasource.vo; + +import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; +import lombok.Data; + +/** + * Data Source + * + * @author Jiaju Zhuang + */ +@Data +public class SimpleDataSourceVO { + + /** + * 主键id + */ + private Long id; + + /** + * 连接别名 + */ + private String alias; + + /** + * 连接地址 + */ + private String url; + + /** + * 环境id + */ + private Long environmentId; + + /** + * 环境 + */ + private SimpleEnvironmentVO environment; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java new file mode 100644 index 000000000..a6eaf129e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java @@ -0,0 +1,62 @@ +package ai.chat2db.server.admin.api.controller.team.converter; + +import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.Team; +import ai.chat2db.server.domain.api.param.team.TeamCreateParam; +import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; +import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) +public abstract class TeamAdminConverter { + + + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract TeamPageQueryParam request2param(CommonPageQueryRequest request); + + + /** + * conversion + * + * @param dto + * @return + */ + public abstract TeamPageQueryVO dto2vo(Team dto); + + + /** + * conversion + * + * @param request + * @return + */ + public abstract TeamCreateParam request2param(TeamCreateRequest request); + + + /** + * conversion + * + * @param request + * @return + */ + public abstract TeamUpdateParam request2param(TeamUpdateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java new file mode 100644 index 000000000..368041b67 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java @@ -0,0 +1,56 @@ +package ai.chat2db.server.admin.api.controller.team.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSourceAccess; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) +public abstract class TeamDataSourcesAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "accessObjectId", source = "teamId"), + @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.TEAM.name())"), + @Mapping(source = "searchKey", target = "dataSourceSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract DataSourceAccessComprehensivePageQueryParam request2param(TeamPageCommonQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + @Mappings({ + @Mapping(target = "teamId", source = "accessObjectId"), + }) + public abstract TeamDataSourcePageQueryVO dto2vo(DataSourceAccess dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java new file mode 100644 index 000000000..42dc9f4e5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java @@ -0,0 +1,49 @@ +package ai.chat2db.server.admin.api.controller.team.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; +import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; +import ai.chat2db.server.domain.api.model.TeamUser; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class TeamUserAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "userSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract TeamUserComprehensivePageQueryParam request2param(TeamPageCommonQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract TeamUserPageQueryVO dto2vo(TeamUser dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java new file mode 100644 index 000000000..1b3bcf9a2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java @@ -0,0 +1,38 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +public class TeamCreateRequest { + + /** + * 团队编码 + */ + @NotNull + private String code; + + /** + * 团队名称 + */ + @NotNull + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + @NotNull + private String status; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java new file mode 100644 index 000000000..58a139929 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java @@ -0,0 +1,34 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamDataSourceBatchCreateRequest { + + /** + * team id + */ + @NotNull + private Long teamId; + + + /** + * Data Source id list + */ + @NotNull + private List dataSourceIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java new file mode 100644 index 000000000..448628157 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java @@ -0,0 +1,25 @@ + +package ai.chat2db.server.admin.api.controller.team.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamPageCommonQueryRequest extends PageQueryRequest { + /** + * team id + */ + @NotNull + private Long teamId; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java new file mode 100644 index 000000000..623b517d6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java @@ -0,0 +1,45 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * update + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUpdateRequest { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 团队名称 + */ + @NotNull + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + @NotNull + private String status; + + /** + * 角色编码 + * + * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum + */ + @NotNull + private String roleCode; + + /** + * 团队描述 + */ + private String description; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java new file mode 100644 index 000000000..6c983bcf9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java @@ -0,0 +1,33 @@ +package ai.chat2db.server.admin.api.controller.team.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TeamUserBatchCreateRequest { + + /** + * team id + */ + @NotNull + private Long teamId; + + /** + * user id list + */ + @NotNull + private List userIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java new file mode 100644 index 000000000..f6b0bf5f2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java @@ -0,0 +1,37 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import lombok.Data; + +/** + * team + * + * @author Jiaju Zhuang + */ +@Data +public class SimpleTeamVO { + + /** + * 主键 + */ + private Long id; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java new file mode 100644 index 000000000..7caa47de1 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java @@ -0,0 +1,31 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamDataSourcePageQueryVO { + + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * team id + */ + private Long teamId; + + /** + * Data Source + */ + private SimpleDataSourceVO dataSource; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java new file mode 100644 index 000000000..75e2b9a51 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java @@ -0,0 +1,57 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import java.util.Date; + +import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamPageQueryVO { + /** + * 主键 + */ + private Long id; + + /** + * 团队编码 + */ + private String code; + + /** + * 团队名称 + */ + private String name; + + /** + * 团队状态 + * + * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum + */ + private String status; + + /** + * 团队描述 + */ + private String description; + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 修改人用户 + */ + private SimpleUserVO modifiedUser; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java new file mode 100644 index 000000000..a5520a23e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java @@ -0,0 +1,28 @@ + +package ai.chat2db.server.admin.api.controller.team.vo; + +import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class TeamUserPageQueryVO { + /** + * 主键 + */ + private Long id; + + /** + * team id + */ + private Long teamId; + + /** + * user + */ + private SimpleUserVO user; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java new file mode 100644 index 000000000..28bc43b69 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java @@ -0,0 +1,57 @@ +package ai.chat2db.server.admin.api.controller.user.converter; + +import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; +import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; +import ai.chat2db.server.domain.api.model.User; +import ai.chat2db.server.domain.api.param.user.UserCreateParam; +import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; +import ai.chat2db.server.domain.api.param.user.UserUpdateParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class UserAdminConverter { + + /** + * conversion + * + * @param request + * @return + */ + @Mappings({ + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract UserPageQueryParam request2param(CommonPageQueryRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract UserPageQueryVO dto2vo(User dto); + + /** + * conversion + * + * @param request + * @return + */ + public abstract UserCreateParam request2param(UserCreateRequest request); + + /** + * conversion + * + * @param request + * @return + */ + public abstract UserUpdateParam request2param(UserUpdateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java new file mode 100644 index 000000000..1de4822bd --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java @@ -0,0 +1,56 @@ +package ai.chat2db.server.admin.api.controller.user.converter; + +import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; +import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSourceAccess; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; +import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) +public abstract class UserDataSourcesAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "userId", target = "accessObjectId"), + @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.USER.name())"), + @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract DataSourceAccessComprehensivePageQueryParam request2param(UserPageCommonQueryRequest request); + + /** + * convert + * + * @param request + * @return + */ + public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + @Mappings({ + @Mapping(target = "userId", source = "accessObjectId"), + }) + public abstract UserDataSourcePageQueryVO dto2vo(DataSourceAccess dto); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java new file mode 100644 index 000000000..77424f29d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java @@ -0,0 +1,38 @@ +package ai.chat2db.server.admin.api.controller.user.converter; + +import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; +import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; +import ai.chat2db.server.domain.api.model.TeamUser; +import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.Mappings; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Mapper(componentModel = "spring") +public abstract class UserTeamAdminConverter { + + /** + * convert + * + * @param request + * @return + */ + @Mappings({ + @Mapping(source = "searchKey", target = "teamSearchKey"), + @Mapping(target = "enableReturnCount", expression = "java(true)"), + }) + public abstract TeamUserComprehensivePageQueryParam request2param(UserPageCommonQueryRequest request); + + /** + * conversion + * + * @param dto + * @return + */ + public abstract UserTeamPageQueryVO dto2vo(TeamUser dto); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java new file mode 100644 index 000000000..4e2d1cd78 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + *@author Jiaju Zhuang + */ +@Data +public class UserCreateRequest { + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 密码 + */ + @NotNull + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + @NotNull + private String roleCode; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java new file mode 100644 index 000000000..b396bb722 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java @@ -0,0 +1,32 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UserDataSourceBatchCreateRequest { + + /** + * user id + */ + private Long userId; + + /** + * Data Source id list + */ + @NotNull + private List dataSourceIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java new file mode 100644 index 000000000..c0e79a32c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java @@ -0,0 +1,25 @@ + +package ai.chat2db.server.admin.api.controller.user.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserPageCommonQueryRequest extends PageQueryRequest { + /** + * user id + */ + @NotNull + private Long userId; + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java new file mode 100644 index 000000000..831bee3ac --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java @@ -0,0 +1,32 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import java.util.List; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * create + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UserTeamBatchCreateRequest { + + /** + * user id + */ + private Long userId; + + /** + * team id list + */ + @NotNull + private List teamIdList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java new file mode 100644 index 000000000..382826b0b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java @@ -0,0 +1,52 @@ +package ai.chat2db.server.admin.api.controller.user.request; + +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * create + *@author Jiaju Zhuang + */ +@Data +public class UserUpdateRequest { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 密码 + */ + private String password; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 邮箱 + */ + @NotNull + private String email; + + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + private String roleCode; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + @NotNull + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java new file mode 100644 index 000000000..8b8f88071 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java @@ -0,0 +1,39 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * user + * + * @author Jiaju Zhuang + */ +@Data +public class SimpleUserVO { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + private String status; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java new file mode 100644 index 000000000..0281b318e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java @@ -0,0 +1,31 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserDataSourcePageQueryVO { + + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * user id + */ + private Long userId; + + /** + * Data Source + */ + private SimpleDataSourceVO dataSource; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java new file mode 100644 index 000000000..5ff1abaf7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java @@ -0,0 +1,73 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import java.util.Date; + +import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserPageQueryVO { + /** + * 主键 + */ + @NotNull + private Long id; + + /** + * 用户名 + */ + @NotNull + private String userName; + + /** + * 昵称 + */ + @NotNull + private String nickName; + + /** + * 用户状态 + * + * @see ValidStatusEnum + */ + private String status; + + /** + * 邮箱 + */ + @NotNull + private String email; + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + private String roleCode; + + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 修改人用户id + */ + private Long modifiedUserId; + + /** + * 修改人用户 + */ + private SimpleUserVO modifiedUser; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java new file mode 100644 index 000000000..56424c24b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java @@ -0,0 +1,28 @@ + +package ai.chat2db.server.admin.api.controller.user.vo; + +import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; +import lombok.Data; + +/** + * Pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class UserTeamPageQueryVO { + /** + * 主键 + */ + private Long id; + + /** + * user id + */ + private Long userId; + + /** + * 团队 + */ + private SimpleTeamVO team; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java new file mode 100644 index 000000000..327a2aab6 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java @@ -0,0 +1,42 @@ + +package ai.chat2db.server.common.api.controller; + +import ai.chat2db.server.common.api.controller.converter.EnvironmentCommonConverter; +import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; +import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; +import ai.chat2db.server.domain.api.service.EnvironmentService; +import ai.chat2db.server.tools.base.wrapper.result.ListResult; +import jakarta.annotation.Resource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Basic interface + * + * @author Jiaju Zhuang + */ +@RequestMapping("/api/common") +@RestController +public class CommonCommonController { + + @Resource + private EnvironmentService environmentService; + @Resource + private EnvironmentCommonConverter environmentCommonConverter; + + /** + * Query all environments + * + * @return + * @version 2.1.0 + */ + @GetMapping("/environment/list_all") + public ListResult environmentList() { + EnvironmentPageQueryParam environmentPageQueryParam = new EnvironmentPageQueryParam(); + environmentPageQueryParam.setPageSize(Integer.MIN_VALUE); + return ListResult.of( + environmentCommonConverter.dto2vo(environmentService.pageQuery(environmentPageQueryParam).getData())); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java new file mode 100644 index 000000000..74978830f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.common.api.controller.converter; + +import java.util.List; + +import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; +import ai.chat2db.server.domain.api.model.Environment; +import lombok.extern.slf4j.Slf4j; +import org.mapstruct.Mapper; + +/** + * converter + * + * @author Jiaju Zhuang + */ +@Slf4j +@Mapper(componentModel = "spring") +public abstract class EnvironmentCommonConverter { + + + /** + * convert + * + * @param list + * @return + */ + public abstract List dto2vo(List list); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java new file mode 100644 index 000000000..87c58b294 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java @@ -0,0 +1,20 @@ + +package ai.chat2db.server.common.api.controller.request; + +import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; +import lombok.Data; + +/** + * Common pagination query + * + * @author Jiaju Zhuang + */ +@Data +public class CommonPageQueryRequest extends PageQueryRequest { + + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java new file mode 100644 index 000000000..048b6c23e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java @@ -0,0 +1,18 @@ + +package ai.chat2db.server.common.api.controller.request; + +import lombok.Data; + +/** + * Common query + * + * @author Jiaju Zhuang + */ +@Data +public class CommonQueryRequest { + + /** + * searchKey + */ + private String searchKey; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java new file mode 100644 index 000000000..4c9efe9df --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java @@ -0,0 +1,48 @@ +package ai.chat2db.server.common.api.controller.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.domain.api.enums.EnvironmentStyleEnum; +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * Environment + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SimpleEnvironmentVO implements Serializable { + + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + private Long id; + + /** + * 环境名称 + */ + private String name; + + /** + * 环境缩写 + */ + private String shortName; + + /** + * 样式类型 + * + * @see EnvironmentStyleEnum + */ + private String style; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java new file mode 100644 index 000000000..d9c67b84d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java @@ -0,0 +1,41 @@ + +package ai.chat2db.server.common.api.controller.vo; + +import java.io.Serial; +import java.io.Serializable; + +import ai.chat2db.server.tools.base.constant.EasyToolsConstant; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * user + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class +SimpleUserVO implements Serializable { + @Serial + private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; + + /** + * 主键 + */ + private Long id; + + /** + * 用户名 + */ + private String userName; + + /** + * 昵称 + */ + private String nickName; +} \ No newline at end of file From 1b4447bdb662cbbe8aff2c0fb46e9431c6132c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A8=8A=E5=8A=B2=E5=AE=87?= Date: Sun, 3 Sep 2023 16:43:31 +0800 Subject: [PATCH 0702/1069] feat: add icon --- chat2db-client/src/components/Iconfont/index.less | 10 ++++++---- chat2db-client/src/components/Iconfont/index.tsx | 9 +++++---- chat2db-client/src/pages/main/index.tsx | 6 +++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 425e4832b..cf735828a 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,7 +1,9 @@ @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('../../assets/font/iconfont.woff2') format('woff2'), url('../../assets/font/iconfont.woff') format('woff'), - url('../../assets/font/iconfont.ttf') format('truetype'); + font-family: 'iconfont'; + /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff2?t=1693730048111') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff?t=1693730048111') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.ttf?t=1693730048111') format('truetype'); } .iconfont { @@ -12,4 +14,4 @@ -webkit-font-smoothing: antialiased; -webkit-text-stroke-width: 0.2px; -moz-osx-font-smoothing: grayscale; -} +} \ No newline at end of file diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index d33e2e789..8b9a1a66a 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -8,10 +8,11 @@ if (__ENV__ === 'local') { let container = ` /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { - font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_3p2ezsbklq9.woff2?t=1692415581018') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_3p2ezsbklq9.woff?t=1692415581018') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_3p2ezsbklq9.ttf?t=1692415581018') format('truetype'); + font-family: 'iconfont'; + /* Project id 3633546 */ + src: url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff2?t=1693730048111') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff?t=1693730048111') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.ttf?t=1693730048111') format('truetype'); } `; let style = document.createElement('style'); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index f6bc56cf4..60f6eca53 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -75,8 +75,8 @@ function MainPage(props: IProps) { if (res.admin) { navConfig.splice(3, 0, { key: 'team', - icon: '\ue66d', - iconFontSize: 20, + icon: '\ue64b', + iconFontSize: 24, isLoad: false, component: , }); @@ -146,7 +146,7 @@ function MainPage(props: IProps) { placement="bottomRight" arrow={{ pointAtCenter: true }} > - + ); }; From 404064b4dd87abf5c004d6580cff19b4ade0c46d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 3 Sep 2023 17:29:49 +0800 Subject: [PATCH 0703/1069] feat: delete --- .../datasource 2/DataSourceCloseParam.java | 26 ----- .../datasource 2/DataSourceCreateParam.java | 104 ------------------ .../DataSourcePageQueryParam.java | 39 ------- .../DataSourcePreConnectParam.java | 94 ---------------- .../datasource 2/DataSourceSelector.java | 23 ---- .../datasource 2/DataSourceTestParam.java | 96 ---------------- .../datasource 2/DataSourceUpdateParam.java | 104 ------------------ .../datasource 2/DatabaseOperationParam.java | 22 ---- .../datasource 2/DatabaseQueryAllParam.java | 41 ------- .../DataSourceAccessBatchCreatParam.java | 29 ----- ...urceAccessComprehensivePageQueryParam.java | 40 ------- .../access/DataSourceAccessCreatParam.java | 39 ------- .../access/DataSourceAccessObjectParam.java | 41 ------- .../DataSourceAccessPageQueryParam.java | 34 ------ .../access/DataSourceAccessSelector.java | 34 ------ .../api/param/team 2/TeamCreateParam.java | 46 -------- .../api/param/team 2/TeamPageQueryParam.java | 32 ------ .../domain/api/param/team 2/TeamSelector.java | 23 ---- .../api/param/team 2/TeamUpdateParam.java | 42 ------- .../TeamUserComprehensivePageQueryParam.java | 40 ------- .../param/team 2/user/TeamUserCreatParam.java | 29 ----- .../team 2/user/TeamUserPageQueryParam.java | 27 ----- .../param/team 2/user/TeamUserSelector.java | 27 ----- .../api/param/user 2/UserCreateParam.java | 54 --------- .../api/param/user 2/UserPageQueryParam.java | 38 ------- .../domain/api/param/user 2/UserSelector.java | 23 ---- .../api/param/user 2/UserUpdateParam.java | 54 --------- .../server/tools/common/enums 2/ModeEnum.java | 34 ------ 28 files changed, 1235 deletions(-) delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java delete mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java deleted file mode 100644 index d5a8904c6..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCloseParam.java +++ /dev/null @@ -1,26 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import jakarta.validation.constraints.NotNull; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 数据源关闭 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceCloseParam { - /** - * 对应数据库存储的来源id - */ - @NotNull - private Long dataSourceId; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java deleted file mode 100644 index 5a8496aae..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceCreateParam.java +++ /dev/null @@ -1,104 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import java.util.List; - -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; - -import lombok.Data; - -/** - * @author moji - * @version DataSourceCreateParam.java, v 0.1 2022年09月23日 15:23 moji Exp $ - * @date 2022/09/23 - */ -@Data -public class DataSourceCreateParam { - - /** - * 别名 - */ - private String alias; - - /** - * 连接地址 - */ - private String url; - - /** - * 用户名 - */ - private String userName; - - /** - * 密码 - */ - private String password; - - /** - * 数据库类型 - */ - private String type; - - /** - * 环境类型 - */ - private String envType; - - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; - - /** - * 连接类型 - * - * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum - */ - private String kind; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java deleted file mode 100644 index b2f960757..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePageQueryParam.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import ai.chat2db.server.tools.base.wrapper.param.OrderBy; -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import lombok.Data; -import lombok.Getter; - -/** - * @author moji - * @version DataSourcePageQueryParam.java, v 0.1 2022年09月23日 15:27 moji Exp $ - * @date 2022/09/23 - */ -@Data -public class DataSourcePageQueryParam extends PageQueryParam { - - /** - * 搜索关键词 - */ - private String searchKey; - - /** - * 连接类型 - * - * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum - */ - private String kind; - - @Getter - public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { - ID_DESC(OrderBy.desc("id")), - ; - - final OrderBy orderBy; - - OrderCondition(OrderBy orderBy) { - this.orderBy = orderBy; - } - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java deleted file mode 100644 index ee4db569e..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourcePreConnectParam.java +++ /dev/null @@ -1,94 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import java.util.List; - -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * @author moji - * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ - * @date 2022/09/16 - */ -@Data -public class DataSourcePreConnectParam { - - /** - * 连接别名 - */ - private String alias; - - /** - * 连接地址 - */ - @NotNull - private String url; - - /** - * 连接用户 - */ - private String user; - - /** - * 密码 - */ - @NotNull - private String password; - - /** - * 连接类型 - */ - @NotNull - private String type; - - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java deleted file mode 100644 index 00f411f6a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceSelector.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * @author moji - * @version DataSourceSelector.java, v 0.1 2022年09月23日 15:28 moji Exp $ - * @date 2022/09/23 - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceSelector { - - /** - * 环境id - */ - private Boolean environment; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java deleted file mode 100644 index 58bd235cb..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceTestParam.java +++ /dev/null @@ -1,96 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import java.util.List; - -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 数据源测试参数 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceTestParam { - - /** - * 数据库类型 - * - * @see DbTypeEnum - */ - @NotNull - private String dbType; - - /** - * 请求连接 - */ - @NotNull - private String url; - - /** - * 用户名 - */ - private String username; - - /** - * 密码 - */ - private String password; - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java deleted file mode 100644 index f565df8d8..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DataSourceUpdateParam.java +++ /dev/null @@ -1,104 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import java.util.List; - -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * @author moji - * @version DataSourceCreateParam.java, v 0.1 2022年09月23日 15:23 moji Exp $ - * @date 2022/09/23 - */ -@Data -public class DataSourceUpdateParam { - - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 别名 - */ - private String alias; - - /** - * 连接地址 - */ - private String url; - - /** - * 用户名 - */ - private String userName; - - /** - * 密码 - */ - private String password; - - /** - * 数据库类型 - */ - private String type; - - /** - * 环境类型 - */ - private String envType; - - - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java deleted file mode 100644 index bc3692814..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseOperationParam.java +++ /dev/null @@ -1,22 +0,0 @@ - -package ai.chat2db.server.domain.api.param.datasource; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * @author jipengfei - * @version : DatabaseOperationParam.java - */ -@Data -@AllArgsConstructor -@Builder -@NoArgsConstructor -public class DatabaseOperationParam { - - private String databaseName; - - private String newDatabaseName; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java deleted file mode 100644 index 2e2b27595..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/DatabaseQueryAllParam.java +++ /dev/null @@ -1,41 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource; - -import java.sql.Connection; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * 展示数据库信息 - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DatabaseQueryAllParam { - /** - * 对应数据库存储的来源id - */ - @NotNull - private Long dataSourceId; - - /** - * if true, refresh the cache - */ - private boolean refresh; - - /** - * Can be null, if null, use the default connection - */ - private Connection connection; - - /** - * Can be null, if null, use the default dbType - */ - private String dbType; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java deleted file mode 100644 index 7529557e2..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessBatchCreatParam.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource.access; - -import java.util.List; - -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Data Source Access - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessBatchCreatParam extends PageQueryParam { - /** - * 数据源id - */ - @NotNull - private Long dataSourceId; - - /** - * DataSource Access Object - */ - @NotNull - @NotEmpty - private List accessObjectList; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java deleted file mode 100644 index b3c94cae4..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessComprehensivePageQueryParam.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource.access; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import lombok.Data; - -/** - * Data Source Access - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessComprehensivePageQueryParam extends PageQueryParam { - /** - * 数据源id - */ - private Long dataSourceId; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String accessObjectType; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long accessObjectId; - - /** - * Query keywords for users or teams - */ - private String userOrTeamSearchKey; - - /** - * Query keywords for data source - */ - private String dataSourceSearchKey; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java deleted file mode 100644 index 133989782..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessCreatParam.java +++ /dev/null @@ -1,39 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource.access; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * Data Source Access - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessCreatParam { - /** - * 数据源id - */ - @NotNull - private Long dataSourceId; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - @NotNull - private String accessObjectType; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - @NotNull - private Long accessObjectId; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java deleted file mode 100644 index 20896eb47..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessObjectParam.java +++ /dev/null @@ -1,41 +0,0 @@ - -package ai.chat2db.server.domain.api.param.datasource.access; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * DataSource Access Object - * It could be a user or a team - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessObjectParam implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long id; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java deleted file mode 100644 index d12310ae7..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessPageQueryParam.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource.access; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Data Source Access - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessPageQueryParam extends PageQueryParam { - /** - * 数据源id - */ - @NotNull - private Long dataSourceId; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - @NotNull - private String accessObjectType; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - @NotNull - private Long accessObjectId; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java deleted file mode 100644 index 65c50c2ba..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource 2/access/DataSourceAccessSelector.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.chat2db.server.domain.api.param.datasource.access; - -import ai.chat2db.server.domain.api.param.datasource.DataSourceSelector; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * slecetor - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessSelector { - - /** - * 授权对象 - */ - private Boolean accessObject; - - /** - * 数据源 - */ - private Boolean dataSource; - - /** - * 数据源 - */ - private DataSourceSelector dataSourceSelector; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java deleted file mode 100644 index f8a3f39e5..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamCreateParam.java +++ /dev/null @@ -1,46 +0,0 @@ -package ai.chat2db.server.domain.api.param.team; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -public class TeamCreateParam { - /** - * 团队编码 - */ - @NotNull - private String code; - - /** - * 团队名称 - */ - @NotNull - private String name; - - /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum - */ - @NotNull - private String status; - - - /** - * 角色编码 - * - * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum - */ - @NotNull - private String roleCode; - - /** - * 团队描述 - */ - private String description; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java deleted file mode 100644 index 59d50572d..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamPageQueryParam.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.chat2db.server.domain.api.param.team; - -import ai.chat2db.server.tools.base.wrapper.param.OrderBy; -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import lombok.Data; -import lombok.Getter; - -/** - * page query - * - * @author Jiaju Zhuang - */ -@Data -public class TeamPageQueryParam extends PageQueryParam { - - /** - * searchKey - */ - private String searchKey; - - @Getter - public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { - ID_DESC(OrderBy.desc("id")), - ; - - final OrderBy orderBy; - - OrderCondition(OrderBy orderBy) { - this.orderBy = orderBy; - } - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java deleted file mode 100644 index 5be026ec1..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamSelector.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.server.domain.api.param.team; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * select - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TeamSelector { - /** - * 修改人用户 - */ - private Boolean modifiedUser; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java deleted file mode 100644 index 047c05a27..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/TeamUpdateParam.java +++ /dev/null @@ -1,42 +0,0 @@ -package ai.chat2db.server.domain.api.param.team; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * update - * - * @author Jiaju Zhuang - */ -@Data -public class TeamUpdateParam { - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 团队名称 - */ - private String name; - - /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum - */ - private String status; - - /** - * 角色编码 - * - * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum - */ - private String roleCode; - - /** - * 团队描述 - */ - private String description; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java deleted file mode 100644 index 26f47e4e4..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserComprehensivePageQueryParam.java +++ /dev/null @@ -1,40 +0,0 @@ -package ai.chat2db.server.domain.api.param.team.user; - -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import lombok.Data; - -/** - * Team User - * - * @author Jiaju Zhuang - */ -@Data -public class TeamUserComprehensivePageQueryParam extends PageQueryParam { - - /** - * 团队id - */ - private Long teamId; - - /** - * 用户id - */ - private Long userId; - - /** - * 团队角色code - * - * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum - */ - private String teamRoleCode; - - /** - * Query keywords for team - */ - private String teamSearchKey; - - /** - * Query keywords for user - */ - private String userSearchKey; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java deleted file mode 100644 index 619fae389..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserCreatParam.java +++ /dev/null @@ -1,29 +0,0 @@ -package ai.chat2db.server.domain.api.param.team.user; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * Team User - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TeamUserCreatParam { - /** - * team id - */ - @NotNull - private Long teamId; - - /** - * user id - */ - private Long userId; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java deleted file mode 100644 index 80cc51160..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserPageQueryParam.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.chat2db.server.domain.api.param.team.user; - -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Team User - * - * @author Jiaju Zhuang - */ -@Data -public class TeamUserPageQueryParam extends PageQueryParam { - - /** - * 团队id - */ - @NotNull - private Long teamId; - - /** - * 用户id - */ - @NotNull - private Long userId; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java deleted file mode 100644 index a14e74efb..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/team 2/user/TeamUserSelector.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.chat2db.server.domain.api.param.team.user; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * select - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TeamUserSelector { - /** - * 团队 - */ - private Boolean team; - - /** - * 用户 - */ - private Boolean user; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java deleted file mode 100644 index eb75caea7..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserCreateParam.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.chat2db.server.domain.api.param.user; - -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; -import ai.chat2db.server.domain.api.enums.ValidStatusEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -public class UserCreateParam { - /** - * 用户名 - */ - @NotNull - private String userName; - - /** - * 密码 - */ - @NotNull - private String password; - - /** - * 昵称 - */ - @NotNull - private String nickName; - - /** - * 邮箱 - */ - @NotNull - private String email; - - /** - * 角色编码 - * - * @see RoleCodeEnum - */ - @NotNull - private String roleCode; - - /** - * 用户状态 - * - * @see ValidStatusEnum - */ - @NotNull - private String status; -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java deleted file mode 100644 index b6e1d8b6a..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserPageQueryParam.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.chat2db.server.domain.api.param.user; - -import ai.chat2db.server.tools.base.wrapper.param.OrderBy; -import ai.chat2db.server.tools.base.wrapper.param.PageQueryParam; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * * page query - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class UserPageQueryParam extends PageQueryParam { - - /** - * searchKey - */ - private String searchKey; - - @Getter - public enum OrderCondition implements ai.chat2db.server.tools.base.wrapper.param.OrderCondition { - ID_DESC(OrderBy.desc("id")), - ; - - final OrderBy orderBy; - - OrderCondition(OrderBy orderBy) { - this.orderBy = orderBy; - } - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java deleted file mode 100644 index 0f25ce938..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserSelector.java +++ /dev/null @@ -1,23 +0,0 @@ -package ai.chat2db.server.domain.api.param.user; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * select - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class UserSelector { - /** - * 修改人用户 - */ - private Boolean modifiedUser; - -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java deleted file mode 100644 index 1b6358cbe..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/user 2/UserUpdateParam.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.chat2db.server.domain.api.param.user; - -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; -import ai.chat2db.server.domain.api.enums.ValidStatusEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -public class UserUpdateParam { - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 密码 - */ - @NotNull - private String password; - - /** - * 昵称 - */ - @NotNull - private String nickName; - - /** - * 邮箱 - */ - @NotNull - private String email; - - - /** - * 角色编码 - * - * @see RoleCodeEnum - */ - private String roleCode; - - /** - * 用户状态 - * - * @see ValidStatusEnum - */ - @NotNull - private String status; -} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java deleted file mode 100644 index c2ca67704..000000000 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/enums 2/ModeEnum.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.chat2db.server.tools.common.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; -import lombok.Getter; - -/** - * model - * - * @author Jiaju Zhuang - */ -@Getter -public enum ModeEnum implements BaseEnum { - /** - * DESKTOP - */ - DESKTOP("DESKTOP"), - - /** - * WEB - */ - WEB("WEB"), - - ; - final String description; - - ModeEnum(String description) { - this.description = description; - } - - @Override - public String getCode() { - return this.name(); - } -} From 46b96dfb9de2b8bf16e9f9a3d620ebf67a75e56a Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 3 Sep 2023 17:41:14 +0800 Subject: [PATCH 0704/1069] Error modifying environment --- .../param/datasource/DataSourceCreateParam.java | 7 +++++++ .../domain/api/service/DataSourceService.java | 2 +- .../domain/core/impl/DataSourceServiceImpl.java | 14 +++++++++----- .../data/source/DataSourceController.java | 12 ++++++------ .../controller/data/source/vo/DataSourceVO.java | 5 +++++ 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceCreateParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceCreateParam.java index 5a8496aae..c86e357f8 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceCreateParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DataSourceCreateParam.java @@ -7,6 +7,7 @@ import ai.chat2db.spi.model.SSHInfo; import ai.chat2db.spi.model.SSLInfo; +import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -101,4 +102,10 @@ public class DataSourceCreateParam { * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum */ private String kind; + + /** + * 环境id + */ + @NotNull + private Long environmentId; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java index ca7c23de6..e9f706315 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceService.java @@ -64,7 +64,7 @@ public interface DataSourceService { * @return * @throws ai.chat2db.server.tools.common.exception.DataNotFoundException */ - DataResult queryExistent(@NotNull Long id); + DataResult queryExistent(@NotNull Long id, DataSourceSelector selector); /** * 克隆连接 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 953deb104..0f275156e 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 @@ -45,6 +45,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.google.common.collect.Lists; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; @@ -117,7 +118,7 @@ private void preWarmingData(Long dataSourceId) { @Override public DataResult updateWithPermission(DataSourceUpdateParam param) { - DataSource dataSource = queryExistent(param.getId()).getData(); + DataSource dataSource = queryExistent(param.getId(), null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = dataSourceConverter.param2do(param); @@ -129,7 +130,7 @@ public DataResult updateWithPermission(DataSourceUpdateParam param) { @Override public ActionResult deleteWithPermission(Long id) { - DataSource dataSource = queryExistent(id).getData(); + DataSource dataSource = queryExistent(id, null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); dataSourceMapper.deleteById(id); @@ -143,17 +144,20 @@ public DataResult queryById(Long id) { } @Override - public DataResult queryExistent(Long id) { + public DataResult queryExistent(Long id, DataSourceSelector selector) { DataResult dataResult = queryById(id); if (dataResult.getData() == null) { throw new DataNotFoundException(); } + + fillData(Lists.newArrayList(dataResult.getData()), selector); + return dataResult; } @Override public DataResult copyByIdWithPermission(Long id) { - DataSource dataSource = queryExistent(id).getData(); + DataSource dataSource = queryExistent(id, null).getData(); PermissionUtils.checkOperationPermission(dataSource.getUserId()); DataSourceDO dataSourceDO = dataSourceMapper.selectById(id); @@ -191,7 +195,7 @@ public PageResult queryPageWithPermission(DataSourcePageQueryParam p IPage iPage = dataSourceCustomMapper.selectPageWithPermission( new Page<>(param.getPageNo(), param.getPageSize()), - BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey(),param.getKind(), + BooleanUtils.isTrue(loginUser.getAdmin()), loginUser.getId(), param.getSearchKey(), param.getKind(), EasySqlUtils.orderBy(param.getOrderByList())); List dataSources = dataSourceConverter.do2dto(iPage.getRecords()); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java index 4453994c8..37c8e4c9d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/DataSourceController.java @@ -63,7 +63,7 @@ @Slf4j public class DataSourceController { - private static final DataSourceSelector DATA_SOURCE_SELECTOR= DataSourceSelector.builder() + private static final DataSourceSelector DATA_SOURCE_SELECTOR = DataSourceSelector.builder() .environment(Boolean.TRUE) .build(); @@ -104,7 +104,7 @@ public ActionResult sshConnect(@RequestBody SSHTestRequest request) { session = SSHManager.getSSHSession(sshWebConverter.toInfo(request)); } catch (Exception e) { log.error("sshConnect error", e); - throw new ConnectionException("connection.ssh.error",null,e); + throw new ConnectionException("connection.ssh.error", null, e); } finally { if (session != null) { session.disconnect(); @@ -184,11 +184,11 @@ public WebPageResult list(DataSourceQueryRequest request) { */ @GetMapping("/datasource/{id}") public DataResult queryById(@PathVariable("id") Long id) { - DataResult dataResult = dataSourceService.queryById(id); + DataResult dataResult = dataSourceService.queryExistent(id, DATA_SOURCE_SELECTOR); DataSourceVO dataSourceVO = dataSourceWebConverter.dto2vo(dataResult.getData()); - if(StringUtils.isNotBlank(dataSourceVO.getUser())){ + if (StringUtils.isNotBlank(dataSourceVO.getUser())) { dataSourceVO.setAuthenticationType("1"); - }else { + } else { dataSourceVO.setAuthenticationType("2"); } return DataResult.of(dataSourceVO); @@ -212,7 +212,7 @@ public DataResult create(@RequestBody DataSourceCreateRequest request) { * @param request * @return */ - @RequestMapping(value = "/datasource/update",method = {RequestMethod.POST, RequestMethod.PUT}) + @RequestMapping(value = "/datasource/update", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult update(@RequestBody DataSourceUpdateRequest request) { DataSourceUpdateParam param = dataSourceWebConverter.updateReq2param(request); return dataSourceService.updateWithPermission(param); 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 e70822017..e3ae01800 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 @@ -101,6 +101,11 @@ public class DataSourceVO { */ private DriverConfig driverConfig; + /** + * 环境id + */ + private Long environmentId; + /** * 环境 */ From 1d3a42d5da14f94d21da09542505bf105ca77ba5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 4 Sep 2023 16:48:20 +0800 Subject: [PATCH 0705/1069] feat: Optimize team page --- .../main/team/datasource-management/index.tsx | 2 +- .../pages/main/team/team-management/index.tsx | 2 +- .../main/team/universal-drawer/index.tsx | 160 +++++++++++------- 3 files changed, 103 insertions(+), 61 deletions(-) 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 dec117284..815b461f5 100644 --- a/chat2db-client/src/pages/main/team/datasource-management/index.tsx +++ b/chat2db-client/src/pages/main/team/datasource-management/index.tsx @@ -159,7 +159,7 @@ function DataSourceManagement() { enterButton={} />
} />
>([]); - const [isModalVisible, setIsModalVisible] = useState(false); const [modalInfo, setModalInfo] = useState<{ open: boolean; type?: SearchType }>({ open: false, }); - const [searchInput, setSearchInput] = useState('') + const [searchInput, setSearchInput] = useState(''); const [pagination, setPagination] = useState({ searchKey: '', @@ -60,12 +87,12 @@ function UniversalDrawer(props: IProps) { { title: '团队编码', dataIndex: ['team', 'code'], - key: 'team.code' + key: 'team.code', }, { title: '团队名称', dataIndex: ['team', 'name'], - key: 'team.name' + key: 'team.name', }, { title: '操作', @@ -79,17 +106,18 @@ function UniversalDrawer(props: IProps) { onConfirm={async () => { if (record.id !== undefined) { await deleteTeamListFromUser({ id: record.id }); - message.success(i18n('common.text.successfullyDelete')) + message.success(i18n('common.text.successfullyDelete')); queryTableList(); } - }}> - e.preventDefault()}> + }} + > + e.preventDefault()}> {i18n('common.button.delete')} - ) - } - ] + ), + }, + ], }, [AffiliationType.USER_DATASOURCE]: { type: AffiliationType.USER_DATASOURCE, @@ -103,12 +131,12 @@ function UniversalDrawer(props: IProps) { { title: '链接名称', dataIndex: ['dataSource', 'alias'], - key: 'dataSource.alias' + key: 'dataSource.alias', }, { title: '链接地址', dataIndex: ['dataSource', 'url'], - key: 'dataSource.url' + key: 'dataSource.url', }, { title: '操作', @@ -122,17 +150,18 @@ function UniversalDrawer(props: IProps) { onConfirm={async () => { if (record.id !== undefined) { await deleteDataSourceFromUser({ id: record.id }); - message.success(i18n('common.text.successfullyDelete')) + message.success(i18n('common.text.successfullyDelete')); queryTableList(); } - }}> - e.preventDefault()}> + }} + > + e.preventDefault()}> {i18n('common.button.delete')} - ) - } - ] + ), + }, + ], }, [AffiliationType.TEAM_USER]: { type: AffiliationType.TEAM_USER, @@ -146,12 +175,12 @@ function UniversalDrawer(props: IProps) { { title: '用户名', dataIndex: ['user', 'userName'], - key: 'user.userName' + key: 'user.userName', }, { title: '昵称', dataIndex: ['user', 'nickName'], - key: 'user.nickName' + key: 'user.nickName', }, { title: '操作', @@ -165,17 +194,18 @@ function UniversalDrawer(props: IProps) { onConfirm={async () => { if (record.id !== undefined) { await deleteUserFromTeam({ id: record.id }); - message.success(i18n('common.text.successfullyDelete')) + message.success(i18n('common.text.successfullyDelete')); queryTableList(); } - }}> - e.preventDefault()}> + }} + > + e.preventDefault()}> {i18n('common.button.delete')} - ) - } - ] + ), + }, + ], }, [AffiliationType.TEAM_DATASOURCE]: { type: AffiliationType.TEAM_DATASOURCE, @@ -189,12 +219,12 @@ function UniversalDrawer(props: IProps) { { title: '链接名称', dataIndex: ['dataSource', 'alias'], - key: 'dataSource.alias' + key: 'dataSource.alias', }, { title: '链接地址', dataIndex: ['dataSource', 'url'], - key: 'dataSource.url' + key: 'dataSource.url', }, { title: '操作', @@ -208,17 +238,18 @@ function UniversalDrawer(props: IProps) { onConfirm={async () => { if (record.id !== undefined) { await deleteDataSourceFromUser({ id: record.id }); - message.success(i18n('common.text.successfullyDelete')) + message.success(i18n('common.text.successfullyDelete')); queryTableList(); } - }}> - e.preventDefault()}> + }} + > + e.preventDefault()}> {i18n('common.button.delete')} - ) - } - ] + ), + }, + ], }, [AffiliationType['DATASOURCE_USER/TEAM']]: { type: AffiliationType['DATASOURCE_USER/TEAM'], @@ -232,18 +263,20 @@ function UniversalDrawer(props: IProps) { { title: '编码', dataIndex: ['accessObject', 'code'], - key: 'accessObject.code' + key: 'accessObject.code', }, { title: '名称', dataIndex: ['accessObject', 'name'], - key: 'accessObject.name' + key: 'accessObject.name', }, { title: '类型', dataIndex: ['accessObject', 'type'], key: 'accessObject.type', - render: (status: ManagementType) => {status} + render: (status: ManagementType) => ( + {status} + ), }, { title: '操作', @@ -257,25 +290,29 @@ function UniversalDrawer(props: IProps) { onConfirm={async () => { if (record.id !== undefined) { await deleteUserOrTeamFromDataSource({ id: record.id }); - message.success(i18n('common.text.successfullyDelete')) + message.success(i18n('common.text.successfullyDelete')); queryTableList(); } - }}> - e.preventDefault()}> + }} + > + e.preventDefault()}> {i18n('common.button.delete')} - ) - } - ] - } + ), + }, + ], + }, }), [props.byId, type], ); - const managementDataByType = type ? managementMap[type] : null + const managementDataByType = type ? managementMap[type] : null; useEffect(() => { + if (!open) { + return; + } setSearchInput(''); setPagination({ searchKey: '', @@ -289,19 +326,24 @@ function UniversalDrawer(props: IProps) { open: false, type: managementDataByType?.searchType, }); - }, [props.byId]); + }, [props.byId, type, open]); useEffect(() => { queryTableList(); - }, [pagination.current, pagination.pageSize, pagination.searchKey, props.byId, type]); + }, [pagination]); - const queryTableList = async () => { - const { searchKey, current: pageNo, pageSize } = pagination; + const queryTableList = async (searchKey?: string) => { + const { current: pageNo, pageSize } = pagination; const requestApi = managementDataByType?.queryListApi; if (!requestApi || !isNumber(props.byId)) { return; } - let res = await requestApi({ searchKey, pageNo, pageSize, [managementDataByType?.byIdKey]: props.byId }); + let res = await requestApi({ + searchKey: searchKey || pagination.searchKey, + pageNo, + pageSize, + [managementDataByType?.byIdKey]: props.byId, + }); if (res) { setDataSource(res?.data ?? []); } @@ -310,8 +352,8 @@ function UniversalDrawer(props: IProps) { const handleSearch = (searchKey: string) => { setPagination({ ...pagination, - searchKey - }) + searchKey, + }); }; if (!managementDataByType) { @@ -336,7 +378,7 @@ function UniversalDrawer(props: IProps) { setModalInfo({ ...modalInfo, open: true, - type: managementDataByType.searchType + type: managementDataByType.searchType, }); }} > @@ -348,10 +390,10 @@ function UniversalDrawer(props: IProps) { { - managementDataByType.updateListApi({ [managementDataByType.byIdKey]: props.byId, ...values }).then(res => { - message.success('更新成功') - queryTableList() - }) + managementDataByType.updateListApi({ [managementDataByType.byIdKey]: props.byId, ...values }).then((res) => { + message.success('更新成功'); + queryTableList(); + }); }} onClose={() => { setModalInfo({ From f7e35d8b8f9f92a78be252bbccf127bd1ed7ecab Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 4 Sep 2023 17:15:50 +0800 Subject: [PATCH 0706/1069] feat: Add i18n --- chat2db-client/src/i18n/en-us/common.ts | 3 +- chat2db-client/src/i18n/en-us/team.ts | 26 ++++-- chat2db-client/src/i18n/zh-cn/common.ts | 3 +- chat2db-client/src/i18n/zh-cn/team.ts | 85 +++++++++++-------- .../main/team/universal-add-modal/index.tsx | 43 ++++++---- .../main/team/universal-drawer/index.tsx | 48 +++++------ 6 files changed, 123 insertions(+), 85 deletions(-) diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index fd9476798..e22b8b130 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -79,5 +79,6 @@ export default { 'common.tips.delete.confirm': 'Are you sure to delete it?', 'common.tips.updateSuccess': 'Update Successfully', 'common.tips.createSuccess': 'Create Successfully', - 'common.text.action': 'Action' + 'common.text.action': 'Action', + 'common.button.add': 'Add', }; diff --git a/chat2db-client/src/i18n/en-us/team.ts b/chat2db-client/src/i18n/en-us/team.ts index d0e7906d6..af0a6ca10 100644 --- a/chat2db-client/src/i18n/en-us/team.ts +++ b/chat2db-client/src/i18n/en-us/team.ts @@ -2,22 +2,35 @@ export default { 'team.tab.datasource': 'DataSource Management', 'team.tab.user': 'User Management', 'team.tab.team': 'Team Management', - 'team.datasource.alias': 'DataSource Name', - 'team.datasource.url': 'DataSource URL', - 'team.user.userName': 'UserName', - 'team.user.nickName': 'NickName', - 'team.user.status': 'Status', + + 'team.action.rightManagement': 'Right Management', 'team.action.editDatasource': 'Edit DataSource', 'team.action.addDatasource': 'Add DataSource', + 'team.action.addDatasource.placeholder': 'Search DataSource', 'team.action.addUser': 'Add User', + 'team.action.addUser.placeholder': 'Search User', 'team.action.editTeam': 'Edit Team', 'team.action.addTeam': 'Add Team', + 'team.action.addTeam.placeholder': 'Search Team', 'team.action.affiliation.user': 'Affiliation User', 'team.action.affiliation.team': 'Affiliation Team', 'team.action.affiliation.datasource': 'Affiliation DataSource', + 'team.action.addUserAndTeam': 'Add User/Team', + 'team.action.addUserAndTeam.placeholder': 'Search User/Team', 'team.input.search.placeholder': 'Please enter keywords to search', + 'team.datasource.rightManagement': 'Right Management', + 'team.datasource.alias': 'DataSource Name', + 'team.datasource.url': 'DataSource URL', + 'team.datasource.code': 'Code', + 'team.datasource.name': 'Name', + 'team.datasource.status': 'Status', + + 'team.user.name': 'User', + 'team.user.userName': 'UserName', + 'team.user.nickName': 'NickName', + 'team.user.status': 'Status', 'team.user.addForm.userName': 'UserName', 'team.user.addForm.nickName': 'NickName', 'team.user.addForm.email': 'Email', @@ -30,10 +43,13 @@ export default { 'team.user.addForm.status.invalid': 'Invalid', + 'team.team.name': 'Team', 'team.team.addForm.code': 'Team Code', 'team.team.addForm.name': 'Team Name', 'team.team.addForm.status': 'Status', 'team.team.addForm.status.valid': 'Valid', 'team.team.addForm.status.invalid': 'Invalid', 'team.team.addForm.description': 'Description', + + } \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 4b1956b2c..f48e502e9 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -77,5 +77,6 @@ export default { 'common.tips.delete.confirm': '确认要删除吗?', 'common.tips.updateSuccess': '更新成功', 'common.tips.createSuccess': '创建成功', - 'common.text.action': '操作' + 'common.text.action': '操作', + 'common.button.add': '添加', }; diff --git a/chat2db-client/src/i18n/zh-cn/team.ts b/chat2db-client/src/i18n/zh-cn/team.ts index e15f1b41e..62e423bf3 100644 --- a/chat2db-client/src/i18n/zh-cn/team.ts +++ b/chat2db-client/src/i18n/zh-cn/team.ts @@ -1,39 +1,52 @@ export default { - 'team.tab.datasource': '链接管理', - 'team.tab.user': '用户管理', - 'team.tab.team': '团队管理', - 'team.datasource.alias': '链接名称', - 'team.datasource.url': '链接地址', - 'team.user.userName': '用户名', - 'team.user.nickName': '昵称', - 'team.user.status': '状态', - 'team.action.rightManagement': '权限管理', - 'team.action.editDatasource': '编辑链接', - 'team.action.addDatasource': '添加链接', - 'team.action.editUser': '编辑用户', - 'team.action.addUser': '添加用户', - 'team.action.editTeam': '编辑团队', - 'team.action.addTeam': '添加团队', - 'team.action.affiliation.user': '包含用户', - 'team.action.affiliation.team': '所属团队', - 'team.action.affiliation.datasource': '归属链接', - 'team.input.search.placeholder': '输入关键字进行搜索', + 'team.tab.datasource': '链接管理', + 'team.tab.user': '用户管理', + 'team.tab.team': '团队管理', - 'team.user.addForm.userName': '用户名', - 'team.user.addForm.nickName': '昵称', - 'team.user.addForm.email': '邮箱', - 'team.user.addForm.password': '密码', - 'team.user.addForm.roleCode': '角色', - 'team.user.addForm.roleCode.admin': '管理员', - 'team.user.addForm.roleCode.user': '用户', - 'team.user.addForm.status': '状态', - 'team.user.addForm.status.valid': '有效', - 'team.user.addForm.status.invalid': '无效', + 'team.action.rightManagement': '权限管理', + 'team.action.editDatasource': '编辑链接', + 'team.action.addDatasource': '添加链接', + 'team.action.addDatasource.placeholder': '搜索链接', + 'team.action.editUser': '编辑用户', + 'team.action.addUser': '添加用户', + 'team.action.addUser.placeholder': '搜索用户', + 'team.action.editTeam': '编辑团队', + 'team.action.addTeam': '添加团队', + 'team.action.addTeam.placeholder': '搜索团队', + 'team.action.affiliation.user': '包含用户', + 'team.action.affiliation.team': '所属团队', + 'team.action.affiliation.datasource': '归属链接', + 'team.action.addUserAndTeam': '添加用户/团队', + 'team.action.addUserAndTeam.placeholder': '搜索人员/团队', + 'team.input.search.placeholder': '输入关键字进行搜索', - 'team.team.addForm.code': '团队编码', - 'team.team.addForm.name': '团队名', - 'team.team.addForm.status': '状态', - 'team.team.addForm.status.valid': '有效', - 'team.team.addForm.status.invalid': '无效', - 'team.team.addForm.description': '描述', -} \ No newline at end of file + 'team.datasource.rightManagement': '权限管理', + 'team.datasource.alias': '链接名称', + 'team.datasource.url': '链接地址', + 'team.datasource.code': '编码', + 'team.datasource.name': '名称', + 'team.datasource.status': '状态', + + 'team.user.name': '用户', + 'team.user.userName': '用户名', + 'team.user.nickName': '昵称', + 'team.user.status': '状态', + 'team.user.addForm.userName': '用户名', + 'team.user.addForm.nickName': '昵称', + 'team.user.addForm.email': '邮箱', + 'team.user.addForm.password': '密码', + 'team.user.addForm.roleCode': '角色', + 'team.user.addForm.roleCode.admin': '管理员', + 'team.user.addForm.roleCode.user': '用户', + 'team.user.addForm.status': '状态', + 'team.user.addForm.status.valid': '有效', + 'team.user.addForm.status.invalid': '无效', + + 'team.team.name': '团队', + 'team.team.addForm.code': '团队编码', + 'team.team.addForm.name': '团队名', + 'team.team.addForm.status': '状态', + 'team.team.addForm.status.valid': '有效', + 'team.team.addForm.status.invalid': '无效', + 'team.team.addForm.description': '描述', +}; diff --git a/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx b/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx index 166cda0a6..4d8b1fa30 100644 --- a/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx +++ b/chat2db-client/src/pages/main/team/universal-add-modal/index.tsx @@ -1,9 +1,15 @@ -import { getCommonDataSourceList, getCommonTeamList, getCommonUserAndTeamList, getCommonUserList } from '@/service/team'; +import { + getCommonDataSourceList, + getCommonTeamList, + getCommonUserAndTeamList, + getCommonUserList, +} from '@/service/team'; import { IDataSourceVO, ITeamAndUserVO, ITeamVO, IUserVO, ManagementType, SearchType } from '@/typings/team'; import { Modal, Select, Spin } from 'antd'; import debounce from 'lodash/debounce'; import React, { useMemo, useState } from 'react'; import styles from './index.less'; +import i18n from '@/i18n'; interface IProps { open: boolean; @@ -21,51 +27,51 @@ interface ValueType { const addAuthMap = { [SearchType['USER/TEAM']]: { - title: '添加人员/团队', + title: i18n('team.action.addUserAndTeam'), loadRequest: getCommonUserAndTeamList, searchLabel: (data: ITeamAndUserVO) => data.name, searchValue: (data: ITeamAndUserVO) => JSON.stringify({ id: data.id, type: data.type }), searchListKey: 'accessObjectList', - placeholder: '搜索人员/团队', + placeholder: i18n('team.action.addUserAndTeam.placeholder'), }, [SearchType.TEAM]: { - title: '添加团队', + title: i18n('team.action.addTeam'), loadRequest: getCommonTeamList, searchLabel: (data: ITeamVO) => data.name, searchValue: (data: ITeamVO) => data.id, searchListKey: 'teamIdList', - placeholder: '搜索团队', + placeholder: i18n('team.action.addTeam.placeholder'), }, [SearchType.USER]: { - title: '添加人员', + title: i18n('team.action.addUser'), loadRequest: getCommonUserList, searchLabel: (data: IUserVO) => data.userName, searchValue: (data: IUserVO) => data.id, searchListKey: 'userIdList', - placeholder: '搜索人员', + placeholder: i18n('team.action.addUser.placeholder'), }, [SearchType.DATASOURCE]: { - title: '添加链接', + title: i18n('team.action.addDatasource'), loadRequest: getCommonDataSourceList, searchLabel: (data: IDataSourceVO) => data.alias, searchValue: (data: IDataSourceVO) => data.id, searchListKey: 'dataSourceIdList', - placeholder: '搜索链接', + placeholder: i18n('team.action.addDatasource.placeholder'), }, -} +}; function UniversalAddModal(props: IProps) { const { open, type } = props; const [fetching, setFetching] = useState(false); const [options, setOptions] = useState([]); - const [selectedValues, setSelectedValues] = useState([]) + const [selectedValues, setSelectedValues] = useState([]); const authData = useMemo(() => { if (type) { - return addAuthMap[type] + return addAuthMap[type]; } - }, [type]) + }, [type]); const loadOptions = (value: string) => { setOptions([]); @@ -91,14 +97,15 @@ function UniversalAddModal(props: IProps) { } const realValue = { - [authData.searchListKey]: type !== SearchType['USER/TEAM'] ? selectedValues : selectedValues.map(i => JSON.parse(i)) - } + [authData.searchListKey]: + type !== SearchType['USER/TEAM'] ? selectedValues : selectedValues.map((i) => JSON.parse(i)), + }; - props.onConfirm(realValue) + props.onConfirm(realValue); props.onClose && props.onClose(); setSelectedValues([]); setOptions([]); - } + }; return ( { - setSelectedValues(values) + setSelectedValues(values); }} /> diff --git a/chat2db-client/src/pages/main/team/universal-drawer/index.tsx b/chat2db-client/src/pages/main/team/universal-drawer/index.tsx index 5bb6f6480..cd20d0be5 100644 --- a/chat2db-client/src/pages/main/team/universal-drawer/index.tsx +++ b/chat2db-client/src/pages/main/team/universal-drawer/index.tsx @@ -78,24 +78,24 @@ function UniversalDrawer(props: IProps) { [AffiliationType.USER_TEAM]: { type: AffiliationType.USER_TEAM, searchType: SearchType.TEAM, - title: '团队', + title: i18n('team.team.name'), byIdKey: 'userId', queryListApi: getTeamListFromUser, updateListApi: updateTeamListFromUser, deleteApi: deleteTeamListFromUser, columns: [ { - title: '团队编码', + title: i18n('team.team.addForm.code'), dataIndex: ['team', 'code'], key: 'team.code', }, { - title: '团队名称', + title: i18n('team.team.addForm.name'), dataIndex: ['team', 'name'], key: 'team.name', }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 100, render: (_: any, record: IUserWithTeamVO) => ( @@ -122,24 +122,24 @@ function UniversalDrawer(props: IProps) { [AffiliationType.USER_DATASOURCE]: { type: AffiliationType.USER_DATASOURCE, searchType: SearchType.DATASOURCE, - title: '归属链接', + title: i18n('team.datasource.rightManagement'), byIdKey: 'userId', queryListApi: getDataSourceListFromUser, updateListApi: updateDataSourceListFromUser, deleteApi: deleteDataSourceFromUser, columns: [ { - title: '链接名称', + title: i18n('team.datasource.alias'), dataIndex: ['dataSource', 'alias'], key: 'dataSource.alias', }, { - title: '链接地址', + title: i18n('team.datasource.url'), dataIndex: ['dataSource', 'url'], key: 'dataSource.url', }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 100, render: (_: any, record: IUserWithDataSourceVO) => ( @@ -166,24 +166,24 @@ function UniversalDrawer(props: IProps) { [AffiliationType.TEAM_USER]: { type: AffiliationType.TEAM_USER, searchType: SearchType.USER, - title: '用户', + title: i18n('team.user.name'), byIdKey: 'teamId', queryListApi: getUserListFromTeam, updateListApi: updateUserListFromTeam, deleteApi: deleteUserFromTeam, columns: [ { - title: '用户名', + title: i18n('team.user.addForm.userName'), dataIndex: ['user', 'userName'], key: 'user.userName', }, { - title: '昵称', + title: i18n('team.user.addForm.nickName'), dataIndex: ['user', 'nickName'], key: 'user.nickName', }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 100, render: (_: any, record: ITeamWithUserVO) => ( @@ -210,24 +210,24 @@ function UniversalDrawer(props: IProps) { [AffiliationType.TEAM_DATASOURCE]: { type: AffiliationType.TEAM_DATASOURCE, searchType: SearchType.DATASOURCE, - title: '归属链接', + title: i18n('team.action.affiliation.datasource'), byIdKey: 'teamId', queryListApi: getDataSourceListFromTeam, updateListApi: updateDataSourceListFromTeam, deleteApi: deleteDataSourceFromTeam, columns: [ { - title: '链接名称', + title: i18n('team.datasource.alias'), dataIndex: ['dataSource', 'alias'], key: 'dataSource.alias', }, { - title: '链接地址', + title: i18n('team.datasource.url'), dataIndex: ['dataSource', 'url'], key: 'dataSource.url', }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 100, render: (_: any, record: ITeamWithDataSourceVO) => ( @@ -254,24 +254,24 @@ function UniversalDrawer(props: IProps) { [AffiliationType['DATASOURCE_USER/TEAM']]: { type: AffiliationType['DATASOURCE_USER/TEAM'], searchType: SearchType['USER/TEAM'], - title: '链接权限管理', + title: i18n('team.datasource.rightManagement'), byIdKey: 'dataSourceId', queryListApi: getUserAndTeamListFromDataSource, updateListApi: updateUserAndTeamListFromDataSource, deleteApi: deleteUserOrTeamFromDataSource, columns: [ { - title: '编码', + title: i18n('team.datasource.code'), dataIndex: ['accessObject', 'code'], key: 'accessObject.code', }, { - title: '名称', + title: i18n('team.datasource.name'), dataIndex: ['accessObject', 'name'], key: 'accessObject.name', }, { - title: '类型', + title: i18n('team.datasource.status'), dataIndex: ['accessObject', 'type'], key: 'accessObject.type', render: (status: ManagementType) => ( @@ -279,7 +279,7 @@ function UniversalDrawer(props: IProps) { ), }, { - title: '操作', + title: i18n('common.text.action'), key: 'action', width: 100, render: (_: any, record: IDataSourceAccessVO) => ( @@ -365,7 +365,7 @@ function UniversalDrawer(props: IProps) {
setSearchInput(v.target.value)} onSearch={handleSearch} @@ -382,7 +382,7 @@ function UniversalDrawer(props: IProps) { }); }} > - 添加 + {i18n('common.button.add')}
@@ -391,7 +391,7 @@ function UniversalDrawer(props: IProps) { {...modalInfo} onConfirm={(values) => { managementDataByType.updateListApi({ [managementDataByType.byIdKey]: props.byId, ...values }).then((res) => { - message.success('更新成功'); + message.success(i18n('common.tips.updateSuccess')); queryTableList(); }); }} From de895f1c4d6086c642b90e161f0ae3e166ee83d2 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 4 Sep 2023 17:33:35 +0800 Subject: [PATCH 0707/1069] feat: Add i18n --- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/en-us/index.ts | 4 +++- chat2db-client/src/i18n/en-us/login.ts | 12 ++++++++++++ chat2db-client/src/i18n/zh-cn/common.ts | 1 + chat2db-client/src/i18n/zh-cn/index.ts | 4 +++- chat2db-client/src/i18n/zh-cn/login.ts | 12 ++++++++++++ chat2db-client/src/pages/login/index.tsx | 19 ++++++++++--------- chat2db-client/src/pages/main/index.tsx | 3 ++- 8 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 chat2db-client/src/i18n/en-us/login.ts create mode 100644 chat2db-client/src/i18n/zh-cn/login.ts diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index e22b8b130..a9cca9f0d 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -81,4 +81,5 @@ export default { 'common.tips.createSuccess': 'Create Successfully', 'common.text.action': 'Action', 'common.button.add': 'Add', + }; diff --git a/chat2db-client/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts index deacf628b..964e0571b 100644 --- a/chat2db-client/src/i18n/en-us/index.ts +++ b/chat2db-client/src/i18n/en-us/index.ts @@ -6,6 +6,7 @@ import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; import team from './team' +import login from './login'; export default { lang: 'en', @@ -16,5 +17,6 @@ export default { ...menu, ...dashboard, ...chat, - ...team + ...team, + ...login }; diff --git a/chat2db-client/src/i18n/en-us/login.ts b/chat2db-client/src/i18n/en-us/login.ts new file mode 100644 index 000000000..e455739f0 --- /dev/null +++ b/chat2db-client/src/i18n/en-us/login.ts @@ -0,0 +1,12 @@ +export default { + 'login.text.logout': 'Logout', + 'login.text.welcome': 'Welcome to Chat2DB', + 'login.text.tips': 'The Chat2DB account is only for team collaboration management.', + 'login.text.tips.title': 'Why need login?', + 'login.text.setting': 'Setting', + 'login.form.user': 'UserName', + 'login.form.user.placeholder': 'Please enter your username', + 'login.form.password': 'Password', + 'login.form.password.placeholder': 'Please enter your password', + 'login.button.login': 'Login', +}; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index f48e502e9..427dafadf 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -79,4 +79,5 @@ export default { 'common.tips.createSuccess': '创建成功', 'common.text.action': '操作', 'common.button.add': '添加', + }; diff --git a/chat2db-client/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts index 4f46261f7..f9e6e22c6 100644 --- a/chat2db-client/src/i18n/zh-cn/index.ts +++ b/chat2db-client/src/i18n/zh-cn/index.ts @@ -7,6 +7,7 @@ import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; import team from './team' +import login from './login'; export default { lang: LangType.ZH_CN, @@ -18,5 +19,6 @@ export default { ...connection, ...dashboard, ...chat, - ...team + ...team, + ...login }; diff --git a/chat2db-client/src/i18n/zh-cn/login.ts b/chat2db-client/src/i18n/zh-cn/login.ts new file mode 100644 index 000000000..9ea27fe10 --- /dev/null +++ b/chat2db-client/src/i18n/zh-cn/login.ts @@ -0,0 +1,12 @@ +export default { + 'login.text.logout': '退出登录', + 'login.text.welcome': '欢迎使用 Chat2DB', + 'login.text.tips': 'Chat2DB 账号仅用于团队协作管理', + 'login.text.tips.title': '为什么需要登录?', + 'login.text.setting': '设 置', + 'login.form.user': '用户名', + 'login.form.user.placeholder': '请输入用户名', + 'login.form.password': '密码', + 'login.form.password.placeholder': '请输入密码', + 'login.button.login': '登 录', +}; diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index c03bf476e..3394e2e56 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -6,6 +6,7 @@ import LogoImg from '@/assets/logo/logo.png'; import styles from './index.less'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; +import i18n from '@/i18n'; interface IFormData { userName: string; @@ -27,32 +28,32 @@ const App: React.FC = () => {
Chat2DB
-
欢迎使用 Chat2DB
+
{i18n('login.text.welcome')}
- Chat2DB 账号仅用于团队协作管理 + {i18n('login.text.tips')}
} > -
为什么需要登录?
+
{i18n('login.text.tips.title')}
- + - - + + @@ -65,7 +66,7 @@ const App: React.FC = () => { icon={} className={styles.settingBtn} > - 设 置 + {i18n('login.text.setting')} } /> diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 60f6eca53..17e9b6873 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -18,6 +18,7 @@ import { getUser, userLogout } from '@/service/user'; import { ILoginUser } from '@/typings/user'; import { Dropdown } from 'antd'; import Team from './team'; +import i18n from '@/i18n'; let navConfig: INavItem[] = [ { @@ -139,7 +140,7 @@ function MainPage(props: IProps) { items: [ { key: '1', - label:
退出登录
, + label:
{i18n('login.text.logout')}
, }, ], }} From e2ce11bf267e448212c4339adf69b90d71136733 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 5 Sep 2023 19:26:31 +0800 Subject: [PATCH 0708/1069] feat: Connection list add environment --- .../src/components/ConnectionEdit/index.tsx | 38 ++-- chat2db-client/src/layouts/index.tsx | 1 - chat2db-client/src/models/connection.ts | 30 ++- .../src/pages/main/connection/index.less | 5 + .../src/pages/main/connection/index.tsx | 186 ++++++++++-------- chat2db-client/src/pages/main/index.tsx | 3 + chat2db-client/src/service/connection.ts | 11 +- chat2db-client/src/typings/connection.ts | 9 + 8 files changed, 170 insertions(+), 113 deletions(-) diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 7af68e3d7..43c5da91b 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -3,9 +3,8 @@ import { i18n, isEn } from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; import lodash from 'lodash'; - +import { connect } from 'umi'; import connectionService from '@/service/connection'; - import { DatabaseTypeCode, ConnectionEnvType, databaseMap } from '@/constants'; import { dataSourceFormConfigs } from './config/dataSource'; import { IConnectionConfig, IFormItem, ISelect } from './config/types'; @@ -18,6 +17,7 @@ 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'; const { Option } = Select; @@ -35,14 +35,15 @@ interface IProps { connectionData: IConnectionDetails; submitCallback?: Function; submit?: (data: IConnectionDetails) => void; + connectionModel: IConnectionModelType['state']; } export interface ICreateConnectionFunction { getData: () => IConnectionDetails; } -export default forwardRef(function CreateConnection(props: IProps, ref: ForwardedRef) { - const { className, closeCreateConnection, submitCallback, connectionData, submit } = props; +const CreateConnection = forwardRef(function (props: IProps, ref: ForwardedRef) { + const { className, closeCreateConnection, submitCallback, connectionData, submit, connectionModel } = props; const [baseInfoForm] = Form.useForm(); const [sshForm] = Form.useForm(); const [driveData, setDriveData] = useState({}); @@ -53,23 +54,18 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde backfillDataLoading: false, sshTestLoading: false }); - const [envList, setEnvList] = useState<{ value: string, label: string }[]>([]); + const { connectionEnvList } = connectionModel; + const [envList, setEnvList] = useState<{ value: number, label: string }[]>([]); useEffect(() => { - getEnvList(); - }, []); - - function getEnvList() { - connectionService.getEnvList().then((res) => { - setEnvList(res?.map(t => { - return { - value: t.id, - label: t.name - } - })); - }); - } + setEnvList(connectionEnvList?.map(t => { + return { + value: t.id, + label: t.name + } + })); + }, [connectionEnvList]); const dataSourceFormConfigPropsMemo = useMemo(() => { const deepCloneDataSourceFormConfigs = deepClone(dataSourceFormConfigs) @@ -309,6 +305,12 @@ export default forwardRef(function CreateConnection(props: IProps, ref: Forwarde ); }) +export default connect( + ({ connection }: { connection: IConnectionModelType;}) => ({ + connectionModel: connection, + }), +)(CreateConnection); + interface IRenderFormProps { tab: ITabsType; form: any; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 0589a9285..462a8ad0e 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -66,7 +66,6 @@ export default function Layout() { return ( - {/* */} ); } diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index 844af7928..581e8cf81 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -1,7 +1,6 @@ -import { IConnectionDetails } from '@/typings/connection'; import { Effect, Reducer } from 'umi'; import connectionService from '@/service/connection'; -import { IPageResponse } from '@/typings/common'; +import { IPageResponse, IConnectionEnv, IConnectionDetails } from '@/typings'; import { getCurConnection } from '@/utils/localStorage'; /** @@ -10,6 +9,7 @@ import { getCurConnection } from '@/utils/localStorage'; export interface IConnectionModelState { curConnection?: IConnectionDetails; connectionList: IConnectionDetails[]; + connectionEnvList: IConnectionEnv[]; } export interface IConnectionModelType { @@ -19,9 +19,11 @@ export interface IConnectionModelType { // 设置连接列表 setConnectionList: Reducer; setCurConnection: Reducer; + setConnectionEnvList: Reducer; }; effects: { fetchConnectionList: Effect; + fetchConnectionEnvList: Effect; }; } @@ -30,6 +32,7 @@ const ConnectionModel: IConnectionModelType = { state: { curConnection: getCurConnection(), connectionList: [], + connectionEnvList: [] }, reducers: { // 设置连接列表 @@ -45,6 +48,14 @@ const ConnectionModel: IConnectionModelType = { localStorage.setItem('cur-connection', JSON.stringify(payload)); return { ...state, curConnection: payload }; }, + + // 设置连接环境列表 + setConnectionEnvList(state, { payload }) { + return { + ...state, + connectionEnvList: payload, + }; + } }, effects: { @@ -61,6 +72,21 @@ const ConnectionModel: IConnectionModelType = { } 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 { + } }, }, diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 50ff97497..35ce8cc0f 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -197,3 +197,8 @@ transform: scale(1); transition: transform 0.3s ease-in-out; } + +.envLabel{ + line-height: 30px; + padding: 4px; +} diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 437953ab9..e46c8aabb 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -5,14 +5,13 @@ import ConnectionEdit from '@/components/ConnectionEdit'; import Iconfont from '@/components/Iconfont'; import connectionService from '@/service/connection'; import { DatabaseTypeCode, databaseMap, databaseTypeList } from '@/constants'; -import { IDatabase, IConnectionDetails, IEnvironmentVO } from '@/typings'; +import { IDatabase, IConnectionDetails, IConnectionEnv } from '@/typings'; import { Button, Dropdown, Modal } from 'antd'; -import { MoreOutlined } from '@ant-design/icons'; import styles from './index.less'; import { connect, history, Dispatch } from 'umi'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; - +import { deepClone } from '@/utils'; interface IMenu { key: number; @@ -21,11 +20,9 @@ interface IMenu { meta: IConnectionDetails; } -// interface IConnectionList { -// environment: 'dev' | 'test' | 'prod'; -// connectionList: IMenu[]; -// } - +interface ICarryEnvConnectionList extends IConnectionEnv { + connectionList?: IMenu[]; +} interface IProps { connectionModel: IConnectionModelType['state']; dispatch: any; @@ -33,7 +30,7 @@ interface IProps { function Connections(props: IProps) { const { connectionModel, dispatch } = props; - const { connectionList } = connectionModel; + const { connectionList, connectionEnvList } = connectionModel; const volatileRef = useRef(); const [curConnection, setCurConnection] = useState>({}); @@ -43,16 +40,29 @@ function Connections(props: IProps) { }); } - const menuItems: IMenu[] = useMemo( - () => - (connectionList || []).map((t) => ({ + const carryEnvConnectionList: ICarryEnvConnectionList[] = useMemo(() => { + const newConnectionEnvList: ICarryEnvConnectionList[] = deepClone(connectionEnvList); + connectionList.forEach((t) => { + const index = connectionEnvList.findIndex((env) => { + return env.id === t.environmentId; + }); + if(index === -1){ + return; + } + const menu = { key: t.id, icon: , label: t.alias, meta: t, - })), - [connectionList], - ); + }; + if (newConnectionEnvList[index].connectionList) { + newConnectionEnvList[index].connectionList?.push(menu); + } else { + newConnectionEnvList[index].connectionList = [menu]; + } + }); + return newConnectionEnvList; + }, [connectionList, connectionEnvList]); const handleMenuItemDoubleClick = (t?: any) => { dispatch({ @@ -69,85 +79,95 @@ function Connections(props: IProps) { const renderMenu = () => { return (
- {(menuItems || []).map((t) => { - const { key, label, icon } = t; + {carryEnvConnectionList.map((t) => { return ( -
+
{t.name}
+ {(t.connectionList || []).map((t) => { + const { key, label, icon } = t; + return ( +
{ + if (curConnection.id !== t.meta?.id) { + setCurConnection(t.meta); + } + }} + > +
+ {icon} + {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(); + }} + > + +
+
+
+ ); })} - onDoubleClick={handleMenuItemDoubleClick.bind(null, t)} - onClick={(event) => { - if (curConnection.id !== t.meta?.id) { - setCurConnection(t.meta); - } - }} - > -
- {icon} - {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() }}> - -
-
-
+ ); })}
); }; - return (
{i18n('connection.title.connections')}
{renderMenu()} - { - curConnection && !!Object.keys(curConnection).length && + {curConnection && !!Object.keys(curConnection).length && ( - } + )}
{curConnection && Object.keys(curConnection).length ? ( @@ -177,7 +197,7 @@ function Connections(props: IProps) { type: 'connection/fetchConnectionList', callback: (res: any) => { setCurConnection(res.data[res.data?.length - 1]); - } + }, }); }} /> diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 17e9b6873..738b4afce 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -91,6 +91,9 @@ function MainPage(props: IProps) { dispatch({ type: 'connection/fetchConnectionList', }); + dispatch({ + type: 'connection/fetchConnectionEnvList', + }); }, []); useEffect(() => { diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index 59f16cb9d..3ab1619f7 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,4 +1,4 @@ -import { IPageResponse, IConnectionDetails } from '@/typings'; +import { IPageResponse, IConnectionDetails, IConnectionEnv } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import createRequest from './base'; @@ -76,14 +76,7 @@ const downloadDriver = createRequest<{ dbType: string }, void>('/api/jdbc/driver const saveDriver = createRequest('/api/jdbc/driver/save', { errorLevel: false, method: 'post' }); -export interface IEnv { - id: string; - name: string; - shortName: string; - style: string; -} - -const getEnvList = createRequest('/api/common/environment/list_all', { errorLevel: false}); +const getEnvList = createRequest('/api/common/environment/list_all', { errorLevel: false}); export default { getEnvList, diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index f0e6841f3..a11544f86 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -15,6 +15,7 @@ export interface IConnectionDetails { ConsoleOpenedStatus: 'y' | 'n'; EnvType: ConnectionEnv; extendInfo: IConnectionExtendInfoItem[]; + environmentId: number; ssh: any; driverConfig: { jdbcDriver: string; @@ -24,3 +25,11 @@ export interface IConnectionDetails { } export type ICreateConnectionDetails = Omit + +// Connected environment +export interface IConnectionEnv { + id: number; + name: string; + shortName: string; + style: string; +} From ecf40b77730fd69d221fb5c7055a343cf3a0bc68 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 5 Sep 2023 19:33:46 +0800 Subject: [PATCH 0709/1069] fix:Connection list --- chat2db-client/src/components/ConnectionEdit/index.tsx | 1 - chat2db-client/src/pages/main/connection/index.tsx | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 43c5da91b..3bf4af9ec 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -57,7 +57,6 @@ const CreateConnection = forwardRef(function (props: IProps, ref: ForwardedRef([]); - useEffect(() => { setEnvList(connectionEnvList?.map(t => { return { diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index e46c8aabb..519916975 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -41,7 +41,7 @@ function Connections(props: IProps) { } const carryEnvConnectionList: ICarryEnvConnectionList[] = useMemo(() => { - const newConnectionEnvList: ICarryEnvConnectionList[] = deepClone(connectionEnvList); + const newConnectionEnvList: ICarryEnvConnectionList[] = []; connectionList.forEach((t) => { const index = connectionEnvList.findIndex((env) => { return env.id === t.environmentId; @@ -55,10 +55,13 @@ function Connections(props: IProps) { label: t.alias, meta: t, }; - if (newConnectionEnvList[index].connectionList) { + if (newConnectionEnvList[index]) { newConnectionEnvList[index].connectionList?.push(menu); } else { - newConnectionEnvList[index].connectionList = [menu]; + newConnectionEnvList[index] = { + ...connectionEnvList[index], + connectionList: [menu], + } } }); return newConnectionEnvList; From 536bc9911214e1877e07a10ce7d836f131a5a4f2 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Tue, 5 Sep 2023 19:46:27 +0800 Subject: [PATCH 0710/1069] Add return type --- .../web/api/controller/data/source/vo/DataSourceVO.java | 7 +++++++ 1 file changed, 7 insertions(+) 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 e3ae01800..363fc53b4 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 @@ -110,4 +110,11 @@ public class DataSourceVO { * 环境 */ private SimpleEnvironmentVO environment; + + /** + * 连接类型 + * + * @see ai.chat2db.server.domain.api.enums.DataSourceKindEnum + */ + private String kind; } From 77b9b8ced0b8a10aee99736873c0404608f359b9 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 5 Sep 2023 23:17:13 +0800 Subject: [PATCH 0711/1069] feat: Add shared and private to connection list --- .../RefreshLoadingButton/index.less | 4 + .../components/RefreshLoadingButton/index.tsx | 18 ++ chat2db-client/src/constants/common.ts | 5 + chat2db-client/src/i18n/en-us/connection.ts | 2 + chat2db-client/src/i18n/zh-cn/connection.ts | 2 + chat2db-client/src/models/connection.ts | 6 +- .../src/pages/main/connection/index.less | 13 +- .../src/pages/main/connection/index.tsx | 258 +++++++++++------- chat2db-client/src/service/connection.ts | 5 +- chat2db-client/src/typings/connection.ts | 1 + 10 files changed, 210 insertions(+), 104 deletions(-) create mode 100644 chat2db-client/src/components/RefreshLoadingButton/index.less create mode 100644 chat2db-client/src/components/RefreshLoadingButton/index.tsx diff --git a/chat2db-client/src/components/RefreshLoadingButton/index.less b/chat2db-client/src/components/RefreshLoadingButton/index.less new file mode 100644 index 000000000..2e57988d1 --- /dev/null +++ b/chat2db-client/src/components/RefreshLoadingButton/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/components/RefreshLoadingButton/index.tsx b/chat2db-client/src/components/RefreshLoadingButton/index.tsx new file mode 100644 index 000000000..11f4f4e52 --- /dev/null +++ b/chat2db-client/src/components/RefreshLoadingButton/index.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import Iconfont from '@/components/Iconfont'; +import { Spin } from 'antd'; + +interface IProps extends React.DetailedHTMLProps, HTMLDivElement> { + className?: string; + loading: boolean; +} + +export default memo(function RefreshLoadingButton(props) { + const { className, loading, ...res } = props + return
+ {loading && } + {!loading && } +
+}) diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 57a872064..f3e3189d8 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -62,4 +62,9 @@ export const operationTypeConfig: { [OperationType.TRIGGER]: { icon: '\ue64a' } +} + +export enum ConnectionKind { + Private = 'PRIVATE', + Shared = 'SHARED' } \ No newline at end of file diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 41c75686b..15abf400c 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -25,5 +25,7 @@ export default { 'connection.text.downloadSuccess': 'Download Success', 'connection.text.tryAgainDownload': 'Try again download', 'connection.text.downloading': 'Downloading...', + 'connection.label.private': 'Private', + 'connection.label.shared': 'Shared', }; \ No newline at end of file diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 4dfa26add..0e627edc0 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -25,4 +25,6 @@ export default { 'connection.text.downloadSuccess': '下载成功', 'connection.text.tryAgainDownload': '尝试重新下载', 'connection.text.downloading': '下载中...', + 'connection.label.private': '私有', + 'connection.label.shared': '共享', } diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index 581e8cf81..b55438d48 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -61,7 +61,11 @@ const ConnectionModel: IConnectionModelType = { effects: { *fetchConnectionList({ callback, payload }, { call, put }) { try { - const res = (yield connectionService.getList({ pageNo: 1, pageSize: 999, refresh: payload?.refresh })) as IPageResponse; + const res = (yield connectionService.getList({ + pageNo: 1, + pageSize: 999, + refresh: payload?.refresh, + })) as IPageResponse; yield put({ type: 'setConnectionList', payload: res.data, diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 35ce8cc0f..d1a7f7aa2 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -100,7 +100,6 @@ justify-content: center; align-items: center; position: relative; - } .dataBaseList { @@ -198,7 +197,17 @@ transition: transform 0.3s ease-in-out; } -.envLabel{ +.envLabel { + display: flex; + justify-content: space-between; line-height: 30px; padding: 4px; + font-size: 14px; +} + +.envRefreshBox { + &:hover { + cursor: pointer; + color: var(--color-primary); + } } diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 519916975..94ebd3bb3 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -3,8 +3,9 @@ import classnames from 'classnames'; import i18n from '@/i18n'; import ConnectionEdit from '@/components/ConnectionEdit'; import Iconfont from '@/components/Iconfont'; +import RefreshLoadingButton from '@/components/RefreshLoadingButton'; import connectionService from '@/service/connection'; -import { DatabaseTypeCode, databaseMap, databaseTypeList } from '@/constants'; +import { DatabaseTypeCode, databaseMap, databaseTypeList, ConnectionKind } from '@/constants'; import { IDatabase, IConnectionDetails, IConnectionEnv } from '@/typings'; import { Button, Dropdown, Modal } from 'antd'; import styles from './index.less'; @@ -18,11 +19,22 @@ interface IMenu { label: string; icon: React.ReactNode; meta: IConnectionDetails; + env: IConnectionEnv; } -interface ICarryEnvConnectionList extends IConnectionEnv { - connectionList?: IMenu[]; +interface IAllMenuList { + [ConnectionKind.Private]: { + list: IMenu[]; + name: string; + loading: boolean; + }, + [ConnectionKind.Shared]: { + list: IMenu[]; + name: string; + loading: boolean; + }, } + interface IProps { connectionModel: IConnectionModelType['state']; dispatch: any; @@ -33,39 +45,43 @@ function Connections(props: IProps) { const { connectionList, connectionEnvList } = connectionModel; const volatileRef = useRef(); const [curConnection, setCurConnection] = useState>({}); + const [allMenuList, setAllMenuList] = useState(); - function handleCreateConnections(database: IDatabase) { - setCurConnection({ - type: database.code, - }); - } - - const carryEnvConnectionList: ICarryEnvConnectionList[] = useMemo(() => { - const newConnectionEnvList: ICarryEnvConnectionList[] = []; - connectionList.forEach((t) => { - const index = connectionEnvList.findIndex((env) => { - return env.id === t.environmentId; - }); - if(index === -1){ - return; + 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, - }; - if (newConnectionEnvList[index]) { - newConnectionEnvList[index].connectionList?.push(menu); + env: t.environment, + } + if (t.kind === ConnectionKind.Shared) { + list[ConnectionKind.Shared].list.push(menu) } else { - newConnectionEnvList[index] = { - ...connectionEnvList[index], - connectionList: [menu], - } + list[ConnectionKind.Private].list.push(menu) } }); - return newConnectionEnvList; - }, [connectionList, connectionEnvList]); + setAllMenuList(list); + }, [connectionList]) + + function handleCreateConnections(database: IDatabase) { + setCurConnection({ + type: database.code, + }); + } const handleMenuItemDoubleClick = (t?: any) => { dispatch({ @@ -79,87 +95,131 @@ function Connections(props: IProps) { }); }; + const handelEnvRefresh = (kind: ConnectionKind) => { + let p = { + pageNo: 1, + pageSize: 999, + refresh: true, + kind, + } + if (allMenuList) { + setAllMenuList({ + ...allMenuList, + [kind]: { + ...allMenuList[kind], + loading: true, + } + }) + } + 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 (
- {carryEnvConnectionList.map((t) => { - return ( - <> -
{t.name}
- {(t.connectionList || []).map((t) => { - const { key, label, icon } = t; - return ( -
{ - if (curConnection.id !== t.meta?.id) { - setCurConnection(t.meta); - } - }} - > -
- {icon} - {label} -
- { - domEvent.stopPropagation(); - handleMenuItemDoubleClick(t); + {allMenuList && Object.keys(allMenuList).map(t => { + const data = allMenuList[t as ConnectionKind]; + if (data.list?.length) { + return ( + <> +
+
{data.name}
+ handelEnvRefresh(t as ConnectionKind)} /> +
+ {(data.list || []).map(t => { + const { key, label, icon } = t; + return ( +
{ + if (curConnection.id !== t.meta?.id) { + setCurConnection(t.meta); + } + }} + > +
+ {icon} + {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', + { + 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(); + ], }} > - -
-
-
- ); - })} - - ); +
{ + e.stopPropagation(); + }} + > + +
+
+
+ ); + })} + + ); + } })}
); diff --git a/chat2db-client/src/service/connection.ts b/chat2db-client/src/service/connection.ts index 3ab1619f7..e1ce31786 100644 --- a/chat2db-client/src/service/connection.ts +++ b/chat2db-client/src/service/connection.ts @@ -1,12 +1,13 @@ import { IPageResponse, IConnectionDetails, IConnectionEnv } from '@/typings'; -import { DatabaseTypeCode } from '@/constants'; +import { DatabaseTypeCode, ConnectionKind } from '@/constants'; import createRequest from './base'; export interface IGetConnectionParams { searchKey?: string; pageNo: number; pageSize: number; - refresh?: boolean + refresh?: boolean; + kind?: ConnectionKind; } /** diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index a11544f86..5b6917676 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -16,6 +16,7 @@ export interface IConnectionDetails { EnvType: ConnectionEnv; extendInfo: IConnectionExtendInfoItem[]; environmentId: number; + environment: IConnectionEnv, ssh: any; driverConfig: { jdbcDriver: string; From b7bc95665faf0057062044f6466985f08aaf5a9d Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 3 Sep 2023 18:47:38 +0800 Subject: [PATCH 0712/1069] Modifying Oracle Query Blob Error --- .../java/ai/chat2db/spi/util/JdbcUtils.java | 50 +++++++++++++++---- 1 file changed, 39 insertions(+), 11 deletions(-) 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 e0b499664..368e038c6 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,6 +1,7 @@ package ai.chat2db.spi.util; import java.math.BigDecimal; +import java.sql.Blob; import java.sql.Clob; import java.sql.Connection; import java.sql.ResultSet; @@ -144,24 +145,51 @@ public static String getResultSetValue(ResultSet rs, int index, boolean limitSiz if (obj == null) { return null; } - if (obj instanceof BigDecimal bigDecimal) { - return bigDecimal.toPlainString(); - } else if (obj instanceof Double d) { - return BigDecimal.valueOf(d).toPlainString(); - } else if (obj instanceof Float f) { - return BigDecimal.valueOf(f).toPlainString(); - } else if (obj instanceof Clob) { - return largeString(rs, index, limitSize); - } else if (obj instanceof byte[]) { - return largeString(rs, index, limitSize); + try { + if (obj instanceof BigDecimal bigDecimal) { + return bigDecimal.toPlainString(); + } else if (obj instanceof Double d) { + return BigDecimal.valueOf(d).toPlainString(); + } else if (obj instanceof Float f) { + return BigDecimal.valueOf(f).toPlainString(); + } else if (obj instanceof Clob) { + return largeString(rs, index, limitSize); + } else if (obj instanceof byte[]) { + return largeString(rs, index, limitSize); + } else if (obj instanceof Blob blob) { + return largeStringBlob(blob, limitSize); + } + return rs.getString(index); + } catch (Exception e) { + log.warn("解析数失败:{},{}", index, obj, e); + return obj.toString(); + } + } + + private static String largeStringBlob(Blob blob, boolean limitSize) throws SQLException { + if (blob == null) { + return null; + } + int length = Math.toIntExact(blob.length()); + if (limitSize && length > MAX_RESULT_SIZE) { + length = Math.toIntExact(MAX_RESULT_SIZE); + } + byte[] data = blob.getBytes(1, length); + String result = new String(data); + + if (length > MAX_RESULT_SIZE) { + return "[ " + DataSizeUtil.format(MAX_RESULT_SIZE) + " of " + DataSizeUtil.format(length) + + " ," + + I18nUtils.getMessage("execute.exportCsv") + " ] " + result; } - return rs.getString(index); + return result; } private static String largeString(ResultSet rs, int index, boolean limitSize) throws SQLException { String result = rs.getString(index); if (result == null) { return null; + } if (!limitSize) { return result; From 55d582a547c046edef314c60f54085d8fbaaae34 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 3 Sep 2023 18:51:03 +0800 Subject: [PATCH 0713/1069] changelog --- CHANGELOG.md | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74be1c2a2..54f54053e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,28 @@ +# 2.0.14 + +## 🐞 Bug Fixes + +- Fix the issue of 'Oracle' query 'Blob' reporting errors +- Modify the paging logic and fix some SQL queries that cannot be queried + +## 🐞 问题修复 + +- 修复 `Oracle` 查询 `Blob` 报错的问题 +- 修改分页逻辑,修复部分SQL无法查询 + # 2.0.12 ## ⭐ New Features -- 🔥Supports viewing views, functions, triggers, and procedures -- Support selected sql formatting +- 🔥Supports viewing views, functions, triggers, and procedures +- Support selected sql formatting - Added new dark themes ## 🐞 Bug Fixes - Fixed sql formatting failure issue -- Fixed an issue where locally stored theme colors and background colors are incompatible with the new version, causing page crashes +- Fixed an issue where locally stored theme colors and background colors are incompatible with the new version, causing + page crashes - Logs desensitize sensitive data - Fix the issue of 'CLOB' not displaying specific content [Issue #440](https://github.com/chat2db/Chat2DB/issues/440) - Fix the problem that non-Select does not display query results @@ -17,9 +30,6 @@ - Fix the problem of special type of SQL execution error reporting - Fix the problem that the test link is successful, but the error is reported when saving the link - - - ## ⭐ 新特性 - 🔥支持查看视图、函数、触发器、存储过程 From e98e261aa79171d987c91f80ac0ef9675000183d Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 5 Sep 2023 12:47:07 +0800 Subject: [PATCH 0714/1069] set larger timeout --- .../ai/chat2db/server/web/api/controller/ai/ChatController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 634ac7039..89ceb821b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -89,7 +89,7 @@ public class ChatController { /** * chat的超时时间 */ - private static final Long CHAT_TIMEOUT = Duration.ofMinutes(10).toMillis(); + private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); /** * 提示语最大token数 From 18e6ef3e69157902ae051c9f144bb6a2e02419d0 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 6 Sep 2023 16:37:11 +0800 Subject: [PATCH 0715/1069] fix dm function bug --- .../plugin/sqlserver/SqlServerMetaData.java | 38 +++++++++++++++++-- .../ai/chat2db/spi/sql/IDriverManager.java | 7 +++- 2 files changed, 39 insertions(+), 6 deletions(-) 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 35af57867..d83b31355 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 @@ -68,6 +68,10 @@ public String tableDDL(Connection connection, String databaseName, String schema = "SELECT type_desc, OBJECT_NAME(object_id) AS FunctionName, OBJECT_DEFINITION(object_id) AS " + "definition FROM sys.objects WHERE type_desc IN(%s) and name = '%s' ;"; + + private static String OBJECT_SQL + = "SELECT name FROM sys.objects WHERE type = '%s' and SCHEMA_ID = SCHEMA_ID('%s');"; + @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, String functionName) { @@ -87,8 +91,22 @@ public Function function(Connection connection, @NotEmpty String databaseName, S @Override public List functions(Connection connection, String databaseName, String schemaName) { - List functions = SQLExecutor.getInstance().functions(connection, databaseName, schemaName); - return functions.stream().map(function -> removeVersion(function)).collect(Collectors.toList()); + List functions = new ArrayList<>(); +// List functions = SQLExecutor.getInstance().functions(connection, databaseName, schemaName); +// return functions.stream().map(function -> removeVersion(function)).collect(Collectors.toList()); + String sql = String.format(OBJECT_SQL,"FN", schemaName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Function function = new Function(); + function.setDatabaseName(databaseName); + function.setSchemaName(schemaName); + if (resultSet.next()) { + function.setFunctionName(resultSet.getString("name")); + } + functions.add(function); + } + return functions; + }); } private Function removeVersion(Function function) { @@ -103,8 +121,20 @@ private Function removeVersion(Function function) { @Override public List procedures(Connection connection, String databaseName, String schemaName) { - List procedures = SQLExecutor.getInstance().procedures(connection, databaseName, schemaName); - return procedures.stream().map(procedure -> removeVersion(procedure)).collect(Collectors.toList()); + List procedures = new ArrayList<>(); + String sql = String.format(OBJECT_SQL,"P", schemaName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + if (resultSet.next()) { + procedure.setProcedureName(resultSet.getString("name")); + } + procedures.add(procedure); + } + return procedures; + }); } private Procedure removeVersion(Procedure procedure) { 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 83ae4e2be..78f65c3f6 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 @@ -78,13 +78,16 @@ public static Connection getConnection(String url, Properties info, DriverConfig if (url == null) { throw new SQLException("The url cannot be null", "08001"); } - SQLException reason = null; DriverEntry driverEntry = DRIVER_ENTRY_MAP.get(driver.getJdbcDriver()); if (driverEntry == null) { driverEntry = getJDBCDriver(driver); } try { - return driverEntry.getDriver().connect(url, info); + Connection connection = driverEntry.getDriver().connect(url, info); + if(connection == null){ + throw new ConnectionException("driverEntry.getDriver().connect return null",null); + } + return connection; } catch (SQLException var7) { Connection con = tryConnectionAgain(driverEntry, url, info); if (con != null) { From 0e9434f2d583e3a033f3d153ee6db3c66ee0e0f8 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 6 Sep 2023 17:10:31 +0800 Subject: [PATCH 0716/1069] fix dm function bug --- .../src/main/java/ai/chat2db/spi/sql/IDriverManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 78f65c3f6..d841646a0 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,7 +85,7 @@ public static Connection getConnection(String url, Properties info, DriverConfig try { Connection connection = driverEntry.getDriver().connect(url, info); if(connection == null){ - throw new ConnectionException("driverEntry.getDriver().connect return null",null); + throw new SQLException("driver.connect return null , No suitable driver found for url " +url ,"08001"); } return connection; } catch (SQLException var7) { From 883c0db84357795f4e337118a5e52bd3521f91e5 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sun, 3 Sep 2023 17:41:14 +0800 Subject: [PATCH 0717/1069] Add Publishing Branch --- .github/workflows/release_test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index d71beff83..14867d079 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -7,6 +7,8 @@ on: push: branches: - "release_test" + - "release_test_2" + - "release_test_3" # Workflow's jobs # 一共需要3台电脑运行 From 821713499aca0d9c6739eb6bc01f9a1dc9f10b2f Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 6 Sep 2023 20:09:39 +0800 Subject: [PATCH 0718/1069] rest ai fix --- .../api/controller/ai/listener/RestAIEventSourceListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java index 8f6aa47d3..657c458f3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java @@ -54,7 +54,7 @@ public void onEvent(EventSource eventSource, String id, String type, String data } Message message = new Message(); if (StringUtils.isNotBlank(data)) { - message.setContent(data); + message.setContent(data.replace("\"", "")); sseEmitter.send(SseEmitter.event() .id(id) .data(message) From bc69f2e4bbbe209d301f2f4dbbcc0bbcc136ab8f Mon Sep 17 00:00:00 2001 From: robinji0 <137188352+robinji0@users.noreply.github.com> Date: Wed, 6 Sep 2023 20:14:05 +0800 Subject: [PATCH 0719/1069] Update README.md --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index ba1562ccb..9fcc6238f 100644 --- a/README.md +++ b/README.md @@ -116,10 +116,7 @@ To facilitate users' quick use of AI capabilities, you can scan the QR code belo ### CONFIGURE CUSTOM AI -- Customized AI can be any LLM that you deployed, such as ChatGLM、ChatGPT、ERNIE Bot、Tongyi Qianwen, and so on. However, the customized interface need to conform to the protocol definition. Otherwise, secondary development may be required. Two DEMOs are provided in the code, the configuration is as shown below. In specific use, you can refer to the DEMO interface to write a custom interface, or directly perform secondary development in the DEMO interface. -- DEMO for configuring customized stream output interface. -- DEMO for configuring customized non-stream output interface. -5 +* [Refer here to deploy your ChatGLM-6B model](https://github.com/chat2db/chat2db-chatglm-6b-deploy) ## 📦 Docker installation From 87665a817b81a414a0d2359658b182a9440c0cc8 Mon Sep 17 00:00:00 2001 From: robinji0 <137188352+robinji0@users.noreply.github.com> Date: Wed, 6 Sep 2023 20:17:19 +0800 Subject: [PATCH 0720/1069] Update README_CN.md --- README_CN.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README_CN.md b/README_CN.md index 749357b51..5d8fa3a2e 100644 --- a/README_CN.md +++ b/README_CN.md @@ -111,6 +111,9 @@ Redis and MongoDB are partially supported , Hbase、Elasticsearch、openGauss、 +### 使用自定义大模型 +- [参考这里部署本地ChatGLM-6B模型](https://github.com/chat2db/chat2db-chatglm-6b-deploy/blob/main/README_CN.md) + ## 📦 Docker 部署 ```bash From c9050d0010cc27f6ff0e1f07f02e35ba9d2aeee2 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Thu, 7 Sep 2023 16:39:15 +0800 Subject: [PATCH 0721/1069] rest ai fix --- .../api/controller/ai/listener/RestAIEventSourceListener.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java index 657c458f3..90962a40e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java @@ -102,7 +102,7 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { eventSource.cancel(); } Message message = new Message(); - message.setContent("出现异常,请在帮助中查看详细日志:" + bodyString); + message.setContent("Rest AI Error:" + bodyString); sseEmitter.send(SseEmitter.event() .id("[ERROR]") .data(message)); From 322a20d2e997efafb3ca44fb5a7ee32173db9a9b Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Thu, 7 Sep 2023 18:28:50 +0800 Subject: [PATCH 0722/1069] fix tableName is null --- .../src/main/java/ai/chat2db/spi/model/TableColumn.java | 1 + 1 file changed, 1 insertion(+) 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 7d58d1d5c..9a5db8867 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 @@ -31,6 +31,7 @@ public class TableColumn { /** * 表名 */ + @JsonAlias({"TABLE_NAME"}) private String tableName; /** From 1c6fecea4f59e7b7ae952e2ffdf22dea7a68a39e Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Thu, 7 Sep 2023 19:48:53 +0800 Subject: [PATCH 0723/1069] Add query column data type --- .../domain/api/param/TypeQueryParam.java | 22 +++++ .../domain/api/service/TableService.java | 16 ++- .../domain/core/impl/TableServiceImpl.java | 18 ++-- .../api/controller/rdb/TableController.java | 37 ++++--- .../rdb/request/TypeQueryRequest.java | 16 +++ .../main/java/ai/chat2db/spi/MetaData.java | 8 ++ .../chat2db/spi/jdbc/DefaultMetaService.java | 50 +++++----- .../main/java/ai/chat2db/spi/model/Type.java | 99 +++++++++++++++++++ .../java/ai/chat2db/spi/sql/SQLExecutor.java | 79 +++++++++------ 9 files changed, 257 insertions(+), 88 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TypeQueryParam.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TypeQueryRequest.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TypeQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TypeQueryParam.java new file mode 100644 index 000000000..2d430ed3d --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TypeQueryParam.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.domain.api.param; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TypeQueryParam { + + /** + * 对应数据库存储的来源id + */ + @NotNull + private Long dataSourceId; + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java index 961424b6d..32218325d 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java @@ -2,12 +2,8 @@ import java.util.List; +import ai.chat2db.server.domain.api.param.*; import ai.chat2db.spi.model.*; -import ai.chat2db.server.domain.api.param.DropParam; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.param.TableSelector; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -81,6 +77,7 @@ public interface TableService { /** * 查询表包含的字段 + * * @param param * @return */ @@ -88,8 +85,17 @@ public interface TableService { /** * 查询表索引 + * * @param param * @return */ List queryIndexes(TableQueryParam param); + + /** + * + * @param param + * + * @return + */ + List queryTypes(TypeQueryParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 6c80c2a60..25e77f7b3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -6,12 +6,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import ai.chat2db.server.domain.api.param.DropParam; -import ai.chat2db.server.domain.api.param.PinTableParam; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.PinService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.core.cache.CacheManage; @@ -23,10 +18,7 @@ import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; -import ai.chat2db.spi.model.Sql; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.SqlUtils; import com.google.common.collect.Lists; @@ -153,4 +145,10 @@ public List queryIndexes(TableQueryParam param) { return metaSchema.indexes(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName()); } + + @Override + public List queryTypes(TypeQueryParam param) { + MetaData metaSchema = Chat2DBContext.getMetaData(); + return metaSchema.types(Chat2DBContext.getConnection()); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 2be6f0623..c13b5c810 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -2,11 +2,7 @@ import java.util.List; -import ai.chat2db.server.domain.api.param.DropParam; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; -import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; @@ -17,13 +13,7 @@ import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; -import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableCreateDdlQueryRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableModifySqlRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableUpdateDdlQueryRequest; +import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; @@ -31,6 +21,7 @@ import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.Type; import com.google.common.collect.Lists; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; @@ -73,7 +64,7 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), - request.getPageSize()); + request.getPageSize()); } @@ -178,9 +169,23 @@ public DataResult query(@Valid TableDetailQueryRequest request) { @GetMapping("/modify/sql") public ListResult modifySql(@Valid TableModifySqlRequest request) { return tableService.buildSql( - rdbWebConverter.tableRequest2param(request.getOldTable()), - rdbWebConverter.tableRequest2param(request.getNewTable())) - .map(rdbWebConverter::dto2vo); + rdbWebConverter.tableRequest2param(request.getOldTable()), + rdbWebConverter.tableRequest2param(request.getNewTable())) + .map(rdbWebConverter::dto2vo); + } + + + /** + * 获取修改表的sql语句 + * + * @param request + * @return + */ + @GetMapping("/type_list") + public ListResult types(@Valid TypeQueryRequest request) { + TypeQueryParam typeQueryParam = TypeQueryParam.builder().dataSourceId(request.getDataSourceId()).build(); + List types = tableService.queryTypes(typeQueryParam); + return ListResult.of(types); } /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TypeQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TypeQueryRequest.java new file mode 100644 index 000000000..a7f1c6d54 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TypeQueryRequest.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequestInfo; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class TypeQueryRequest implements DataSourceBaseRequestInfo { + + @NotNull + private Long dataSourceId; + /** + * DB名称 + */ + private String databaseName; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 6abcc0204..3e7a85ef7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -172,4 +172,12 @@ List indexes(Connection connection, @NotEmpty String databaseName, S * @return */ Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName,String procedureName); + + + /** + * + * @param connection + * @return + */ + List types(Connection connection); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 002af04c0..4dd49ee90 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -5,14 +5,7 @@ import java.util.Map; import ai.chat2db.spi.MetaData; -import ai.chat2db.spi.model.Database; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Schema; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; /** @@ -26,18 +19,18 @@ public List databases(Connection connection) { } @Override - public List schemas(Connection connection,String databaseName) { + public List schemas(Connection connection, String databaseName) { return SQLExecutor.getInstance().schemas(connection, databaseName, null); } @Override - public String tableDDL(Connection connection,String databaseName, String schemaName, String tableName) { + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { return null; } @Override - public List
tables(Connection connection,String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(connection,databaseName, schemaName, tableName, new String[]{"TABLE"}); + public List
tables(Connection connection, String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().tables(connection, databaseName, schemaName, tableName, new String[]{"TABLE"}); } @Override @@ -46,39 +39,39 @@ public Table view(Connection connection, String databaseName, String schemaName, } @Override - public List
views(Connection connection,String databaseName, String schemaName) { - return SQLExecutor.getInstance().tables(connection,databaseName, schemaName, null, new String[]{"VIEW"}); + public List
views(Connection connection, String databaseName, String schemaName) { + return SQLExecutor.getInstance().tables(connection, databaseName, schemaName, null, new String[]{"VIEW"}); } @Override - public List functions(Connection connection,String databaseName, String schemaName) { - return SQLExecutor.getInstance().functions(connection,databaseName, schemaName); + public List functions(Connection connection, String databaseName, String schemaName) { + return SQLExecutor.getInstance().functions(connection, databaseName, schemaName); } @Override - public List triggers(Connection connection,String databaseName, String schemaName) { + public List triggers(Connection connection, String databaseName, String schemaName) { return null; } @Override - public List procedures(Connection connection,String databaseName, String schemaName) { - return SQLExecutor.getInstance().procedures(connection,databaseName, schemaName); + public List procedures(Connection connection, String databaseName, String schemaName) { + return SQLExecutor.getInstance().procedures(connection, databaseName, schemaName); } @Override - public List columns(Connection connection,String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().columns(connection,databaseName, schemaName, tableName, null); + public List columns(Connection connection, String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().columns(connection, databaseName, schemaName, tableName, null); } @Override - public List columns(Connection connection,String databaseName, String schemaName, String tableName, - String columnName) { - return SQLExecutor.getInstance().columns(connection,databaseName, schemaName, tableName, columnName); + public List columns(Connection connection, String databaseName, String schemaName, String tableName, + String columnName) { + return SQLExecutor.getInstance().columns(connection, databaseName, schemaName, tableName, columnName); } @Override - public List indexes(Connection connection,String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().indexes(connection,databaseName, schemaName, tableName); + public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { + return SQLExecutor.getInstance().indexes(connection, databaseName, schemaName, tableName); } @Override @@ -95,4 +88,9 @@ public Trigger trigger(Connection connection, String databaseName, String schema public Procedure procedure(Connection connection, String databaseName, String schemaName, String procedureName) { return null; } + + @Override + public List types(Connection connection) { + return SQLExecutor.getInstance().types(connection); + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java new file mode 100644 index 000000000..cb7568c17 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Type.java @@ -0,0 +1,99 @@ +package ai.chat2db.spi.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Type { + + @JsonAlias("TYPE_NAME") + private String typeName; + + @JsonAlias("DATA_TYPE") + private Integer dataType; + + @JsonAlias("PRECISION") + private Integer precision; + + @JsonAlias("LITERAL_PREFIX") + private String literalPrefix; + + @JsonAlias("LITERAL_SUFFIX") + private String literalSuffix; + + @JsonAlias("CREATE_PARAMS") + private String createParams; + + @JsonAlias("NULLABLE") + private Short nullable; + + + @JsonAlias("CASE_SENSITIVE") + private Boolean caseSensitive; + + @JsonAlias("SEARCHABLE") + private Short searchable; + + @JsonAlias("UNSIGNED_ATTRIBUTE") + private Boolean unsignedAttribute; + + + @JsonAlias("FIXED_PREC_SCALE") + private Boolean fixedPrecScale; + + @JsonAlias("AUTO_INCREMENT") + private Boolean autoIncrement; + + @JsonAlias("LOCAL_TYPE_NAME") + private String localTypeName; + + @JsonAlias("MINIMUM_SCALE") + private Short minimumScale; + + @JsonAlias("MAXIMUM_SCALE") + private Short maximumScale; + + @JsonAlias("SQL_DATA_TYPE") + private Integer sqlDataType; + + @JsonAlias("SQL_DATETIME_SUB") + private Integer sqlDatetimeSub; + + + @JsonAlias("NUM_PREC_RADIX") + private Integer numPrecRadix; + + + +// TYPE_NAME String => Type name +// DATA_TYPE int => SQL data type from java.sql.Types +// PRECISION int => maximum precision +// LITERAL_PREFIX String => prefix used to quote a literal (may be null) +// LITERAL_SUFFIX String => suffix used to quote a literal (may be null) +// CREATE_PARAMS String => parameters used in creating the type (may be null) +// NULLABLE short => can you use NULL for this type. +// typeNoNulls - does not allow NULL values +// typeNullable - allows NULL values +// typeNullableUnknown - nullability unknown +// CASE_SENSITIVE boolean=> is it case sensitive. +// SEARCHABLE short => can you use "WHERE" based on this type: +// typePredNone - No support +// typePredChar - Only supported with WHERE .. LIKE +// typePredBasic - Supported except for WHERE .. LIKE +// typeSearchable - Supported for all WHERE .. +// UNSIGNED_ATTRIBUTE boolean => is it unsigned. +// FIXED_PREC_SCALE boolean => can it be a money value. +// AUTO_INCREMENT boolean => can it be used for an auto-increment value. +// LOCAL_TYPE_NAME String => localized version of type name (may be null) +// MINIMUM_SCALE short => minimum scale supported +// MAXIMUM_SCALE short => maximum scale supported +// SQL_DATA_TYPE int => unused +// SQL_DATETIME_SUB int => unused +// NUM_PREC_RADIX int => usually 2 or 10 +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 06f1fc6d2..e0612b8d9 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -91,12 +91,12 @@ public R execute(Connection connection, String sql, ResultSetFunction fun } public void executeSql(Connection connection, String sql, Consumer> headerConsumer, - Consumer> rowConsumer) { + Consumer> rowConsumer) { executeSql(connection, sql, headerConsumer, rowConsumer, true); } public void executeSql(Connection connection, String sql, Consumer> headerConsumer, - Consumer> rowConsumer, boolean limitSize) { + Consumer> rowConsumer, boolean limitSize) { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); try (Statement stmt = connection.createStatement();) { @@ -114,10 +114,10 @@ public void executeSql(Connection connection, String sql, Consumer> List
headerList = Lists.newArrayListWithExpectedSize(col); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() - .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) - .build()); + .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) + .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) + .build()); } headerConsumer.accept(headerList); @@ -160,8 +160,8 @@ public ExecuteResult execute(final String sql, Connection connection) throws SQL * @throws SQLException */ public ExecuteResult execute(final String sql, Connection connection, boolean limitRowSize, Integer offset, - Integer count) - throws SQLException { + Integer count) + throws SQLException { Assert.notNull(sql, "SQL must not be null"); log.info("execute:{}", sql); @@ -185,10 +185,10 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li executeResult.setHeaderList(headerList); for (int i = 1; i <= col; i++) { headerList.add(Header.builder() - .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) - .build()); + .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) + .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) + .build()); } // 获取数据信息 @@ -297,9 +297,9 @@ public List schemas(Connection connection, String databaseName, String s * @return */ public List
tables(Connection connection, String databaseName, String schemaName, String tableName, - String types[]) { + String types[]) { try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, tableName, - types)) { + types)) { return ResultSetUtils.toObjectList(resultSet, Table.class); } catch (SQLException e) { throw new RuntimeException(e); @@ -317,9 +317,9 @@ public List
tables(Connection connection, String databaseName, String sch * @return */ public List columns(Connection connection, String databaseName, String schemaName, String tableName, - String columnName) { + String columnName) { try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, - columnName)) { + columnName)) { return ResultSetUtils.toObjectList(resultSet, TableColumn.class); } catch (Exception e) { throw new RuntimeException(e); @@ -338,22 +338,22 @@ public List columns(Connection connection, String databaseName, Str public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { List tableIndices = Lists.newArrayList(); try (ResultSet resultSet = connection.getMetaData().getIndexInfo(databaseName, schemaName, tableName, - false, - false)) { + false, + false)) { List tableIndexColumns = ResultSetUtils.toObjectList(resultSet, TableIndexColumn.class); tableIndexColumns.stream().filter(c -> c.getIndexName() != null).collect( - Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() - .stream().forEach(entry -> { - TableIndex tableIndex = new TableIndex(); - TableIndexColumn column = entry.getValue().get(0); - tableIndex.setName(entry.getKey()); - tableIndex.setTableName(column.getTableName()); - tableIndex.setSchemaName(column.getSchemaName()); - tableIndex.setDatabaseName(column.getDatabaseName()); - tableIndex.setUnique(!column.getNonUnique()); - tableIndex.setColumnList(entry.getValue()); - tableIndices.add(tableIndex); - }); + Collectors.groupingBy(TableIndexColumn::getIndexName)).entrySet() + .stream().forEach(entry -> { + TableIndex tableIndex = new TableIndex(); + TableIndexColumn column = entry.getValue().get(0); + tableIndex.setName(entry.getKey()); + tableIndex.setTableName(column.getTableName()); + tableIndex.setSchemaName(column.getSchemaName()); + tableIndex.setDatabaseName(column.getDatabaseName()); + tableIndex.setUnique(!column.getNonUnique()); + tableIndex.setColumnList(entry.getValue()); + tableIndices.add(tableIndex); + }); } catch (SQLException e) { throw new RuntimeException(e); } @@ -369,7 +369,7 @@ public List indexes(Connection connection, String databaseName, Stri * @return List */ public List functions(Connection connection, String databaseName, - String schemaName) { + String schemaName) { try (ResultSet resultSet = connection.getMetaData().getFunctions(databaseName, schemaName, null);) { return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Function.class); } catch (Exception e) { @@ -377,6 +377,23 @@ public List functions(Connection connection, Stri } } + + /** + * Retrieves a description of all the data types supported by this database. They are ordered by DATA_TYPE and then by how closely the data type maps to the corresponding JDBC SQL type. + * If the database supports SQL distinct types, then getTypeInfo() will return a single row with a TYPE_NAME of DISTINCT and a DATA_TYPE of Types.DISTINCT. If the database supports SQL structured types, then getTypeInfo() will return a single row with a TYPE_NAME of STRUCT and a DATA_TYPE of Types.STRUCT. + * If SQL distinct or structured types are supported, then information on the individual types may be obtained from the getUDTs() method. + * + * @param connection connection + * @return List + */ + public List types(Connection connection) { + try (ResultSet resultSet = connection.getMetaData().getTypeInfo();) { + return ResultSetUtils.toObjectList(resultSet, ai.chat2db.spi.model.Type.class); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** * procedure list * From b80666d0ef25479aeb355f58aeabecd273363860 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Thu, 7 Sep 2023 19:49:33 +0800 Subject: [PATCH 0724/1069] Add query column data type --- .../chat2db/server/web/api/controller/rdb/TableController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index c13b5c810..272bff03f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -176,7 +176,7 @@ public ListResult modifySql(@Valid TableModifySqlRequest request) { /** - * 获取修改表的sql语句 + * 数据库支持的数据类型 * * @param request * @return From 6fe08e4eec896ae016c450024626c881cf299992 Mon Sep 17 00:00:00 2001 From: robinji0 <137188352+robinji0@users.noreply.github.com> Date: Thu, 7 Sep 2023 20:52:37 +0800 Subject: [PATCH 0725/1069] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9fcc6238f..32d6e334c 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ To facilitate users' quick use of AI capabilities, you can scan the QR code belo ### CONFIGURE CUSTOM AI * [Refer here to deploy your ChatGLM-6B model](https://github.com/chat2db/chat2db-chatglm-6b-deploy) - +* [Refer here to deploy your sqlcoder model](https://github.com/chat2db/chat2db-sqlcoder-deploy) ## 📦 Docker installation From d49c592451005f6f98355c81b6e244c1070f59e6 Mon Sep 17 00:00:00 2001 From: robinji0 <137188352+robinji0@users.noreply.github.com> Date: Thu, 7 Sep 2023 20:55:19 +0800 Subject: [PATCH 0726/1069] Update README_CN.md --- README_CN.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README_CN.md b/README_CN.md index 5d8fa3a2e..c3c8c22fa 100644 --- a/README_CN.md +++ b/README_CN.md @@ -113,6 +113,7 @@ Redis and MongoDB are partially supported , Hbase、Elasticsearch、openGauss、 ### 使用自定义大模型 - [参考这里部署本地ChatGLM-6B模型](https://github.com/chat2db/chat2db-chatglm-6b-deploy/blob/main/README_CN.md) +- [参考这里部署本地sqlcoder模型](https://github.com/chat2db/chat2db-sqlcoder-deploy/blob/main/README_CN.md) ## 📦 Docker 部署 From fc46048c9ddffc4c0ca8e63dcf9ef16fdf97a27f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 7 Sep 2023 22:32:56 +0800 Subject: [PATCH 0727/1069] Open edit table --- .../SQLExecute}/index.less | 2 +- .../SQLExecute}/index.tsx | 0 chat2db-client/src/constants/common.ts | 28 +++-- chat2db-client/src/constants/index.ts | 1 + chat2db-client/src/constants/workspace.ts | 5 + chat2db-client/src/models/workspace.ts | 16 ++- .../Tree/TreeNodeRightClick/index.tsx | 18 ++-- .../components/WorkspaceLeft/index.tsx | 4 +- .../components/WorkspaceRight/index.less | 5 +- .../components/WorkspaceRight/index.tsx | 100 +++++++++++++----- chat2db-client/src/typings/common.ts | 4 +- chat2db-client/src/typings/index.ts | 3 +- chat2db-client/src/typings/user.ts | 25 +++++ chat2db-client/src/typings/workspace.ts | 10 ++ chat2db-client/src/utils/check.ts | 32 ++++++ 15 files changed, 194 insertions(+), 59 deletions(-) rename chat2db-client/src/{pages/main/workspace/components/WorkspaceRightItem => blocks/SQLExecute}/index.less (83%) rename chat2db-client/src/{pages/main/workspace/components/WorkspaceRightItem => blocks/SQLExecute}/index.tsx (100%) create mode 100644 chat2db-client/src/constants/workspace.ts create mode 100644 chat2db-client/src/typings/user.ts create mode 100644 chat2db-client/src/typings/workspace.ts create mode 100644 chat2db-client/src/utils/check.ts diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less b/chat2db-client/src/blocks/SQLExecute/index.less similarity index 83% rename from chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less rename to chat2db-client/src/blocks/SQLExecute/index.less index 917a41b1c..c20e0db75 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.less +++ b/chat2db-client/src/blocks/SQLExecute/index.less @@ -1,4 +1,4 @@ -@import '../../../../../styles/var.less'; +@import '../../styles/var.less'; .box { height: 100%; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx similarity index 100% rename from chat2db-client/src/pages/main/workspace/components/WorkspaceRightItem/index.tsx rename to chat2db-client/src/blocks/SQLExecute/index.tsx diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 57a872064..8ed836440 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -33,33 +33,41 @@ export enum OSType { RESTS = 'rests', } -export enum OperationType { +export enum TabType { CONSOLE = 'console', FUNCTION = 'function', PROCEDURE = 'procedure', VIEW = 'view', TRIGGER = 'trigger', + EditTable = 'editTable', + OpenTable = 'openTable', } -export const operationTypeConfig: { - [key in OperationType]: { +export const tabTypeConfig: { + [key in TabType]: { icon: string }; } = { - [OperationType.CONSOLE]: { + [TabType.CONSOLE]: { icon: '\uec83' }, - [OperationType.VIEW]: { + [TabType.VIEW]: { icon: '\ue70c' }, - [OperationType.FUNCTION]: { + [TabType.FUNCTION]: { icon: '\ue76a' }, - [OperationType.PROCEDURE]: { + [TabType.PROCEDURE]: { icon: '\ue73c' }, - - [OperationType.TRIGGER]: { + [TabType.TRIGGER]: { icon: '\ue64a' + }, + [TabType.EditTable]: { + icon: '\ue6b6' + }, + [TabType.OpenTable]: { + icon: '\ue618' } -} \ No newline at end of file +} + diff --git a/chat2db-client/src/constants/index.ts b/chat2db-client/src/constants/index.ts index 4e87d59d5..1d5a88212 100644 --- a/chat2db-client/src/constants/index.ts +++ b/chat2db-client/src/constants/index.ts @@ -6,4 +6,5 @@ export * from './monacoEditor'; export * from './table'; export * from './theme'; export * from './tree'; +export * from './workspace'; diff --git a/chat2db-client/src/constants/workspace.ts b/chat2db-client/src/constants/workspace.ts new file mode 100644 index 000000000..8b9ea89b2 --- /dev/null +++ b/chat2db-client/src/constants/workspace.ts @@ -0,0 +1,5 @@ +export enum CreateTabIntroType { + EditorTable = 'EditorTable', + OpenTable = 'openTable', +} + diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index cb53f6a16..931cbffb9 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -3,7 +3,7 @@ import sqlService, { MetaSchemaVO } from '@/service/sql'; import historyService from '@/service/history'; import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants'; import { Effect, Reducer } from 'umi'; -import { ITreeNode, IConsole, IPageResponse } from '@/typings'; +import { ITreeNode, IConsole, IPageResponse, ICreateTabIntro } from '@/typings'; import { treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; export type ICurWorkspaceParams = { @@ -26,6 +26,8 @@ export interface IWorkspaceModelState { openConsoleList: IConsole[]; curTableList: ITreeNode[]; curViewList: ITreeNode[]; + // 触发tab编辑表或打开表 + createTabIntro: ICreateTabIntro | undefined; } export interface IWorkspaceModelType { @@ -41,6 +43,7 @@ export interface IWorkspaceModelType { setCurConsoleId: Reducer; setCurTableList: Reducer; setCurViewList: Reducer; + setCreateTabIntro: Reducer; }; effects: { fetchDatabaseAndSchema: Effect; @@ -62,7 +65,8 @@ const WorkspaceModel: IWorkspaceModelType = { openConsoleList: [], curTableList: [], curViewList: [], - curConsoleId: null + curConsoleId: null, + createTabIntro: undefined, }, reducers: { @@ -125,6 +129,14 @@ const WorkspaceModel: IWorkspaceModelType = { curViewList: payload, }; }, + // 创建tab的引子 + setCreateTabIntro(state, { payload }) { + return { + ...state, + createTabIntro: payload, + }; + }, + }, effects: { diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 3d4b76139..6a1c83a55 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -4,21 +4,16 @@ import classnames from 'classnames'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import { MenuProps, message, Modal, Input, Dropdown, notification } from 'antd'; -import { TreeNodeType, DatabaseTypeCode } from '@/constants'; +import { TreeNodeType, CreateTabIntroType, TabType } from '@/constants'; import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; import { ITreeNode } from '@/typings'; import connectionServer from '@/service/connection'; -import historyService from '@/service/history'; import mysqlServer from '@/service/sql'; import { OperationColumn } from '../treeConfig'; import { dataSourceFormConfigs } from '@/components/CreateConnection/config/dataSource'; import { IConnectionConfig } from '@/components/CreateConnection/config/types'; import { IWorkspaceModelType } from '@/models/workspace'; -import EditDialog from '@/components/EditDialog'; -import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; -import MonacoEditor, { IExportRefFunction, IRangeType } from '@/components/Console/MonacoEditor'; - -type MenuItem = Required['items'][number]; +import MonacoEditor from '@/components/Console/MonacoEditor'; export type IProps = { className?: string; @@ -121,7 +116,14 @@ function TreeNodeRightClick(props: IProps) { text: i18n('workspace.menu.editTable'), icon: '\ue602', handle: () => { - setVerifyDialog(true); + dispatch({ + type: 'workspace/setCreateTabIntro', + payload: { + type: CreateTabIntroType.EditorTable, + tabType: TabType.EditTable, + treeNodeData: data, + }, + }) } } }, 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 7fff45c33..501de1709 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -9,7 +9,7 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import historyServer from '@/service/history'; import Tree from '../Tree'; -import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus, OperationType } from '@/constants'; +import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus, TabType } from '@/constants'; import { IConsole, ITreeNode, ICreateConsole } from '@/typings'; import styles from './index.less'; import { approximateTreeNode, approximateList } from '@/utils'; @@ -70,7 +70,7 @@ const WorkspaceLeft = memo(function (props) { type: databaseType, status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, - operationType: OperationType.CONSOLE + operationType: TabType.CONSOLE } historyService.saveConsole(params || p).then(res => { dispatch({ 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 105ce9e52..77a28144f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -14,9 +14,6 @@ } } -.tabs { -} - .consoleBox { position: absolute; top: 32px; @@ -48,7 +45,7 @@ overflow: hidden; } -.tab-item{ +.tab-item { display: flex; align-items: center; } 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 53cb74f79..704a206f2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -2,21 +2,20 @@ import React, { memo, useRef, useEffect, useState } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; -import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TreeNodeType, operationTypeConfig, OperationType } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TreeNodeType, tabTypeConfig, TabType } from '@/constants'; import { IConsole, ICreateConsole } from '@/typings'; import historyService from '@/service/history'; import sqlService from '@/service/sql'; import Tabs, { IOption } from '@/components/Tabs'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; -import WorkspaceRightItem from '../WorkspaceRightItem'; import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; +import SQLExecute from '@/blocks/SQLExecute'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IAIState } from '@/models/ai'; import { handleLocalStorageSavedConsole } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; -import Tree from 'antd/es/tree/Tree'; -import Iconfont from '@/components/Iconfont'; +import { v4 as uuidV4 } from 'uuid'; interface IProps { className?: string; @@ -25,12 +24,52 @@ interface IProps { dispatch: any; } +export interface ITab { + id: number; + tabType: TabType; + icon: string; + [key: string]: any; +} + const WorkspaceRight = memo(function (props) { const [activeConsoleId, setActiveConsoleId] = useState(); const { className, aiModel, workspaceModel, dispatch } = props; - const { curWorkspaceParams, doubleClickTreeNodeData, openConsoleList, curConsoleId } = workspaceModel; + const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, curConsoleId } = workspaceModel; const openConsoleListRef = useRef(openConsoleList); + const [tabList, setTabList] = useState([]); + + useEffect(() => { + const newTabList = openConsoleList?.map(t => { + return { + ...t, + tabType: TabType.CONSOLE, + icon: tabTypeConfig[t.operationType]?.icon || tabTypeConfig.console.icon, + } + }) + setTabList(newTabList || []) + }, [openConsoleList]) + + // 监听编辑表事件 + useEffect(() => { + if (createTabIntro) { + const id: any = uuidV4(); + setTabList([...tabList, { + id, + tabType: createTabIntro.tabType, + icon: tabTypeConfig[createTabIntro.tabType as TabType]?.icon, + name: `edit-${createTabIntro.treeNodeData.name}`, + }]) + setActiveConsoleId(id); + + // 用完之后就清掉createTabIntro + dispatch({ + type: 'workspace/setCreateTabIntro', + payload: null, + }) + } + }, [createTabIntro]) + // 监听双击树节点事件 生成console useEffect(() => { if (!doubleClickTreeNodeData) { return; @@ -239,7 +278,6 @@ const WorkspaceRight = memo(function (props) { } }, [openConsoleList]); - function createConsole(params: { doubleClickTreeNodeData: any, name: string, @@ -288,8 +326,8 @@ const WorkspaceRight = memo(function (props) { }); } - function onChange(key: number | string) { - setActiveConsoleId(+key); + function onChange(key: any) { + setActiveConsoleId(key); } const onEdit = (action: 'add' | 'remove', key?: number) => { @@ -315,7 +353,7 @@ const WorkspaceRight = memo(function (props) { type: databaseType, status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, - operationType: OperationType.CONSOLE, + operationType: TabType.CONSOLE, }; historyService.saveConsole(params?.newConsole || p).then((res) => { params?.callback?.(res); @@ -403,7 +441,7 @@ const WorkspaceRight = memo(function (props) { return (
- +
(function (props) { editableName={true} editableNameOnBlur={editableNameOnBlur} activeTab={activeConsoleId} - tabs={(openConsoleList || [])?.map((t, i) => { + tabs={(tabList || [])?.map((t, i) => { return { - prefixIcon: operationTypeConfig[t.operationType]?.icon || operationTypeConfig.console.icon, + prefixIcon: t.icon, label: t.name, value: t.id, }; })} />
- {openConsoleList?.map((t, index) => { + {tabList?.map((t, index) => { return (
- - {/* */} + { + [TabType.CONSOLE, TabType.FUNCTION, TabType.PROCEDURE, TabType.TRIGGER, TabType.VIEW].includes(t.tabType) && + } + { + t.tabType === TabType.EditTable && + }
); })} diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 56eac3240..3eaf7cc38 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -1,4 +1,4 @@ -import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, OperationType } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TabType } from '@/constants'; export interface IPageResponse { data: T[]; @@ -27,7 +27,7 @@ export interface IConsole { status: ConsoleStatus; // 控制台状态 connectable: boolean; // 是否可连接 tabOpened?: ConsoleOpenedStatus; // 控制台tab是否打开 - operationType: OperationType; // 操作类型 + operationType: TabType; // 操作类型 } export interface Option { diff --git a/chat2db-client/src/typings/index.ts b/chat2db-client/src/typings/index.ts index d2aad71e6..a79722c7a 100644 --- a/chat2db-client/src/typings/index.ts +++ b/chat2db-client/src/typings/index.ts @@ -5,4 +5,5 @@ export * from './database'; export * from './main'; export * from './theme'; export * from './tree'; -export * from './setting' \ No newline at end of file +export * from './setting' +export * from './workspace' \ No newline at end of file diff --git a/chat2db-client/src/typings/user.ts b/chat2db-client/src/typings/user.ts new file mode 100644 index 000000000..7c11a48c0 --- /dev/null +++ b/chat2db-client/src/typings/user.ts @@ -0,0 +1,25 @@ +export type IRole = 'admin' | 'normal'; +export interface IUser { + id?: number; + userName: string; + nickName: string; + password?: string; + password2?: string; + email: string; + role?: IRole; +} + +export interface ILoginUser { + /** + * Is it an administrator + */ + admin?: boolean; + /** + * 用户id + */ + id?: number; + /** + * 昵称 + */ + nickName?: string; +} diff --git a/chat2db-client/src/typings/workspace.ts b/chat2db-client/src/typings/workspace.ts new file mode 100644 index 000000000..19497051a --- /dev/null +++ b/chat2db-client/src/typings/workspace.ts @@ -0,0 +1,10 @@ +import { CreateTabIntroType, TabType } from '@/constants'; +import { ITreeNode } from '@/typings'; + + +export interface ICreateTabIntro { + type: CreateTabIntroType; + tabType: TabType; + treeNodeData: ITreeNode; +} + diff --git a/chat2db-client/src/utils/check.ts b/chat2db-client/src/utils/check.ts new file mode 100644 index 000000000..766331e81 --- /dev/null +++ b/chat2db-client/src/utils/check.ts @@ -0,0 +1,32 @@ +const toString = Object.prototype.toString + + +const isType = + (type: string | string[]) => + (obj: unknown): obj is T => + getType(obj) === `[object ${type}]` +export const getType = (obj: any) => toString.call(obj) +export const isArr = Array.isArray; +export const isValid = (val: any) => val !== null && val !== undefined; +export const isFn = (val: any): val is Function => typeof val === 'function' +export const isPlainObj = isType('Object') +export const isStr = isType('String') +export const isBool = isType('Boolean') +export const isNum = isType('Number') +export const isMap = (val: any): val is Map => + val && val instanceof Map +export const isSet = (val: any): val is Set => val && val instanceof Set +export const isWeakMap = (val: any): val is WeakMap => + val && val instanceof WeakMap +export const isWeakSet = (val: any): val is WeakSet => + val && val instanceof WeakSet +export const isNumberLike = (index: any): index is number => + isNum(index) || /^\d+$/.test(index) +export const isObj = (val: unknown): val is object => typeof val === 'object' +export const isRegExp = isType('RegExp') +export const isReactElement = (obj: any): boolean => + obj && obj['$$typeof'] && obj['_owner'] +export const isHTMLElement = (target: any): target is EventTarget => { + return Object.prototype.toString.call(target).indexOf('HTML') > -1 +} + From 77daf5907dc256f18f9818482ee0a577ffdadcfd Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 11:24:54 +0800 Subject: [PATCH 0728/1069] Fix some team project bugs --- .../server/domain/api/model/Chart.java | 6 ++--- .../domain/repository/entity/ChartDO.java | 10 ++++----- .../domain/repository/mapper/ChartMapper.java | 4 ++-- .../server/start/test/dto 2/TestDTO.java | 22 ------------------- .../test/mybatis/MybatisGeneratorTest.java | 2 +- .../converter/DataSourceAdminConverter.java | 1 + .../controller/dashboard/ChartController.java | 3 ++- .../dashboard/request/ChartCreateRequest.java | 4 +++- .../api/controller/dashboard/vo/ChartVO.java | 6 ++--- 9 files changed, 20 insertions(+), 38 deletions(-) delete mode 100644 chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Chart.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Chart.java index 9cf8b7384..b2c17f5e4 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Chart.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Chart.java @@ -1,6 +1,6 @@ package ai.chat2db.server.domain.api.model; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Data; @@ -20,12 +20,12 @@ public class Chart { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 图表名称 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ChartDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ChartDO.java index f2859bd42..2c233cb66 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ChartDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/ChartDO.java @@ -4,7 +4,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Getter; import lombok.Setter; @@ -13,8 +13,8 @@ * 自定义报表表 *

* - * @author ali-dbhub - * @since 2023-06-09 + * @author chat2db + * @since 2023-09-09 */ @Getter @Setter @@ -32,12 +32,12 @@ public class ChartDO implements Serializable { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 图表名称 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ChartMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ChartMapper.java index 6b368020b..b390aabd1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ChartMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/ChartMapper.java @@ -8,8 +8,8 @@ * 自定义报表表 Mapper 接口 *

* - * @author ali-dbhub - * @since 2023-06-09 + * @author chat2db + * @since 2023-09-09 */ public interface ChartMapper extends BaseMapper { diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java deleted file mode 100644 index 0646e0b68..000000000 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/dto 2/TestDTO.java +++ /dev/null @@ -1,22 +0,0 @@ -package ai.chat2db.server.start.test.dto; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TestDTO implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - private String name; -} diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index 38af74412..4b11e3830 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -35,7 +35,7 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("operation_saved")); //doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access", // "dbhub_user")); - doGenerator(Lists.newArrayList("dashboard")); + doGenerator(Lists.newArrayList("chart")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java index 5236de606..3b91cd068 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java @@ -29,6 +29,7 @@ public abstract class DataSourceAdminConverter { */ @Mappings({ @Mapping(target = "enableReturnCount", expression = "java(true)"), + @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), }) public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java index b393a38e7..1660b8139 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/ChartController.java @@ -14,6 +14,7 @@ import ai.chat2db.server.web.api.controller.dashboard.request.ChartQueryRequest; import ai.chat2db.server.web.api.controller.dashboard.request.ChartUpdateRequest; import ai.chat2db.server.web.api.controller.dashboard.vo.ChartVO; +import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -78,7 +79,7 @@ public ListResult list(ChartQueryRequest request) { * @return */ @PostMapping("/create") - public DataResult create(@RequestBody ChartCreateRequest request) { + public DataResult create(@Valid @RequestBody ChartCreateRequest request) { ChartCreateParam chartCreateParam = chartWebConverter.req2param(request); return chartService.createWithPermission(chartCreateParam); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java index cd220fe83..130c82a6f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java @@ -1,6 +1,6 @@ package ai.chat2db.server.web.api.controller.dashboard.request; - +import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -25,11 +25,13 @@ public class ChartCreateRequest { /** * 图表信息 */ + @NotNull private String schema; /** * 数据源连接ID */ + @NotNull private Long dataSourceId; /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/vo/ChartVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/vo/ChartVO.java index 8bc4150de..b5560faae 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/vo/ChartVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/vo/ChartVO.java @@ -1,6 +1,6 @@ package ai.chat2db.server.web.api.controller.dashboard.vo; -import java.time.LocalDateTime; +import java.util.Date; import lombok.Data; @@ -20,12 +20,12 @@ public class ChartVO { /** * 创建时间 */ - private LocalDateTime gmtCreate; + private Date gmtCreate; /** * 修改时间 */ - private LocalDateTime gmtModified; + private Date gmtModified; /** * 图表名称 From 0f9f0e314f6ad2e165b55bf4a79b0f0bec609e5c Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 11:33:09 +0800 Subject: [PATCH 0729/1069] Fix some team project bugs --- .../server/start/config/config/Chat2dbWebMvcConfigurer.java | 1 + .../server/start/controller/oauth/OauthController.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index b74f2619f..b4d4a0146 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -92,6 +92,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl return null; } if (!ValidStatusEnum.VALID.getCode().equals(user.getStatus())) { + StpUtil.logout(); throw new BusinessException("oauth.invalidUserName"); } boolean admin = RoleCodeEnum.ADMIN.getCode().equals(user.getRoleCode()); diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java index 4ff19fafe..a29173ad0 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java @@ -1,6 +1,7 @@ package ai.chat2db.server.start.controller.oauth; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.ValidStatusEnum; import jakarta.annotation.Resource; import ai.chat2db.server.domain.api.model.User; @@ -50,6 +51,9 @@ public DataResult login(@Validated @RequestBody LoginRequest request) { if (user == null) { throw new BusinessException("oauth.userNameNotExits"); } + if (!ValidStatusEnum.VALID.getCode().equals(user.getStatus())) { + throw new BusinessException("oauth.invalidUserName"); + } if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(user.getId())) { throw new BusinessException("oauth.IllegalUserName"); } From 20cbf5fed51fe217872729ab81ad5089e5afc9d7 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 14:09:41 +0800 Subject: [PATCH 0730/1069] Fix some team project bugs --- .../api/controller/dashboard/request/ChartCreateRequest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java index 130c82a6f..ebdc17510 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/dashboard/request/ChartCreateRequest.java @@ -1,6 +1,5 @@ package ai.chat2db.server.web.api.controller.dashboard.request; -import jakarta.validation.constraints.NotNull; import lombok.Data; /** @@ -25,13 +24,11 @@ public class ChartCreateRequest { /** * 图表信息 */ - @NotNull private String schema; /** * 数据源连接ID */ - @NotNull private Long dataSourceId; /** From 085e2e7a98fb0f2b2486339539c7ad50e5e497f0 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 14:35:45 +0800 Subject: [PATCH 0731/1069] Fix some team project bugs --- .../param/dashboard/DashboardSelector.java | 23 +++++++++++++++++++ .../domain/api/service/DashboardService.java | 1 + .../core/impl/DashboardServiceImpl.java | 9 +++++++- .../ai/chat2db/server/start/Application.java | 7 ++++++ .../server/tools/common/model/ConfigJson.java | 5 ++++ .../{ => system}/SystemController.java | 14 +++++++---- .../api/controller/system/vo/SystemVO.java | 22 ++++++++++++++++++ 7 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardSelector.java rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/{ => system}/SystemController.java (83%) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/SystemVO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardSelector.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardSelector.java new file mode 100644 index 000000000..f5e072f74 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/dashboard/DashboardSelector.java @@ -0,0 +1,23 @@ +package ai.chat2db.server.domain.api.param.dashboard; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * selectro + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class DashboardSelector { + + /** + * 图表ID列表 + */ + private Boolean chartIds; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java index 07a765c25..645f9df60 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DashboardService.java @@ -45,6 +45,7 @@ public interface DashboardService { * 查询一条数据 * * @param param + * @param selector * @return */ DataResult queryExistent(@NotNull DashboardQueryParam param); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java index 4825dc833..b630e66d3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DashboardServiceImpl.java @@ -102,7 +102,14 @@ public DataResult queryExistent(DashboardQueryParam param) { if (CollectionUtils.isEmpty(page.getRecords())) { throw new DataNotFoundException(); } - return DataResult.of(dashboardConverter.do2model(page.getRecords().get(0))); + Dashboard data = dashboardConverter.do2model(page.getRecords().get(0)); + LambdaQueryWrapper dashboardChartRelationQueryWrapper = new LambdaQueryWrapper<>(); + dashboardChartRelationQueryWrapper.eq(DashboardChartRelationDO::getDashboardId, param.getId()); + List relationDO = dashboardChartRelationMapper.selectList( + dashboardChartRelationQueryWrapper); + List chartIds = relationDO.stream().map(DashboardChartRelationDO::getChartId).toList(); + data.setChartIds(chartIds); + return DataResult.of(data); } @Override diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java index 92dab365d..32f805945 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/Application.java @@ -37,6 +37,13 @@ public class Application { public static void main(String[] args) { String currentVersion = ConfigUtils.getLocalVersion(); ConfigJson configJson = ConfigUtils.getConfig(); + + // The unique ID of the entire system will not change after multiple starts + if (StringUtils.isBlank(configJson.getSystemUuid())) { + configJson.setSystemUuid(UUID.fastUUID().toString(true)); + ConfigUtils.setConfig(configJson); + } + // Represents that the current version has been successfully launched if (StringUtils.isNotBlank(currentVersion) && StringUtils.equals(currentVersion, configJson.getLatestStartupSuccessVersion())) { diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java index 484e18aeb..5d0006097 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/ConfigJson.java @@ -25,4 +25,9 @@ public class ConfigJson { * jwt */ private String jwtSecretKey; + + /** + * The unique ID of the system + */ + private String systemUuid; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java similarity index 83% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java index 9f7c05cbc..58c506e3a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/SystemController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java @@ -2,18 +2,19 @@ * Alipay.com Inc. * Copyright (c) 2004-2022 All Rights Reserved. */ -package ai.chat2db.server.web.api.controller; +package ai.chat2db.server.web.api.controller.system; import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; -import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.tools.common.model.ConfigJson; +import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.web.api.controller.system.vo.SystemVO; import ai.chat2db.spi.ssh.SSHManager; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; -import org.springframework.context.i18n.LocaleContextHolder; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -39,8 +40,11 @@ public class SystemController { * @return */ @GetMapping - public DataResult get() { - return DataResult.of("success"); + public DataResult get() { + ConfigJson configJson = ConfigUtils.getConfig(); + return DataResult.of(SystemVO.builder() + .systemUuid(configJson.getSystemUuid()) + .build()); } /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/SystemVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/SystemVO.java new file mode 100644 index 000000000..cee2b8cf8 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/SystemVO.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.web.api.controller.system.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * system + * + * @author Jiaju Zhuang + */ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SystemVO { + /** + * The unique ID of the system + */ + private String systemUuid; +} From 8e9891eb8b9836a9709cd420f8f3d30edb568e35 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 9 Sep 2023 15:18:54 +0800 Subject: [PATCH 0732/1069] fix: dashboard error --- .../src/components/Console/index.tsx | 4 +- chat2db-client/src/i18n/en-us/dashboard.ts | 2 + chat2db-client/src/i18n/zh-cn/dashboard.ts | 2 + .../main/dashboard/chart-item/index.less | 2 +- .../pages/main/dashboard/chart-item/index.tsx | 53 +++++++++++-------- .../src/pages/main/dashboard/index.tsx | 2 + 6 files changed, 40 insertions(+), 25 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index ba927760d..bc9a26cea 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -405,7 +405,7 @@ function Console(props: IProps) { /** * 格式化sql */ - const handelSQLFormat = () => { + const handleSQLFormat = () => { let setValueType = 'select'; let sql = editorRef?.current?.getCurrentSelectContent(); if (!sql) { @@ -482,7 +482,7 @@ function Console(props: IProps) { )} - diff --git a/chat2db-client/src/i18n/en-us/dashboard.ts b/chat2db-client/src/i18n/en-us/dashboard.ts index 93cc80ebb..4ffeb2d29 100644 --- a/chat2db-client/src/i18n/en-us/dashboard.ts +++ b/chat2db-client/src/i18n/en-us/dashboard.ts @@ -7,4 +7,6 @@ export default { 'dashboard.export2image': 'Export to image', 'dashboard.delete': 'Delete', 'dashboard.editor.cascader.placeholder': 'Please select a connection pool', + 'dashboard.editor.execute.noDataSource': 'Please select a data source first', + 'dashboard.editor.execute.success': 'Successful, Please select Chart Configuration', }; diff --git a/chat2db-client/src/i18n/zh-cn/dashboard.ts b/chat2db-client/src/i18n/zh-cn/dashboard.ts index 93cfaea5b..c2bb6326b 100644 --- a/chat2db-client/src/i18n/zh-cn/dashboard.ts +++ b/chat2db-client/src/i18n/zh-cn/dashboard.ts @@ -7,4 +7,6 @@ export default { 'dashboard.delete': '删除', 'dashboard.export2image': '导出图片', 'dashboard.editor.cascader.placeholder': '请选择连接', + 'dashboard.editor.execute.noDataSource': '请先选择数据源', + 'dashboard.editor.execute.success': '执行成功,请选择图表配置', }; 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 ae2f2ed1f..e350d2926 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.less +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.less @@ -115,7 +115,7 @@ .emptyDataText { font-size: 12px; - color: rgba(0, 0, 0, 0.40); + color: var(--color-gray-300); line-height: 14px; margin-bottom: 40px; } 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 8759d94f7..2547ecfb0 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -19,6 +19,7 @@ import i18n from '@/i18n'; import { useTheme } from '@/hooks'; import { EditorThemeType, ThemeType } from '@/constants'; import { IRemainingUse } from '@/typings/ai'; +import _ from 'lodash'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; @@ -134,29 +135,34 @@ function ChartItem(props: IChartItemProps) { }; const handleExecuteSQL = async (sql: string, chartData: IChartItem) => { - setIsLoading(true); - const { dataSourceId, databaseName } = chartData; - - let executeSQLParams: IExecuteSqlParams = { - sql, - dataSourceId, - databaseName, - }; - - // 获取当前SQL的查询结果 - let sqlResult = await sqlService.executeSql(executeSQLParams); - - let sqlData; - if (sqlResult && sqlResult[0]) { - sqlData = handleSQLResult2ChartData(sqlResult[0]); + if (_.isEmpty(cascaderValue)) { + message.success(i18n('dashboard.editor.execute.noDataSource')); + return; + } + setIsLoading(true); + try { + let executeSQLParams: IExecuteSqlParams = { + sql, + dataSourceId, + databaseName, + }; + // 获取当前SQL的查询结果 + let sqlResult = await sqlService.executeSql(executeSQLParams); + + let sqlData; + if (sqlResult && sqlResult[0]) { + sqlData = handleSQLResult2ChartData(sqlResult[0]); + } + setChartData({ + ...chartData, + ddl: sql, + sqlData, + }); + message.success(i18n('dashboard.editor.execute.success')); + } finally { + setIsLoading(false); } - setChartData({ - ...chartData, - ddl: sql, - sqlData, - }); - setIsLoading(false); }; /** 根据id请求Chart数据 */ @@ -379,7 +385,10 @@ function ChartItem(props: IChartItemProps) { loadData={loadData} onChange={(value, selectedOptions) => { console.log('onChange', value, selectedOptions); - let p: any = {}; //包含了dataSourceId、databaseName、schemaName + let p: any = { + dataSourceId: '', + }; + //包含了dataSourceId、databaseName、schemaName (selectedOptions || []).forEach((o) => { if (o.type) { p[`${o.type}Name`] = o.value; diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 2750ecb9a..ccf330c55 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -79,6 +79,8 @@ function Chart(props: IProps) { }; const initCreateChart = async (dashboard?: IDashboardItem) => { + if(!dashboard) return; + let chartId = await createChart({}); const newDashboard = { ...dashboard, From a9445ef735059d5c16adc029efb259c778d2819c Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 15:29:11 +0800 Subject: [PATCH 0733/1069] Fix some team project bugs --- .../api/enums/EnvironmentStyleEnum.java | 34 ------ .../server/domain/api/model/Environment.java | 7 +- .../core/impl/DataSourceServiceImpl.java | 9 ++ .../domain/core/impl/TeamServiceImpl.java | 19 +++ .../domain/core/impl/UserServiceImpl.java | 19 +++ .../repository/entity/EnvironmentDO.java | 6 +- .../repository/mapper/EnvironmentMapper.java | 2 +- ...0\346\210\267\346\235\203\351\231\220.sql" | 10 +- .../test/mybatis/MybatisGeneratorTest.java | 2 +- .../request/TeamUserPageQueryRequest.java | 27 ----- .../controller/common/vo/TeamUserListVO.java | 50 -------- .../DataSourceAccessAdminConverter.java | 50 -------- .../converter/DataSourceAdminConverter.java | 76 ------------ .../DataSourceAccessBatchCreateRequest.java | 33 ------ .../DataSourceAccessObjectRequest.java | 41 ------- .../DataSourceAccessPageQueryRequest.java | 26 ----- .../request/DataSourceCloneRequest.java | 19 --- .../request/DataSourceCreateRequest.java | 106 ----------------- .../request/DataSourceUpdateRequest.java | 108 ------------------ .../vo/DataSourceAccessObjectVO.java | 50 -------- .../vo/DataSourceAccessPageQueryVO.java | 40 ------- .../datasource/vo/DataSourcePageQueryVO.java | 39 ------- .../datasource/vo/SimpleDataSourceVO.java | 39 ------- .../team/converter/TeamAdminConverter.java | 62 ---------- .../TeamDataSourcesAdminConverter.java | 56 --------- .../converter/TeamUserAdminConverter.java | 49 -------- .../team/request/TeamCreateRequest.java | 38 ------ .../TeamDataSourceBatchCreateRequest.java | 34 ------ .../request/TeamPageCommonQueryRequest.java | 25 ---- .../team/request/TeamUpdateRequest.java | 45 -------- .../request/TeamUserBatchCreateRequest.java | 33 ------ .../api/controller/team/vo/SimpleTeamVO.java | 37 ------ .../team/vo/TeamDataSourcePageQueryVO.java | 31 ----- .../controller/team/vo/TeamPageQueryVO.java | 57 --------- .../team/vo/TeamUserPageQueryVO.java | 28 ----- .../user/converter/UserAdminConverter.java | 57 --------- .../UserDataSourcesAdminConverter.java | 56 --------- .../converter/UserTeamAdminConverter.java | 38 ------ .../user/request/UserCreateRequest.java | 54 --------- .../UserDataSourceBatchCreateRequest.java | 32 ------ .../request/UserPageCommonQueryRequest.java | 25 ---- .../request/UserTeamBatchCreateRequest.java | 32 ------ .../user/request/UserUpdateRequest.java | 52 --------- .../api/controller/user/vo/SimpleUserVO.java | 39 ------- .../user/vo/UserDataSourcePageQueryVO.java | 31 ----- .../controller/user/vo/UserPageQueryVO.java | 73 ------------ .../user/vo/UserTeamPageQueryVO.java | 28 ----- .../controller/CommonCommonController.java | 42 ------- .../converter/EnvironmentCommonConverter.java | 27 ----- .../request/CommonPageQueryRequest.java | 20 ---- .../request/CommonQueryRequest.java | 18 --- .../controller/vo/SimpleEnvironmentVO.java | 48 -------- .../api/controller/vo/SimpleUserVO.java | 41 ------- .../controller/vo/SimpleEnvironmentVO.java | 7 +- 54 files changed, 61 insertions(+), 1966 deletions(-) delete mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java deleted file mode 100644 index 1c5fa6a40..000000000 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/EnvironmentStyleEnum.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.chat2db.server.domain.api.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; -import lombok.Getter; - -/** - * Environment - * - * @author Jiaju Zhuang - */ -@Getter -public enum EnvironmentStyleEnum implements BaseEnum { - /** - * RELEASE - */ - RELEASE("RELEASE"), - - /** - * TEST - */ - TEST("TEST"), - - ; - final String description; - - EnvironmentStyleEnum(String description) { - this.description = description; - } - - @Override - public String getCode() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java index ffb5971c5..a7732bc20 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/Environment.java @@ -3,7 +3,6 @@ import java.io.Serial; import java.io.Serializable; -import ai.chat2db.server.domain.api.enums.EnvironmentStyleEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; @@ -40,9 +39,7 @@ public class Environment implements Serializable { private String shortName; /** - * 样式类型 - * - * @see EnvironmentStyleEnum + * 颜色 */ - private String style; + private String color; } 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 0f275156e..9d99de061 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 @@ -18,7 +18,9 @@ import ai.chat2db.server.domain.core.converter.DataSourceConverter; import ai.chat2db.server.domain.core.converter.EnvironmentConverter; import ai.chat2db.server.domain.core.util.PermissionUtils; +import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.DataSourceDO; +import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceCustomMapper; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; @@ -75,6 +77,8 @@ public class DataSourceServiceImpl implements DataSourceService { private DataSourceCustomMapper dataSourceCustomMapper; @Resource private EnvironmentConverter environmentConverter; + @Resource + private DataSourceAccessMapper dataSourceAccessMapper; @Override public DataResult createWithPermission(DataSourceCreateParam param) { @@ -134,6 +138,11 @@ public ActionResult deleteWithPermission(Long id) { PermissionUtils.checkOperationPermission(dataSource.getUserId()); dataSourceMapper.deleteById(id); + + LambdaQueryWrapper dataSourceAccessQueryWrapper = new LambdaQueryWrapper<>(); + dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getDataSourceId, id) + ; + dataSourceAccessMapper.delete(dataSourceAccessQueryWrapper); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java index 8ca145162..a8eaa3316 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TeamServiceImpl.java @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.Team; import ai.chat2db.server.domain.api.param.team.TeamCreateParam; @@ -11,8 +12,12 @@ import ai.chat2db.server.domain.api.service.TeamService; import ai.chat2db.server.domain.core.converter.TeamConverter; import ai.chat2db.server.domain.core.converter.UserConverter; +import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.TeamDO; +import ai.chat2db.server.domain.repository.entity.TeamUserDO; +import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.TeamMapper; +import ai.chat2db.server.domain.repository.mapper.TeamUserMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -44,6 +49,10 @@ public class TeamServiceImpl implements TeamService { @Resource private TeamMapper teamMapper; @Resource + private TeamUserMapper teamUserMapper; + @Resource + private DataSourceAccessMapper dataSourceAccessMapper; + @Resource private TeamConverter teamConverter; @Resource private UserConverter userConverter; @@ -108,6 +117,16 @@ public DataResult update(TeamUpdateParam param) { @Override public ActionResult delete(Long id) { teamMapper.deleteById(id); + + LambdaQueryWrapper teamUserQueryWrapper = new LambdaQueryWrapper<>(); + teamUserQueryWrapper.eq(TeamUserDO::getTeamId, id); + teamUserMapper.delete(teamUserQueryWrapper); + + LambdaQueryWrapper dataSourceAccessQueryWrapper = new LambdaQueryWrapper<>(); + dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getAccessObjectId, id) + .eq(DataSourceAccessDO::getAccessObjectType, AccessObjectTypeEnum.TEAM.getCode()) + ; + dataSourceAccessMapper.delete(dataSourceAccessQueryWrapper); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java index b6c011b56..dd1f067b7 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/UserServiceImpl.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Objects; +import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.User; import ai.chat2db.server.domain.api.param.user.UserCreateParam; @@ -11,8 +12,12 @@ import ai.chat2db.server.domain.api.param.user.UserUpdateParam; import ai.chat2db.server.domain.api.service.UserService; import ai.chat2db.server.domain.core.converter.UserConverter; +import ai.chat2db.server.domain.repository.entity.DataSourceAccessDO; import ai.chat2db.server.domain.repository.entity.DbhubUserDO; +import ai.chat2db.server.domain.repository.entity.TeamUserDO; +import ai.chat2db.server.domain.repository.mapper.DataSourceAccessMapper; import ai.chat2db.server.domain.repository.mapper.DbhubUserMapper; +import ai.chat2db.server.domain.repository.mapper.TeamUserMapper; import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -45,6 +50,10 @@ public class UserServiceImpl implements UserService { private DbhubUserMapper dbhubUserMapper; @Resource private UserConverter userConverter; + @Resource + private TeamUserMapper teamUserMapper; + @Resource + private DataSourceAccessMapper dataSourceAccessMapper; @Override public DataResult query(Long id) { @@ -126,6 +135,16 @@ public ActionResult delete(Long id) { throw new BusinessException("user.canNotOperateSystemAccount"); } dbhubUserMapper.deleteById(id); + + LambdaQueryWrapper teamUserQueryWrapper = new LambdaQueryWrapper<>(); + teamUserQueryWrapper.eq(TeamUserDO::getUserId, id); + teamUserMapper.delete(teamUserQueryWrapper); + + LambdaQueryWrapper dataSourceAccessQueryWrapper = new LambdaQueryWrapper<>(); + dataSourceAccessQueryWrapper.eq(DataSourceAccessDO::getAccessObjectId, id) + .eq(DataSourceAccessDO::getAccessObjectType, AccessObjectTypeEnum.USER.getCode()) + ; + dataSourceAccessMapper.delete(dataSourceAccessQueryWrapper); return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java index 65508a413..a921bc834 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/EnvironmentDO.java @@ -14,7 +14,7 @@ *

* * @author chat2db - * @since 2023-08-26 + * @since 2023-09-09 */ @Getter @Setter @@ -60,7 +60,7 @@ public class EnvironmentDO implements Serializable { private String shortName; /** - * 样式类型 + * 颜色 */ - private String style; + private String color; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java index 9626ffb80..75469d31b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/EnvironmentMapper.java @@ -9,7 +9,7 @@ *

* * @author chat2db - * @since 2023-08-26 + * @since 2023-09-09 */ public interface EnvironmentMapper extends BaseMapper { diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" index 943f82af0..2fc9b6380 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -7,18 +7,18 @@ CREATE TABLE IF NOT EXISTS `environment` `modified_user_id` bigint(20) unsigned NOT NULL COMMENT '修改人用户id', `name` varchar(128) DEFAULT NOT NULL COMMENT '环境名称', `short_name` varchar(128) DEFAULT NULL COMMENT '环境缩写', - `style` varchar(32) DEFAULT NULL COMMENT '样式类型', + `color` varchar(32) DEFAULT NULL COMMENT '颜色', PRIMARY KEY (`id`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='数据库连接环境' ; INSERT INTO `environment` -(`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `style`) -VALUES (1, 1, 1, 'Release Environment', 'RELEASE', 'RELEASE'); +(`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `color`) +VALUES (1, 1, 1, 'Release Environment', 'RELEASE', 'RED'); INSERT INTO `environment` -(`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `style`) -VALUES (2, 1, 1, 'Test Environment', 'TEST', 'TEST'); +(`id`, `create_user_id`, `modified_user_id`, `name`, `short_name`, `color`) +VALUES (2, 1, 1, 'Test Environment', 'TEST', 'GREEN'); ALTER TABLE `data_source` ADD COLUMN `environment_id` bigint(20) unsigned NOT NULL DEFAULT 2 COMMENT '环境id'; diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index 4b11e3830..cc3acaf17 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -35,7 +35,7 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("operation_saved")); //doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access", // "dbhub_user")); - doGenerator(Lists.newArrayList("chart")); + doGenerator(Lists.newArrayList("environment")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java deleted file mode 100644 index 53a5c01af..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/request/TeamUserPageQueryRequest.java +++ /dev/null @@ -1,27 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.common.request; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; -import lombok.Data; - -/** - * Common pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class TeamUserPageQueryRequest extends PageQueryRequest { - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java deleted file mode 100644 index 7b2b55f25..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/common/vo/TeamUserListVO.java +++ /dev/null @@ -1,50 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.common.vo; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * DataSource Access Object - * It could be a user or a team - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TeamUserListVO implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long id; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - - /** - * The name of the code that belongs to the authorization type, such as user account, team code - */ - private String code; - - /** - * Code that belongs to the authorization type, such as user name, team name - */ - private String name; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java deleted file mode 100644 index f97e2098a..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAccessAdminConverter.java +++ /dev/null @@ -1,50 +0,0 @@ -package ai.chat2db.server.admin.api.controller.datasource.converter; - -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessPageQueryRequest; -import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourceAccessPageQueryVO; -import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; -import ai.chat2db.server.domain.api.model.DataSourceAccess; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class}) -public abstract class DataSourceAccessAdminConverter { - - /** - * convert - * - * @param request - * @return - */ - @Mappings({ - @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract DataSourceAccessComprehensivePageQueryParam request2param(DataSourceAccessPageQueryRequest request); - - /** - * convert - * - * @param request - * @return - */ - public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - public abstract DataSourceAccessPageQueryVO dto2vo(DataSourceAccess dto); - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java deleted file mode 100644 index 5236de606..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/converter/DataSourceAdminConverter.java +++ /dev/null @@ -1,76 +0,0 @@ -package ai.chat2db.server.admin.api.controller.datasource.converter; - -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceCreateRequest; -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceUpdateRequest; -import ai.chat2db.server.admin.api.controller.datasource.vo.DataSourcePageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; -import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; -import ai.chat2db.server.domain.api.model.DataSource; -import ai.chat2db.server.domain.api.param.datasource.DataSourceCreateParam; -import ai.chat2db.server.domain.api.param.datasource.DataSourcePageQueryParam; -import ai.chat2db.server.domain.api.param.datasource.DataSourceUpdateParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) -public abstract class DataSourceAdminConverter { - - /** - * conversion - * - * @param request - * @return - */ - @Mappings({ - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract DataSourcePageQueryParam request2param(CommonPageQueryRequest request); - - /** - * conversion - * - * @param request - * @return - */ - @Mappings({ - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract DataSourcePageQueryParam request2paramAccess(CommonPageQueryRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - public abstract DataSourcePageQueryVO dto2vo(DataSource dto); - - /** - * 参数转换 - * - * @param request - * @return - */ - @Mappings({ - @Mapping(source = "user", target = "userName"), - @Mapping(target = "kind", expression = "java(DataSourceKindEnum.SHARED.getCode())"), - }) - public abstract DataSourceCreateParam createReq2param(DataSourceCreateRequest request); - - /** - * 参数转换 - * - * @param request - * @return - */ - @Mappings({ - @Mapping(source = "user", target = "userName") - }) - public abstract DataSourceUpdateParam updateReq2param(DataSourceUpdateRequest request); -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java deleted file mode 100644 index 14d179409..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessBatchCreateRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.chat2db.server.admin.api.controller.datasource.request; - -import java.util.List; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessBatchCreateRequest { - - /** - * 数据源id - */ - @NotNull - private Long dataSourceId; - - /** - * DataSource Access Object - */ - @NotNull - private List accessObjectList; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java deleted file mode 100644 index e0bdf202e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessObjectRequest.java +++ /dev/null @@ -1,41 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.datasource.request; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * DataSource Access Object - * It could be a user or a team - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessObjectRequest implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long id; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java deleted file mode 100644 index f833609b6..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceAccessPageQueryRequest.java +++ /dev/null @@ -1,26 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.datasource.request; - -import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Common pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessPageQueryRequest extends PageQueryRequest { - - /** - * 数据源id - */ - @NotNull - private Long dataSourceId; - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java deleted file mode 100644 index 54386e61b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCloneRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package ai.chat2db.server.admin.api.controller.datasource.request; - - -import lombok.Data; - -/** - * @author moji - * @version ConnectionCloneRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ - * @date 2022/09/16 - */ -@Data -public class DataSourceCloneRequest { - - /** - * 主键id - */ - private Long id; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java deleted file mode 100644 index b8606a5e3..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceCreateRequest.java +++ /dev/null @@ -1,106 +0,0 @@ -package ai.chat2db.server.admin.api.controller.datasource.request; - -import java.util.List; - -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * @author moji - * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ - * @date 2022/09/16 - */ -@Data -public class DataSourceCreateRequest { - - /** - * 连接别名 - */ - private String alias; - - /** - * 连接地址 - */ - @NotNull - private String url; - - /** - * 连接用户名 - */ - private String user; - - /** - * 密码 - */ - @NotNull - private String password; - - /** - * 认证类型 - */ - private String authenticationType; - - /** - * 连接类型 - */ - @NotNull - private String type; - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - - /** - * jdbc版本 - */ - private String jdbc; - /** - * 扩展信息 - */ - private List extendInfo; - - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; - - /** - * 环境id - */ - @NotNull - private Long environmentId; - - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java deleted file mode 100644 index ae779838c..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/request/DataSourceUpdateRequest.java +++ /dev/null @@ -1,108 +0,0 @@ -package ai.chat2db.server.admin.api.controller.datasource.request; - -import java.util.List; - -import ai.chat2db.spi.config.DriverConfig; -import ai.chat2db.spi.model.KeyValue; -import ai.chat2db.spi.model.SSHInfo; -import ai.chat2db.spi.model.SSLInfo; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * @author moji - * @version ConnectionCreateRequest.java, v 0.1 2022年09月16日 14:23 moji Exp $ - * @date 2022/09/16 - */ -@Data -public class DataSourceUpdateRequest { - - /** - * 主键id - */ - @NotNull - private Long id; - - /** - * 连接别名 - */ - private String alias; - - /** - * 连接地址 - */ - private String url; - - /** - * 连接用户 - */ - private String user; - - /** - * 密码 - */ - private String password; - - /** - * 连接类型 - */ - private String type; - - /** - * 环境类型 - * - * @see EnvTypeEnum - */ - private String envType; - - /** - * host - */ - private String host; - - /** - * port - */ - private String port; - - /** - * ssh - */ - private SSHInfo ssh; - - /** - * ssh - */ - private SSLInfo ssl; - - /** - * sid - */ - private String sid; - - /** - * driver - */ - private String driver; - - /** - * jdbc版本 - */ - private String jdbc; - - /** - * 扩展信息 - */ - private List extendInfo; - - /** - * 驱动配置 - */ - private DriverConfig driverConfig; - - /** - * 环境id - */ - @NotNull - private Long environmentId; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java deleted file mode 100644 index 4d1ccb914..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessObjectVO.java +++ /dev/null @@ -1,50 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.datasource.vo; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * DataSource Access Object - * It could be a user or a team - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class DataSourceAccessObjectVO implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - private Long id; - - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - private String type; - - /** - * The name of the code that belongs to the authorization type, such as user account, team code - */ - private String code; - - /** - * Code that belongs to the authorization type, such as user name, team name - */ - private String name; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java deleted file mode 100644 index 6d2220dc6..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourceAccessPageQueryVO.java +++ /dev/null @@ -1,40 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.datasource.vo; - -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourceAccessPageQueryVO { - - /** - * 主键 - */ - @NotNull - private Long id; - /** - * 授权类型 - * - * @see AccessObjectTypeEnum - */ - @NotNull - private String accessObjectType; - - /** - * 授权id,根据类型区分是用户还是团队 - */ - @NotNull - private Long accessObjectId; - - /** - * 授权对象 - */ - @NotNull - private DataSourceAccessObjectVO accessObject; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java deleted file mode 100644 index b77956d66..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/DataSourcePageQueryVO.java +++ /dev/null @@ -1,39 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.datasource.vo; - -import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class DataSourcePageQueryVO { - - /** - * 主键id - */ - private Long id; - - /** - * 连接别名 - */ - private String alias; - - /** - * 连接地址 - */ - private String url; - - /** - * 环境id - */ - private Long environmentId; - - /** - * 环境 - */ - private SimpleEnvironmentVO environment; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java deleted file mode 100644 index 8ae1f9820..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/datasource/vo/SimpleDataSourceVO.java +++ /dev/null @@ -1,39 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.datasource.vo; - -import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; -import lombok.Data; - -/** - * Data Source - * - * @author Jiaju Zhuang - */ -@Data -public class SimpleDataSourceVO { - - /** - * 主键id - */ - private Long id; - - /** - * 连接别名 - */ - private String alias; - - /** - * 连接地址 - */ - private String url; - - /** - * 环境id - */ - private Long environmentId; - - /** - * 环境 - */ - private SimpleEnvironmentVO environment; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java deleted file mode 100644 index a6eaf129e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamAdminConverter.java +++ /dev/null @@ -1,62 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.converter; - -import ai.chat2db.server.admin.api.controller.team.request.TeamCreateRequest; -import ai.chat2db.server.admin.api.controller.team.request.TeamUpdateRequest; -import ai.chat2db.server.admin.api.controller.team.vo.TeamPageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; -import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; -import ai.chat2db.server.domain.api.model.Team; -import ai.chat2db.server.domain.api.param.team.TeamCreateParam; -import ai.chat2db.server.domain.api.param.team.TeamPageQueryParam; -import ai.chat2db.server.domain.api.param.team.TeamUpdateParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring",imports = {DataSourceKindEnum.class}) -public abstract class TeamAdminConverter { - - - /** - * conversion - * - * @param request - * @return - */ - @Mappings({ - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract TeamPageQueryParam request2param(CommonPageQueryRequest request); - - - /** - * conversion - * - * @param dto - * @return - */ - public abstract TeamPageQueryVO dto2vo(Team dto); - - - /** - * conversion - * - * @param request - * @return - */ - public abstract TeamCreateParam request2param(TeamCreateRequest request); - - - /** - * conversion - * - * @param request - * @return - */ - public abstract TeamUpdateParam request2param(TeamUpdateRequest request); -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java deleted file mode 100644 index 368041b67..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamDataSourcesAdminConverter.java +++ /dev/null @@ -1,56 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.converter; - -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; -import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; -import ai.chat2db.server.admin.api.controller.team.vo.TeamDataSourcePageQueryVO; -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; -import ai.chat2db.server.domain.api.model.DataSourceAccess; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) -public abstract class TeamDataSourcesAdminConverter { - - /** - * convert - * - * @param request - * @return - */ - @Mappings({ - @Mapping(target = "accessObjectId", source = "teamId"), - @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.TEAM.name())"), - @Mapping(source = "searchKey", target = "dataSourceSearchKey"), - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract DataSourceAccessComprehensivePageQueryParam request2param(TeamPageCommonQueryRequest request); - - /** - * convert - * - * @param request - * @return - */ - public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - @Mappings({ - @Mapping(target = "teamId", source = "accessObjectId"), - }) - public abstract TeamDataSourcePageQueryVO dto2vo(DataSourceAccess dto); - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java deleted file mode 100644 index 42dc9f4e5..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/converter/TeamUserAdminConverter.java +++ /dev/null @@ -1,49 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.converter; - -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; -import ai.chat2db.server.admin.api.controller.team.request.TeamPageCommonQueryRequest; -import ai.chat2db.server.admin.api.controller.team.vo.TeamUserPageQueryVO; -import ai.chat2db.server.domain.api.model.TeamUser; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; -import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring") -public abstract class TeamUserAdminConverter { - - /** - * convert - * - * @param request - * @return - */ - @Mappings({ - @Mapping(source = "searchKey", target = "userSearchKey"), - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract TeamUserComprehensivePageQueryParam request2param(TeamPageCommonQueryRequest request); - - /** - * convert - * - * @param request - * @return - */ - public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - public abstract TeamUserPageQueryVO dto2vo(TeamUser dto); - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java deleted file mode 100644 index 1b3bcf9a2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamCreateRequest.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.request; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -public class TeamCreateRequest { - - /** - * 团队编码 - */ - @NotNull - private String code; - - /** - * 团队名称 - */ - @NotNull - private String name; - - /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum - */ - @NotNull - private String status; - - /** - * 团队描述 - */ - private String description; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java deleted file mode 100644 index 58a139929..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamDataSourceBatchCreateRequest.java +++ /dev/null @@ -1,34 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.request; - -import java.util.List; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TeamDataSourceBatchCreateRequest { - - /** - * team id - */ - @NotNull - private Long teamId; - - - /** - * Data Source id list - */ - @NotNull - private List dataSourceIdList; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java deleted file mode 100644 index 448628157..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamPageCommonQueryRequest.java +++ /dev/null @@ -1,25 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.team.request; - -import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class TeamPageCommonQueryRequest extends PageQueryRequest { - /** - * team id - */ - @NotNull - private Long teamId; - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java deleted file mode 100644 index 623b517d6..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUpdateRequest.java +++ /dev/null @@ -1,45 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.request; - -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * update - * - * @author Jiaju Zhuang - */ -@Data -public class TeamUpdateRequest { - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 团队名称 - */ - @NotNull - private String name; - - /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum - */ - @NotNull - private String status; - - /** - * 角色编码 - * - * @see ai.chat2db.server.domain.api.enums.RoleCodeEnum - */ - @NotNull - private String roleCode; - - /** - * 团队描述 - */ - private String description; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java deleted file mode 100644 index 6c983bcf9..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/request/TeamUserBatchCreateRequest.java +++ /dev/null @@ -1,33 +0,0 @@ -package ai.chat2db.server.admin.api.controller.team.request; - -import java.util.List; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class TeamUserBatchCreateRequest { - - /** - * team id - */ - @NotNull - private Long teamId; - - /** - * user id list - */ - @NotNull - private List userIdList; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java deleted file mode 100644 index f6b0bf5f2..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/SimpleTeamVO.java +++ /dev/null @@ -1,37 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.team.vo; - -import lombok.Data; - -/** - * team - * - * @author Jiaju Zhuang - */ -@Data -public class SimpleTeamVO { - - /** - * 主键 - */ - private Long id; - - /** - * 团队编码 - */ - private String code; - - /** - * 团队名称 - */ - private String name; - - - /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum - */ - private String status; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java deleted file mode 100644 index 7caa47de1..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamDataSourcePageQueryVO.java +++ /dev/null @@ -1,31 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.team.vo; - -import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class TeamDataSourcePageQueryVO { - - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * team id - */ - private Long teamId; - - /** - * Data Source - */ - private SimpleDataSourceVO dataSource; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java deleted file mode 100644 index 75e2b9a51..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamPageQueryVO.java +++ /dev/null @@ -1,57 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.team.vo; - -import java.util.Date; - -import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class TeamPageQueryVO { - /** - * 主键 - */ - private Long id; - - /** - * 团队编码 - */ - private String code; - - /** - * 团队名称 - */ - private String name; - - /** - * 团队状态 - * - * @see ai.chat2db.server.domain.api.enums.ValidStatusEnum - */ - private String status; - - /** - * 团队描述 - */ - private String description; - - /** - * 修改时间 - */ - private Date gmtModified; - - /** - * 修改人用户id - */ - private Long modifiedUserId; - - /** - * 修改人用户 - */ - private SimpleUserVO modifiedUser; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java deleted file mode 100644 index a5520a23e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/team/vo/TeamUserPageQueryVO.java +++ /dev/null @@ -1,28 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.team.vo; - -import ai.chat2db.server.admin.api.controller.user.vo.SimpleUserVO; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class TeamUserPageQueryVO { - /** - * 主键 - */ - private Long id; - - /** - * team id - */ - private Long teamId; - - /** - * user - */ - private SimpleUserVO user; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java deleted file mode 100644 index 28bc43b69..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserAdminConverter.java +++ /dev/null @@ -1,57 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.converter; - -import ai.chat2db.server.admin.api.controller.user.request.UserCreateRequest; -import ai.chat2db.server.admin.api.controller.user.request.UserUpdateRequest; -import ai.chat2db.server.admin.api.controller.user.vo.UserPageQueryVO; -import ai.chat2db.server.common.api.controller.request.CommonPageQueryRequest; -import ai.chat2db.server.domain.api.model.User; -import ai.chat2db.server.domain.api.param.user.UserCreateParam; -import ai.chat2db.server.domain.api.param.user.UserPageQueryParam; -import ai.chat2db.server.domain.api.param.user.UserUpdateParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring") -public abstract class UserAdminConverter { - - /** - * conversion - * - * @param request - * @return - */ - @Mappings({ - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract UserPageQueryParam request2param(CommonPageQueryRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - public abstract UserPageQueryVO dto2vo(User dto); - - /** - * conversion - * - * @param request - * @return - */ - public abstract UserCreateParam request2param(UserCreateRequest request); - - /** - * conversion - * - * @param request - * @return - */ - public abstract UserUpdateParam request2param(UserUpdateRequest request); -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java deleted file mode 100644 index 1de4822bd..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserDataSourcesAdminConverter.java +++ /dev/null @@ -1,56 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.converter; - -import ai.chat2db.server.admin.api.controller.datasource.request.DataSourceAccessBatchCreateRequest; -import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; -import ai.chat2db.server.admin.api.controller.user.vo.UserDataSourcePageQueryVO; -import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; -import ai.chat2db.server.domain.api.model.DataSourceAccess; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessBatchCreatParam; -import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessComprehensivePageQueryParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring", imports = {DataSourceKindEnum.class, AccessObjectTypeEnum.class}) -public abstract class UserDataSourcesAdminConverter { - - /** - * convert - * - * @param request - * @return - */ - @Mappings({ - @Mapping(source = "userId", target = "accessObjectId"), - @Mapping(target = "accessObjectType", expression = "java(AccessObjectTypeEnum.USER.name())"), - @Mapping(source = "searchKey", target = "userOrTeamSearchKey"), - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract DataSourceAccessComprehensivePageQueryParam request2param(UserPageCommonQueryRequest request); - - /** - * convert - * - * @param request - * @return - */ - public abstract DataSourceAccessBatchCreatParam request2param(DataSourceAccessBatchCreateRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - @Mappings({ - @Mapping(target = "userId", source = "accessObjectId"), - }) - public abstract UserDataSourcePageQueryVO dto2vo(DataSourceAccess dto); - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java deleted file mode 100644 index 77424f29d..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/converter/UserTeamAdminConverter.java +++ /dev/null @@ -1,38 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.converter; - -import ai.chat2db.server.admin.api.controller.user.request.UserPageCommonQueryRequest; -import ai.chat2db.server.admin.api.controller.user.vo.UserTeamPageQueryVO; -import ai.chat2db.server.domain.api.model.TeamUser; -import ai.chat2db.server.domain.api.param.team.user.TeamUserComprehensivePageQueryParam; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.Mappings; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Mapper(componentModel = "spring") -public abstract class UserTeamAdminConverter { - - /** - * convert - * - * @param request - * @return - */ - @Mappings({ - @Mapping(source = "searchKey", target = "teamSearchKey"), - @Mapping(target = "enableReturnCount", expression = "java(true)"), - }) - public abstract TeamUserComprehensivePageQueryParam request2param(UserPageCommonQueryRequest request); - - /** - * conversion - * - * @param dto - * @return - */ - public abstract UserTeamPageQueryVO dto2vo(TeamUser dto); -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java deleted file mode 100644 index 4e2d1cd78..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserCreateRequest.java +++ /dev/null @@ -1,54 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.request; - -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; -import ai.chat2db.server.domain.api.enums.ValidStatusEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * create - *@author Jiaju Zhuang - */ -@Data -public class UserCreateRequest { - /** - * 用户名 - */ - @NotNull - private String userName; - - /** - * 密码 - */ - @NotNull - private String password; - - /** - * 昵称 - */ - @NotNull - private String nickName; - - /** - * 邮箱 - */ - @NotNull - private String email; - - - /** - * 角色编码 - * - * @see RoleCodeEnum - */ - @NotNull - private String roleCode; - - /** - * 用户状态 - * - * @see ValidStatusEnum - */ - @NotNull - private String status; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java deleted file mode 100644 index b396bb722..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserDataSourceBatchCreateRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.request; - -import java.util.List; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class UserDataSourceBatchCreateRequest { - - /** - * user id - */ - private Long userId; - - /** - * Data Source id list - */ - @NotNull - private List dataSourceIdList; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java deleted file mode 100644 index c0e79a32c..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserPageCommonQueryRequest.java +++ /dev/null @@ -1,25 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.request; - -import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class UserPageCommonQueryRequest extends PageQueryRequest { - /** - * user id - */ - @NotNull - private Long userId; - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java deleted file mode 100644 index 831bee3ac..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserTeamBatchCreateRequest.java +++ /dev/null @@ -1,32 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.request; - -import java.util.List; - -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * create - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class UserTeamBatchCreateRequest { - - /** - * user id - */ - private Long userId; - - /** - * team id list - */ - @NotNull - private List teamIdList; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java deleted file mode 100644 index 382826b0b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/request/UserUpdateRequest.java +++ /dev/null @@ -1,52 +0,0 @@ -package ai.chat2db.server.admin.api.controller.user.request; - -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; -import ai.chat2db.server.domain.api.enums.ValidStatusEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * create - *@author Jiaju Zhuang - */ -@Data -public class UserUpdateRequest { - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 密码 - */ - private String password; - - /** - * 昵称 - */ - @NotNull - private String nickName; - - /** - * 邮箱 - */ - @NotNull - private String email; - - - /** - * 角色编码 - * - * @see RoleCodeEnum - */ - private String roleCode; - - /** - * 用户状态 - * - * @see ValidStatusEnum - */ - @NotNull - private String status; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java deleted file mode 100644 index 8b8f88071..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/SimpleUserVO.java +++ /dev/null @@ -1,39 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.vo; - -import ai.chat2db.server.domain.api.enums.ValidStatusEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * user - * - * @author Jiaju Zhuang - */ -@Data -public class SimpleUserVO { - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 用户名 - */ - @NotNull - private String userName; - - /** - * 昵称 - */ - @NotNull - private String nickName; - - /** - * 用户状态 - * - * @see ValidStatusEnum - */ - private String status; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java deleted file mode 100644 index 0281b318e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserDataSourcePageQueryVO.java +++ /dev/null @@ -1,31 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.vo; - -import ai.chat2db.server.admin.api.controller.datasource.vo.SimpleDataSourceVO; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class UserDataSourcePageQueryVO { - - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * user id - */ - private Long userId; - - /** - * Data Source - */ - private SimpleDataSourceVO dataSource; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java deleted file mode 100644 index 5ff1abaf7..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserPageQueryVO.java +++ /dev/null @@ -1,73 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.vo; - -import java.util.Date; - -import ai.chat2db.server.common.api.controller.vo.SimpleUserVO; -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; -import ai.chat2db.server.domain.api.enums.ValidStatusEnum; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class UserPageQueryVO { - /** - * 主键 - */ - @NotNull - private Long id; - - /** - * 用户名 - */ - @NotNull - private String userName; - - /** - * 昵称 - */ - @NotNull - private String nickName; - - /** - * 用户状态 - * - * @see ValidStatusEnum - */ - private String status; - - /** - * 邮箱 - */ - @NotNull - private String email; - - /** - * 角色编码 - * - * @see RoleCodeEnum - */ - private String roleCode; - - - /** - * 修改时间 - */ - private Date gmtModified; - - /** - * 修改人用户id - */ - private Long modifiedUserId; - - /** - * 修改人用户 - */ - private SimpleUserVO modifiedUser; - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java b/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java deleted file mode 100644 index 56424c24b..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-admin-api/src 2/main/java/ai/chat2db/server/admin/api/controller/user/vo/UserTeamPageQueryVO.java +++ /dev/null @@ -1,28 +0,0 @@ - -package ai.chat2db.server.admin.api.controller.user.vo; - -import ai.chat2db.server.admin.api.controller.team.vo.SimpleTeamVO; -import lombok.Data; - -/** - * Pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class UserTeamPageQueryVO { - /** - * 主键 - */ - private Long id; - - /** - * user id - */ - private Long userId; - - /** - * 团队 - */ - private SimpleTeamVO team; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java deleted file mode 100644 index 327a2aab6..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/CommonCommonController.java +++ /dev/null @@ -1,42 +0,0 @@ - -package ai.chat2db.server.common.api.controller; - -import ai.chat2db.server.common.api.controller.converter.EnvironmentCommonConverter; -import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; -import ai.chat2db.server.domain.api.param.EnvironmentPageQueryParam; -import ai.chat2db.server.domain.api.service.EnvironmentService; -import ai.chat2db.server.tools.base.wrapper.result.ListResult; -import jakarta.annotation.Resource; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -/** - * Basic interface - * - * @author Jiaju Zhuang - */ -@RequestMapping("/api/common") -@RestController -public class CommonCommonController { - - @Resource - private EnvironmentService environmentService; - @Resource - private EnvironmentCommonConverter environmentCommonConverter; - - /** - * Query all environments - * - * @return - * @version 2.1.0 - */ - @GetMapping("/environment/list_all") - public ListResult environmentList() { - EnvironmentPageQueryParam environmentPageQueryParam = new EnvironmentPageQueryParam(); - environmentPageQueryParam.setPageSize(Integer.MIN_VALUE); - return ListResult.of( - environmentCommonConverter.dto2vo(environmentService.pageQuery(environmentPageQueryParam).getData())); - } - -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java deleted file mode 100644 index 74978830f..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/converter/EnvironmentCommonConverter.java +++ /dev/null @@ -1,27 +0,0 @@ -package ai.chat2db.server.common.api.controller.converter; - -import java.util.List; - -import ai.chat2db.server.common.api.controller.vo.SimpleEnvironmentVO; -import ai.chat2db.server.domain.api.model.Environment; -import lombok.extern.slf4j.Slf4j; -import org.mapstruct.Mapper; - -/** - * converter - * - * @author Jiaju Zhuang - */ -@Slf4j -@Mapper(componentModel = "spring") -public abstract class EnvironmentCommonConverter { - - - /** - * convert - * - * @param list - * @return - */ - public abstract List dto2vo(List list); -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java deleted file mode 100644 index 87c58b294..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonPageQueryRequest.java +++ /dev/null @@ -1,20 +0,0 @@ - -package ai.chat2db.server.common.api.controller.request; - -import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; -import lombok.Data; - -/** - * Common pagination query - * - * @author Jiaju Zhuang - */ -@Data -public class CommonPageQueryRequest extends PageQueryRequest { - - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java deleted file mode 100644 index 048b6c23e..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/request/CommonQueryRequest.java +++ /dev/null @@ -1,18 +0,0 @@ - -package ai.chat2db.server.common.api.controller.request; - -import lombok.Data; - -/** - * Common query - * - * @author Jiaju Zhuang - */ -@Data -public class CommonQueryRequest { - - /** - * searchKey - */ - private String searchKey; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java deleted file mode 100644 index 4c9efe9df..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java +++ /dev/null @@ -1,48 +0,0 @@ -package ai.chat2db.server.common.api.controller.vo; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.domain.api.enums.EnvironmentStyleEnum; -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * Environment - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class SimpleEnvironmentVO implements Serializable { - - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 主键 - */ - private Long id; - - /** - * 环境名称 - */ - private String name; - - /** - * 环境缩写 - */ - private String shortName; - - /** - * 样式类型 - * - * @see EnvironmentStyleEnum - */ - private String style; -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java deleted file mode 100644 index d9c67b84d..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src 2/main/java/ai/chat2db/server/common/api/controller/vo/SimpleUserVO.java +++ /dev/null @@ -1,41 +0,0 @@ - -package ai.chat2db.server.common.api.controller.vo; - -import java.io.Serial; -import java.io.Serializable; - -import ai.chat2db.server.tools.base.constant.EasyToolsConstant; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; - -/** - * user - * - * @author Jiaju Zhuang - */ -@Data -@SuperBuilder -@NoArgsConstructor -@AllArgsConstructor -public class -SimpleUserVO implements Serializable { - @Serial - private static final long serialVersionUID = EasyToolsConstant.SERIAL_VERSION_UID; - - /** - * 主键 - */ - private Long id; - - /** - * 用户名 - */ - private String userName; - - /** - * 昵称 - */ - private String nickName; -} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java index 4c9efe9df..6ee9e7334 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-common-api/src/main/java/ai/chat2db/server/common/api/controller/vo/SimpleEnvironmentVO.java @@ -3,7 +3,6 @@ import java.io.Serial; import java.io.Serializable; -import ai.chat2db.server.domain.api.enums.EnvironmentStyleEnum; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import lombok.AllArgsConstructor; import lombok.Data; @@ -40,9 +39,7 @@ public class SimpleEnvironmentVO implements Serializable { private String shortName; /** - * 样式类型 - * - * @see EnvironmentStyleEnum + * 颜色 */ - private String style; + private String color; } From 11e890f1c7888d0e613826fc9aba9e4d1bff151c Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 15:43:10 +0800 Subject: [PATCH 0734/1069] Fix some team project bugs --- .../operation/OperationLogPageQueryParam.java | 5 +++++ .../domain/core/impl/OperationLogServiceImpl.java | 15 ++++++++------- ...4\250\346\210\267\346\235\203\351\231\220.sql" | 6 ++++++ .../operation/log/OperationLogController.java | 2 ++ 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java index f5092bae1..5b2c01f45 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/operation/OperationLogPageQueryParam.java @@ -12,6 +12,11 @@ @Data public class OperationLogPageQueryParam extends PageQueryParam { + /** + * 用户id + */ + private Long userId; + /** * 搜索关键词 */ diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java index c3cbc1edd..eb7fdaa75 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/OperationLogServiceImpl.java @@ -18,12 +18,12 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; - -import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import ai.chat2db.server.tools.common.model.EasyLambdaQueryWrapper; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.tools.common.util.EasySqlUtils; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -49,16 +49,17 @@ public DataResult create(OperationLogCreateParam param) { OperationLogDO userExecutedDdlDO = operationLogConverter.param2do(param); userExecutedDdlDO.setGmtCreate(LocalDateTime.now()); userExecutedDdlDO.setGmtModified(LocalDateTime.now()); + userExecutedDdlDO.setUserId(ContextUtils.getUserId()); operationLogMapper.insert(userExecutedDdlDO); return DataResult.of(userExecutedDdlDO.getId()); } @Override public PageResult queryPage(OperationLogPageQueryParam param) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - if (StringUtils.isNotBlank(param.getSearchKey())) { - queryWrapper.like("ddl", param.getSearchKey()); - } + EasyLambdaQueryWrapper queryWrapper = new EasyLambdaQueryWrapper<>(); + queryWrapper.likeWhenPresent(OperationLogDO::getDdl, EasySqlUtils.buildLikeRightFuzzy(param.getSearchKey())) + .eqWhenPresent(OperationLogDO::getUserId, param.getUserId()) + ; Integer start = param.getPageNo(); Integer offset = param.getPageSize(); Page page = new Page<>(start, offset); diff --git "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" index 2fc9b6380..e7b895b69 100644 --- "a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" +++ "b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_0__\346\224\257\346\214\201\347\216\257\345\242\203\343\200\201\347\224\250\346\210\267\346\235\203\351\231\220.sql" @@ -114,6 +114,12 @@ ALTER TABLE `operation_saved` update operation_saved set user_id= 1; +ALTER TABLE `operation_log` + modify COLUMN `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; + +update operation_log +set user_id= 1; + ALTER TABLE `dashboard` modify `user_id` bigint(20) unsigned NOT NULL DEFAULT 1 COMMENT '用户id'; update dashboard diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java index ce0edefe1..5760a647f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/operation/log/OperationLogController.java @@ -9,6 +9,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.operation.log.converter.OperationLogWebConverter; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogCreateRequest; import ai.chat2db.server.web.api.controller.operation.log.request.OperationLogQueryRequest; @@ -47,6 +48,7 @@ public class OperationLogController { @GetMapping("/list") public WebPageResult list(OperationLogQueryRequest request) { OperationLogPageQueryParam param = operationLogWebConverter.req2param(request); + param.setUserId(ContextUtils.getUserId()); PageResult result = operationLogService.queryPage(param); List operationLogVOList = operationLogWebConverter.dto2vo(result.getData()); return WebPageResult.of(operationLogVOList, result.getTotal(), result.getPageNo(), result.getPageSize()); From b87096e4a061131d35cf06ed6c497fde9ce1c8f0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 9 Sep 2023 15:45:26 +0800 Subject: [PATCH 0735/1069] fix: optimize UI --- chat2db-client/src/blocks/Setting/AiSetting/index.tsx | 8 ++++---- chat2db-client/src/i18n/en-us/setting.ts | 3 ++- chat2db-client/src/i18n/zh-cn/setting.ts | 2 +- chat2db-client/src/pages/login/index.tsx | 2 +- chat2db-client/src/pages/main/index.tsx | 4 +++- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 56c27a4c0..c59f93006 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -1,12 +1,12 @@ import React, { useEffect, useState } from 'react'; import configService from '@/service/config'; import { AiSqlSourceType } from '@/typings/ai'; -import { Button, Input, Radio } from 'antd'; +import { Alert, Button, Input, Radio, RadioChangeEvent } from 'antd'; import i18n from '@/i18n'; import classnames from 'classnames'; import { IAiConfig } from '@/typings/setting'; import styles from './index.less'; -import Popularize from '@/components/Popularize'; + interface IProps { handleApplyAiConfig: (aiConfig: IAiConfig) => void; aiConfig: IAiConfig; @@ -17,14 +17,14 @@ export default function SettingAI(props: IProps) { const [aiConfig, setAiConfig] = useState(props?.aiConfig); if (!aiConfig) { - return null; + return ; } useEffect(() => { setAiConfig(props.aiConfig); }, [props.aiConfig]); - const handleAiTypeChange = async (e) => { + const handleAiTypeChange = async (e: RadioChangeEvent) => { const aiSqlSource = e.target.value; // 查询对应ai类型的配置 diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index aeced01d4..5f36202dd 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -32,4 +32,5 @@ export default { 'setting.placeholder.azureOpenAIKey': 'Get Azure OpenAI key credential from the Azure Portal', 'setting.placeholder.azureEndpoint': 'Get Azure OpenAI endpoint from the Azure Portal', 'setting.placeholder.azureDeployment': 'Deployment id of the deployed model', -} + 'setting.ai.tips': 'Please log in and select AI configuration', +}; diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 22c879163..88f9caac4 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -32,5 +32,5 @@ export default { 'setting.placeholder.azureOpenAIKey': '从Azure门户获取Azure OpenAI密钥凭证', 'setting.placeholder.azureEndpoint': '从Azure门户获取Azure OpenA端口', 'setting.placeholder.azureDeployment': '部署模型的部署id', - + 'setting.ai.tips': '请登录后选择AI配置', } \ No newline at end of file diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 3394e2e56..828e01efa 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -17,7 +17,7 @@ const App: React.FC = () => { const handleLogin = async (formData: { userName: string; password: string }) => { let res = await userLogin(formData); if (res) { - window.location.href = '/'; + window.location.href = location.origin; } }; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 738b4afce..76dea5ad1 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -81,7 +81,9 @@ function MainPage(props: IProps) { isLoad: false, component: , }); - setActiveNav(navConfig[3]); + if (localStorage.getItem('curPage') === 'team') { + setActiveNav(navConfig[3]); + } } } }); From e1b2b22dc7ae6ccc8943595dd4f8f9377648a514 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 9 Sep 2023 15:50:40 +0800 Subject: [PATCH 0736/1069] feat: get baseInfo --- .../DatabaseTableEditor/BaseInfo/index.less | 5 +- .../DatabaseTableEditor/BaseInfo/index.tsx | 78 ++++----- .../DatabaseTableEditor/ColumnList/index.less | 13 +- .../DatabaseTableEditor/ColumnList/index.tsx | 148 +++++++++--------- .../DatabaseTableEditor/IndexList/index.less | 3 +- .../DatabaseTableEditor/IndexList/index.tsx | 2 +- .../src/blocks/DatabaseTableEditor/index.less | 39 ++--- .../src/blocks/DatabaseTableEditor/index.tsx | 134 ++++++++++------ chat2db-client/src/pages/demo/index.tsx | 2 - .../components/WorkspaceHeader/index.tsx | 2 +- .../components/WorkspaceRight/index.tsx | 8 +- chat2db-client/src/service/sql.ts | 23 ++- chat2db-client/src/styles/global.less | 43 +++-- chat2db-client/src/typings/database.ts | 36 +++++ 14 files changed, 325 insertions(+), 211 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less index 1f40d282c..b33787deb 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less @@ -1,8 +1,9 @@ @import '../../../styles/var.less'; .box { + padding: 10px; } -.formBox{ +.formBox { width: 50%; -} \ No newline at end of file +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index 61a348a54..8d84aa0b0 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -1,50 +1,56 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useState, useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef } from 'react'; import styles from './index.less'; import classnames from 'classnames'; -import { - Form, - Input, -} from 'antd'; +import { Form, Input } from 'antd'; +import { Context } from '../index'; + +export interface IBaseInfoRef { + getBaseInfo: () => IBaseInfo; +} interface IProps { className?: string; } -export const basicInfo = { - data: {} +export interface IBaseInfo { + name: string; + comment: string; } -export default memo(function BaseInfo({ className }) { +const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => { + const { className } = props; + const { tableDetails } = useContext(Context); const [form] = Form.useForm(); - function onChangeForm(type: string) { - basicInfo.data = { - ...form.getFieldsValue() - } - console.log(basicInfo) + useEffect(() => { + form.setFieldsValue({ + name: tableDetails.name, + comment: tableDetails.comment, + }); + }, [tableDetails]); + + function getBaseInfo(): IBaseInfo { + return form.getFieldsValue(); } - return
-
-
- - { onChangeForm('name') }} /> - - - { onChangeForm('comment') }} /> - - + useImperativeHandle(ref, () => ({ + getBaseInfo, + })); + + return ( +
+
+
+ + + + + + + +
-
-}) + ); +}); + +export default BaseInfo diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less index 72f156809..83b19addd 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less @@ -1,16 +1,25 @@ @import '../../../styles/var.less'; .box { - height: 500px; + height: 100%; + padding: 10px; + box-sizing: border-box; + display: flex; + flex-direction: column; } .columnListHeader { - margin: 0px -10px 20px; + margin: 0px -10px 10px; button { margin: 0px 10px; } } +.tableBox { + flex: 1; + overflow: auto; +} + .editableCell { height: 28px; line-height: 28px; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 9b86d02d2..5e327b91a 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,10 +1,12 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useContext, useEffect, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; import type { DragEndEvent } from '@dnd-kit/core'; import { DndContext } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; +import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd'; +import { v4 as uuidv4 } from 'uuid'; import { arrayMove, SortableContext, @@ -12,59 +14,17 @@ import { verticalListSortingStrategy, } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd'; -import { v4 as uuidv4 } from 'uuid' - -interface IProps { - className?: string; -} - -// 数据库字段类型 枚举 -enum DatabaseFieldType { - // 数字 - Number = 'number', - // 字符串 - String = 'string', - // 日期 - Date = 'date', - // 布尔 - Boolean = 'boolean', - // 二进制 - Binary = 'binary', - // 对象 - Object = 'object', -} - -const databaseFieldTypeList = [DatabaseFieldType['Number'], DatabaseFieldType['String'], DatabaseFieldType['Date'], DatabaseFieldType['Boolean'], DatabaseFieldType['Binary'], DatabaseFieldType['Object']] +import sqlService from '@/service/sql'; +import { Context } from '../index' interface Item { key: string; columnName: string; length: number | null; - fieldType: DatabaseFieldType; + fieldType: string | null; + nullable: boolean; } -const mockData: Item[] = [ - { - key: uuidv4(), - columnName: 'John Brown', - length: 32, - fieldType: DatabaseFieldType.Binary, - }, - { - key: uuidv4(), - columnName: 'Jim Green', - length: 42, - fieldType: DatabaseFieldType.Number, - }, - { - key: uuidv4(), - columnName: 'Joe Black', - length: 32, - fieldType: DatabaseFieldType.String, - }, -] - interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } @@ -109,10 +69,12 @@ const Row = ({ children, ...props }: RowProps) => { ); }; -const ColumnList: React.FC = () => { - const [dataSource, setDataSource] = useState(mockData); +const ColumnList = memo(() => { + const { dataSourceId, databaseName, tableDetails } = useContext(Context); + const [dataSource, setDataSource] = useState([]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(''); + const [databaseFieldTypeList, setDatabaseFieldTypeList] = useState([]) const isEditing = (record: Item) => record.key === editingKey; @@ -121,6 +83,31 @@ const ColumnList: React.FC = () => { setEditingKey(record.key); }; + useEffect(() => { + if (tableDetails) { + const list = tableDetails?.columnList?.map(t => { + return { + key: uuidv4(), + columnName: t.name, + length: t.dataType, + fieldType: t.columnType, + nullable: t.nullable === 0, + comment: t.comment, + } + }) || [] + setDataSource(list) + } + }, [tableDetails]) + + useEffect(() => { + sqlService.getDatabaseFieldTypeList({ + dataSourceId, + databaseName, + }).then(res => { + setDatabaseFieldTypeList(res.map(i => i.typeName)) + }) + }, []) + const columns = [ { key: 'sort', @@ -200,23 +187,24 @@ const ColumnList: React.FC = () => { { title: 'nullable', dataIndex: 'nullable', - render: (text: string, record: Item) => { + width: '100px', + render: (text: boolean, record: Item) => { return - + } }, { - title: 'annotation', - dataIndex: 'annotation', + title: 'comment', + dataIndex: 'comment', render: (text: string, record: Item) => { const editable = isEditing(record); return editable ? ( @@ -261,7 +249,8 @@ const ColumnList: React.FC = () => { key: uuidv4(), columnName: '', length: null, - fieldType: DatabaseFieldType.String, + fieldType: null, + nullable: false, } setDataSource([...dataSource, newData]) edit(newData) @@ -296,35 +285,38 @@ const ColumnList: React.FC = () => { } return ( -
+
-
- - i.key)} - strategy={verticalListSortingStrategy} - > -
- - - +
+
+ + i.key)} + strategy={verticalListSortingStrategy} + > +
+ + + + ); -}; +}) + export default ColumnList; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less index 1e7e1eeeb..d9165efa7 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less @@ -1,10 +1,11 @@ @import '../../../styles/var.less'; .box { + padding: 10px; } .indexListHeader { - margin: 0px -10px 20px; + margin: 0px -10px 10px; button { margin: 0px 10px; } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index c74049159..12e66e032 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -14,7 +14,7 @@ import { import { CSS } from '@dnd-kit/utilities'; import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal } from 'antd'; import { v4 as uuidv4 } from 'uuid'; -import IncludeCol from '../IncludeCol' +import IncludeCol from '../IncludeCol'; interface IProps { className?: string; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index 85071c53f..c13537a2f 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -1,35 +1,40 @@ @import '../../styles/var.less'; .box { - height: 500px; - padding: 20px; + height: 100%; display: flex; flex-direction: column; } -.tabList{ +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin: 10px 10px 0px 10px; + box-sizing: border-box; +} + +.tabList { display: flex; border-bottom: 0; - margin-right: 20px; font-size: 14px; - margin-bottom: 20px; } -.tabItem{ +.tabItem { position: relative; - height: 38px; - line-height: 38px; - padding:0px 18px; + height: 28px; + line-height: 28px; + padding: 0px 18px; border: 1px solid var(--color-border); margin-right: 1px; cursor: pointer; } -.currentTab{ +.currentTab { font-weight: 500; color: var(--color-primary); - &::before{ + &::before { content: ''; position: absolute; left: 0; @@ -40,15 +45,13 @@ } } - - -.main{ - height: 500px; - overflow: hidden; +.main { + flex: 1; position: relative; + overflow: hidden; } -.tab{ +.tab { z-index: 2; position: absolute; left: 0px; @@ -57,7 +60,7 @@ height: 100%; } -.hidden{ +.hidden { z-index: 1; opacity: 0; } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 97bab9bd5..cfea95f01 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -1,12 +1,18 @@ -import React, { memo, useState } from 'react'; +import React, { memo, useRef, useState, createContext, useEffect, forwardRef, useMemo } from 'react'; +import { Button, Form } from 'antd'; import styles from './index.less'; import classnames from 'classnames'; import IndexList from './IndexList'; import ColumnList from './ColumnList'; -import BaseInfo from './BaseInfo'; +import BaseInfo, { IBaseInfoRef } from './BaseInfo'; +import sqlService from '@/service/sql'; +import { IDatabaseTableDetail } from '@/typings'; interface IProps { - className?: string; + dataSourceId: number, + databaseName: string, + schemaName: string | undefined, + tableName: string } interface ITabItem { @@ -15,54 +21,92 @@ interface ITabItem { component: any; // TODO: 组件的Ts是什么 } -const tabList: ITabItem[] = [ - { - title: '基本信息', - key: 'basic', - component: - }, - { - title: '列信息', - key: 'column', - component: - }, - { - title: '索引信息', - key: 'index', - component: - }, -] +interface IContext extends IProps { + tableDetails: IDatabaseTableDetail +} + +export const Context = createContext({} as any); export default memo(function DatabaseTableEditor(props) { - const { className } = props; - const [currentTab, setCurrentTab] = useState(tabList[1]); + const { databaseName, dataSourceId, tableName, schemaName } = props; + const [tableDetails, setTableDetails] = useState({} as any); + const baseInfoRef = useRef(null); + const tabList = useMemo(() => { + return [ + { + title: '基本信息', + key: 'basic', + component: + }, + { + title: '列信息', + key: 'column', + component: + }, + { + title: '索引信息', + key: 'index', + component: + }, + ] + }, []) + const [currentTab, setCurrentTab] = useState(tabList[0]); + function changeTab(item: ITabItem) { setCurrentTab(item) } - return
-
- { - tabList.map((item, index) => { - return
- {item.title} -
- }) - } -
-
- { - tabList.map(t => { - return
- {t.component} -
- }) - } + useEffect(() => { + let params = { + databaseName, + dataSourceId, + tableName, + schemaName, + refresh: true + } + sqlService.getTableDetails(params).then(res => { + setTableDetails(res) + }) + }, []) + + function submit() { + console.log(baseInfoRef.current?.getBaseInfo()) + } + + return +
+
+
+ { + tabList.map((item, index) => { + return
+ {item.title} +
+ }) + } +
+
+ +
+
+
+ { + tabList.map(t => { + return
+ {t.component} +
+ }) + } +
-
+ + }) diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index 097048f22..068a752a3 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,9 +1,7 @@ import React, { memo } from 'react'; import i18n from '@/i18n'; -import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; export default function Demo() { return
-
} \ No newline at end of file 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 3ec6a396f..e8a5c13a8 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -6,7 +6,7 @@ import Iconfont from '@/components/Iconfont'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; -import { Cascader, Spin, Modal, Button } from 'antd'; +import { Cascader, Spin, Modal, Button, Tag } from 'antd'; import { databaseMap, TreeNodeType } from '@/constants'; import { treeConfig } from '../Tree/treeConfig'; import { useUpdateEffect } from '@/hooks/useUpdateEffect' 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 704a206f2..343c2e637 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -58,6 +58,7 @@ const WorkspaceRight = memo(function (props) { tabType: createTabIntro.tabType, icon: tabTypeConfig[createTabIntro.tabType as TabType]?.icon, name: `edit-${createTabIntro.treeNodeData.name}`, + tableName: createTabIntro.treeNodeData.name, }]) setActiveConsoleId(id); @@ -483,7 +484,12 @@ const WorkspaceRight = memo(function (props) { /> } { - t.tabType === TabType.EditTable && + t.tabType === TabType.EditTable && }
); diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 896867b6b..5945ee3d5 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,5 +1,5 @@ import createRequest from './base'; -import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines } from '@/typings'; +import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseFieldType, IDatabaseTableDetail } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; @@ -174,12 +174,29 @@ const getProcedureDetail = createRequest<{ }, { procedureBody: string }>('/api/rdb/procedure/detail', { method: 'get' }); /** 格式化sql */ -const sqlFormat = createRequest<{ +const sqlFormat = createRequest<{ sql: string; dbType: DatabaseTypeCode; }, string>('/api/sql/format', { method: 'get' }); +/** 数据库支持的数据类型 */ +const getDatabaseFieldTypeList = createRequest<{ + dataSourceId: number; + databaseName: string; +}, IDatabaseFieldType[]>('/api/rdb/table/type_list', { method: 'get' }); + +/** 数据库支持的数据类型 */ +const getTableDetails = createRequest<{ + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; + refresh: boolean; +}, IDatabaseTableDetail>('/api/rdb/table/query', { method: 'get' }); + export default { + getTableDetails, + getDatabaseFieldTypeList, sqlFormat, getTriggerDetail, getProcedureDetail, @@ -208,3 +225,5 @@ export default { getDMLCount, // exportResultTable }; + + diff --git a/chat2db-client/src/styles/global.less b/chat2db-client/src/styles/global.less index be6bd00d1..f748ab607 100644 --- a/chat2db-client/src/styles/global.less +++ b/chat2db-client/src/styles/global.less @@ -17,29 +17,29 @@ input:-webkit-autofill { caret-color: var(--color-text); // 光标的颜色 } -::-webkit-scrollbar { - width: 6px; - height: 6px; -} +// ::-webkit-scrollbar { +// width: 6px; +// height: 6px; +// } -::-webkit-scrollbar-thumb { - background-color: var(--color-fill); - border-radius: 3px; -} +// ::-webkit-scrollbar-thumb { +// background-color: var(--color-fill); +// border-radius: 3px; +// } -::-webkit-scrollbar-button { - width: 0; - height: 0; - background: transparent; -} +// ::-webkit-scrollbar-button { +// width: 0; +// height: 0; +// background: transparent; +// } -::-webkit-scrollbar-track { - background: transparent; -} +// ::-webkit-scrollbar-track { +// background: transparent; +// } -::-webkit-scrollbar-corner { - background: transparent; -} +// ::-webkit-scrollbar-corner { +// background: transparent; +// } html, body, @@ -133,9 +133,8 @@ li { list-style: none; } - // 覆盖antd 的一些样式 -button{ +button { box-shadow: none !important; -} \ No newline at end of file +} diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 2dcbedb0c..1ca39d253 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -34,3 +34,39 @@ export interface IResultConfig { total: number | string; hasNextPage: boolean; } + +/** 不同数据库支持的列字段类型*/ +export interface IDatabaseFieldType { + typeName: string; +} + +export interface IColumn { + name: string; + dataType: string; + columnType: string; + nullable: 0 | 1; + primaryKey: boolean; + defaultValue: string; + autoIncrement: boolean; + numericPrecision: number; + numericScale: number; + characterMaximumLength: number; + comment: string; +} +export interface IIndex { + columns: string; + name: string; + type: string; + comment: string; + columnList: IColumn[]; +} + +/** 数据库表的详情*/ +export interface IDatabaseTableDetail { + name: string; + comment: string; + pinned: false; + ddl: string; + columnList: IColumn[]; + indexList: IIndex[]; +} From 70a86ea72b7cb0c04074582505b8bbd96689ab4b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 9 Sep 2023 15:50:51 +0800 Subject: [PATCH 0737/1069] feat: Add the client startup command parameters --- chat2db-client/src/main/preload.js | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index be75ed59b..8e1e1b9b6 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -20,6 +20,7 @@ contextBridge.exposeInMainWorld('myAPI', { '-Xmx1024M', `-Dspring.profiles.active=${isTest ? 'test' : 'release'}`, '-Dserver.address=127.0.0.1', + '-Dchat2db.mode=DESKTOP', `-Dproject.path=${javaPath}`, `-Dloader.path=${libPath}`, javaPath, From 8356fd7d57e679f550bc79f34aa6a7b5a66c8562 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Thu, 7 Sep 2023 19:17:40 +0800 Subject: [PATCH 0738/1069] rest ai optimize --- .../ai/listener/RestAIEventSourceListener.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java index 90962a40e..49174a899 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java @@ -54,7 +54,9 @@ public void onEvent(EventSource eventSource, String id, String type, String data } Message message = new Message(); if (StringUtils.isNotBlank(data)) { - message.setContent(data.replace("\"", "")); + data = data.replaceAll("^\"|\"$", ""); + data = data.replaceAll("\\\\n", "\n"); + message.setContent(data); sseEmitter.send(SseEmitter.event() .id(id) .data(message) @@ -78,7 +80,6 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { try { if (Objects.isNull(response)) { String message = t.getMessage(); - message = message + ", AI无法正常访问, 请参考文章进行配置"; Message sseMessage = new Message(); sseMessage.setContent(message); sseEmitter.send(SseEmitter.event() @@ -94,9 +95,9 @@ public void onFailure(EventSource eventSource, Throwable t, Response response) { String bodyString = null; if (Objects.nonNull(body)) { bodyString = body.string(); - log.error("REST AI sse连接异常data:{},异常:{}", bodyString, t); + log.error("REST AI sse body error:{},exception:{}", bodyString, t); } else { - log.error("REST AI sse连接异常data:{},异常:{}", response, t); + log.error("REST AI sse response error:{},exception:{}", response, t); } if (Objects.nonNull(eventSource)) { eventSource.cancel(); From 6e02c2fbd2fb0870deff4d22691761d219ecc1db Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 16:02:59 +0800 Subject: [PATCH 0739/1069] Add Publishing Branch --- .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 14867d079..d0f47abc0 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -296,7 +296,7 @@ jobs: content: | { "title": "MacOS-amd64-test-构建完成通知", - "text": "# MacOS-amd64-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 }}/${{ github.run_id }}/chat2db-server-start.zip) " + "text": "# MacOS-amd64-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) " } # 构建完成通知 From 238afef96358486c23859900a127fecceb99d5b2 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 16:32:15 +0800 Subject: [PATCH 0740/1069] Fix some team project bugs --- .../config/config/Chat2dbWebMvcConfigurer.java | 1 + .../start/controller/oauth/OauthController.java | 15 +++++++++++++-- .../server/tools/common/model/LoginUser.java | 7 +++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index b4d4a0146..ab298cdb4 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -101,6 +101,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl .id(user.getId()) .nickName(user.getNickName()) .admin(admin) + .roleCode(user.getRoleCode()) .build(); }); diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java index a29173ad0..0ac4bc835 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java @@ -92,7 +92,7 @@ public ActionResult logout() { */ @GetMapping("user") public DataResult user() { - return DataResult.of(ContextUtils.getLoginUser()); + return DataResult.of(getLoginUser()); } /** @@ -102,7 +102,18 @@ public DataResult user() { */ @GetMapping("user_a") public DataResult usera() { - return DataResult.of(ContextUtils.queryLoginUser()); + return DataResult.of(getLoginUser()); + } + + private LoginUser getLoginUser() { + LoginUser loginUser = ContextUtils.queryLoginUser(); + if (loginUser == null) { + return null; + } + if (RoleCodeEnum.DESKTOP.getCode().equals(loginUser.getRoleCode())) { + return null; + } + return loginUser; } } diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java index 72b740876..a7c718264 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/model/LoginUser.java @@ -37,4 +37,11 @@ public class LoginUser implements Serializable { * Is it an administrator */ private Boolean admin; + + /** + * 角色编码 + * + * @see RoleCodeEnum + */ + private String roleCode; } From 044e87fe62749d90841127ac5acdae16700349fc Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 9 Sep 2023 16:37:53 +0800 Subject: [PATCH 0741/1069] fix: fix bug --- chat2db-client/src/pages/main/dashboard/chart-item/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2547ecfb0..2f966b98f 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -136,7 +136,7 @@ function ChartItem(props: IChartItemProps) { const handleExecuteSQL = async (sql: string, chartData: IChartItem) => { const { dataSourceId, databaseName } = chartData; - if (_.isEmpty(cascaderValue)) { + if (_.isEmpty(dataSourceId)) { message.success(i18n('dashboard.editor.execute.noDataSource')); return; } From fb9f773c411fef3e1011dc9551b460b75e2ae204 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 9 Sep 2023 16:47:13 +0800 Subject: [PATCH 0742/1069] feat: iconfont --- chat2db-client/src/components/Iconfont/index.less | 8 +++----- chat2db-client/src/components/Iconfont/index.tsx | 7 +++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index cf735828a..bd00dfd77 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -1,9 +1,7 @@ @font-face { - font-family: 'iconfont'; - /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff2?t=1693730048111') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff?t=1693730048111') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.ttf?t=1693730048111') format('truetype'); + font-family: 'iconfont'; /* Project id 3633546 */ + src: url('../../assets/font/iconfont.woff2') format('woff2'), url('../../assets/font/iconfont.woff') format('woff'), + url('../../assets/font/iconfont.ttf') format('truetype'); } .iconfont { diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 8b9a1a66a..55e81cc2c 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -8,11 +8,10 @@ if (__ENV__ === 'local') { let container = ` /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { - font-family: 'iconfont'; - /* Project id 3633546 */ + font-family: 'iconfont'; /* Project id 3633546 */ src: url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff2?t=1693730048111') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff?t=1693730048111') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.ttf?t=1693730048111') format('truetype'); + url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff?t=1693730048111') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.ttf?t=1693730048111') format('truetype'); } `; let style = document.createElement('style'); From e5fca7429361b36f9c6ed6db34f74c0c5423e854 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 17:09:20 +0800 Subject: [PATCH 0743/1069] Fix some team project bugs --- .../service/DataSourceAccessBusinessService.java | 5 +++-- .../DataSourceAccessBusinessServiceImpl.java | 16 +++++++++------- .../web/api/aspect/ConnectionInfoHandler.java | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java index e86105758..4d4acf880 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DataSourceAccessBusinessService.java @@ -1,5 +1,6 @@ package ai.chat2db.server.domain.api.service; +import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import jakarta.validation.constraints.NotNull; @@ -12,8 +13,8 @@ public interface DataSourceAccessBusinessService { /** * delete * - * @param dataSourceId + * @param dataSource * @return */ - ActionResult checkPermission(@NotNull Long dataSourceId); + ActionResult checkPermission(@NotNull DataSource dataSource); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java index 02886ed49..64d88a859 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DataSourceAccessBusinessServiceImpl.java @@ -1,7 +1,8 @@ package ai.chat2db.server.domain.core.impl; import ai.chat2db.server.domain.api.enums.AccessObjectTypeEnum; -import ai.chat2db.server.domain.api.enums.RoleCodeEnum; +import ai.chat2db.server.domain.api.enums.DataSourceKindEnum; +import ai.chat2db.server.domain.api.model.DataSource; import ai.chat2db.server.domain.api.param.datasource.access.DataSourceAccessPageQueryParam; import ai.chat2db.server.domain.api.service.DataSourceAccessBusinessService; import ai.chat2db.server.domain.api.service.DataSourceAccessService; @@ -11,6 +12,7 @@ import ai.chat2db.server.tools.common.model.LoginUser; import ai.chat2db.server.tools.common.util.ContextUtils; import jakarta.annotation.Resource; +import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -29,11 +31,11 @@ public class DataSourceAccessBusinessServiceImpl implements DataSourceAccessBusi private DataSourceAccessCustomMapper dataSourceAccessCustomMapper; @Override - public ActionResult checkPermission(Long dataSourceId) { + public ActionResult checkPermission(@NotNull DataSource dataSource) { LoginUser loginUser = ContextUtils.getLoginUser(); - // Representative is desktop mode - if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(loginUser.getId())) { - if (RoleCodeEnum.DESKTOP.getDefaultUserId().equals(dataSourceId)) { + // private + if (DataSourceKindEnum.PRIVATE.getCode().equals(dataSource.getKind())) { + if (loginUser.getId().equals(dataSource.getUserId())) { return ActionResult.isSuccess(); } else { throw new PermissionDeniedBusinessException(); @@ -47,7 +49,7 @@ public ActionResult checkPermission(Long dataSourceId) { // Verify if user have permission DataSourceAccessPageQueryParam dataSourceAccessPageQueryParam = new DataSourceAccessPageQueryParam(); - dataSourceAccessPageQueryParam.setDataSourceId(dataSourceId); + dataSourceAccessPageQueryParam.setDataSourceId(dataSource.getId()); dataSourceAccessPageQueryParam.setAccessObjectType(AccessObjectTypeEnum.USER.getCode()); dataSourceAccessPageQueryParam.setAccessObjectId(loginUser.getId()); dataSourceAccessPageQueryParam.queryOne(); @@ -56,7 +58,7 @@ public ActionResult checkPermission(Long dataSourceId) { } // Verify if the team has permission - if (dataSourceAccessCustomMapper.checkTeamPermission(dataSourceId, loginUser.getId()) != null) { + if (dataSourceAccessCustomMapper.checkTeamPermission(dataSource.getId(), loginUser.getId()) != null) { return ActionResult.isSuccess(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java index a56447234..d52ea1282 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/aspect/ConnectionInfoHandler.java @@ -72,7 +72,7 @@ public ConnectInfo toInfo(Long dataSourceId, String database, Long consoleId, St } // Verify permissions - dataSourceAccessBusinessService.checkPermission(dataSourceId); + dataSourceAccessBusinessService.checkPermission(dataSource); ConnectInfo connectInfo = new ConnectInfo(); connectInfo.setAlias(dataSource.getAlias()); From c6ab1803cb57814c34fb0e6edb2b93b36071b66b Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 9 Sep 2023 17:20:26 +0800 Subject: [PATCH 0744/1069] fix: fix dashboard execute error --- chat2db-client/src/pages/main/dashboard/chart-item/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 2f966b98f..6bfa172ef 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -19,7 +19,7 @@ import i18n from '@/i18n'; import { useTheme } from '@/hooks'; import { EditorThemeType, ThemeType } from '@/constants'; import { IRemainingUse } from '@/typings/ai'; -import _ from 'lodash'; +import { isValid } from '@/utils/check'; const handleSQLResult2ChartData = (data) => { const { headerList, dataList } = data; @@ -136,7 +136,7 @@ function ChartItem(props: IChartItemProps) { const handleExecuteSQL = async (sql: string, chartData: IChartItem) => { const { dataSourceId, databaseName } = chartData; - if (_.isEmpty(dataSourceId)) { + if (!isValid(dataSourceId)) { message.success(i18n('dashboard.editor.execute.noDataSource')); return; } From f7fafcb2a82d86d5532fb8742e7ea998bce0d009 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 17:32:09 +0800 Subject: [PATCH 0745/1069] Fix some team project bugs --- .../mapper/DataSourceCustomMapper.xml | 45 ++++++++----------- .../request/DataSourceQueryRequest.java | 1 - 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml index cd4e5b6c5..fda92baff 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml @@ -7,39 +7,32 @@ from DATA_SOURCE ds - - and ds.USER_ID != 1 + + and ds.USER_ID = #{userId} - - - and ds.USER_ID = #{userId} - - - and (ds.USER_ID = #{userId} - or exists( - select 1 from DATA_SOURCE_ACCESS dsa where dsa.ACCESS_OBJECT_TYPE = 'USER' - and dsa.ACCESS_OBJECT_ID = #{userId} - and dsa.DATA_SOURCE_ID = ds.ID - ) - or exists ( - select 1 - from DATA_SOURCE_ACCESS dsa - LEFT JOIN TEAM_USER tu on tu.ID = dsa.ACCESS_OBJECT_ID and dsa.ACCESS_OBJECT_TYPE = 'TEAM' - left join TEAM t on t.id = tu.TEAM_ID - where tu.USER_ID = #{userId} - and dsa.DATA_SOURCE_ID = ds.ID - and t.STATUS = 'VALID' - ) - ) - - + and (ds.USER_ID = #{userId} + or exists( + select 1 from DATA_SOURCE_ACCESS dsa where dsa.ACCESS_OBJECT_TYPE = 'USER' + and dsa.ACCESS_OBJECT_ID = #{userId} + and dsa.DATA_SOURCE_ID = ds.ID + ) + or exists ( + select 1 + from DATA_SOURCE_ACCESS dsa + LEFT JOIN TEAM_USER tu on tu.ID = dsa.ACCESS_OBJECT_ID and dsa.ACCESS_OBJECT_TYPE = 'TEAM' + left join TEAM t on t.id = tu.TEAM_ID + where tu.USER_ID = #{userId} + and dsa.DATA_SOURCE_ID = ds.ID + and t.STATUS = 'VALID' + ) + ) and (ds.alias like concat('%',#{searchKey},'%') or ds.url like concat('%',#{searchKey},'%')) - + and ds.kind = #{kind} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java index 28cbef2ae..c4448b677 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/data/source/request/DataSourceQueryRequest.java @@ -1,7 +1,6 @@ package ai.chat2db.server.web.api.controller.data.source.request; import ai.chat2db.server.tools.base.wrapper.request.PageQueryRequest; - import lombok.Data; /** From f03301059d807b5fe00b33a951c9d58991216d43 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 17:56:09 +0800 Subject: [PATCH 0746/1069] Fix some team project bugs --- .../main/resources/i18n/messages.properties | 2 + .../resources/i18n/messages_en_US.properties | 2 + .../resources/i18n/messages_zh_CN.properties | 50 ++++++++++--------- .../api/controller/ai/AiConfigController.java | 16 +++++- .../web/api/http/response/QrCodeResponse.java | 6 +++ 5 files changed, 50 insertions(+), 26 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties index 8b1c6f2bb..c7b3939a9 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages.properties @@ -29,3 +29,5 @@ sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV + +settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index 5847dd2f9..f70c21be5 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -27,3 +27,5 @@ sqlResult.success=Execution successful user.canNotOperateSystemAccount=System accounts cannot be operated execute.exportCsv=For more data, please click on Export CSV + +settings.permissionDeniedForAiConfig=Please contact the administrator to set ApiKey in "Settings ->Custom Ai" \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index 4de4905be..212a36c00 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -1,28 +1,30 @@ -common.businessError=请尝试重新提交或者刷新页面 -common.systemError=系统发生异常,可在帮助中点击日志查看异常详情。 -common.needLoggedIn=需要登陆页面 -common.redirect=重定向页面 -common.paramError=您输入的参数异常 -common.paramDetailError=您输入的参数:{0},存在异常 -common.paramCheckError=请检查以下参数: -common.maxUploadSize=您输入的文件超过最大限制 -common.permissionDenied=您没有权限访问该页面 -common.dataNotFound=您访问的数据不存在 -common.dataAlreadyExists=数据库总已经存在该数据 -common.dataAlreadyExistsWithParam=数据库总已经存在该数据,{0}:{1} +common.businessError=????????????? +common.systemError=??????????????????????? +common.needLoggedIn=?????? +common.redirect=????? +common.paramError=???????? +common.paramDetailError=??????:{0},???? +common.paramCheckError=???????: +common.maxUploadSize=???????????? +common.permissionDenied=?????????? +common.dataNotFound=????????? +common.dataAlreadyExists=??????????? +common.dataAlreadyExistsWithParam=???????????,{0}:{1} -oauth.userNameNotExits=当前账号不存在 -oauth.IllegalUserName=当前账号无法登录,请换一个账号 -oauth.passwordIncorrect=您输入的密码有误 -oauth.invalidUserName=您输入的账号已经失效 +oauth.userNameNotExits=??????? +oauth.IllegalUserName=??????????????? +oauth.passwordIncorrect=???????? +oauth.invalidUserName=?????????? -dataSource.sqlAnalysisError=不合法的执行语句 -connection.error=数据库链接异常,请检查数据库配置 -connection.ssh.error=SSH 链接异常,请检查SSH配置 -connection.driver.load.error=数据库驱动加载异常,请检查驱动配置 +dataSource.sqlAnalysisError=???????? +connection.error=???????????????? +connection.ssh.error=SSH ????????SSH?? +connection.driver.load.error=????????????????? # sqlResult -sqlResult.rowNumber=行号 -sqlResult.success=执行成功 +sqlResult.rowNumber=?? +sqlResult.success=???? -user.canNotOperateSystemAccount=不能操作系统账号 -execute.exportCsv=更多数据请点击导出csv +user.canNotOperateSystemAccount=???????? +execute.exportCsv=?????????csv + +settings.permissionDeniedForAiConfig=??????? ???->???AI? ????ApiKey \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java index c5eccfc6b..2142d5a4a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/AiConfigController.java @@ -3,11 +3,15 @@ import java.util.Objects; import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; +import ai.chat2db.server.domain.api.enums.RoleCodeEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.param.SystemConfigParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; +import ai.chat2db.server.tools.common.model.LoginUser; +import ai.chat2db.server.tools.common.util.ContextUtils; +import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; @@ -15,7 +19,6 @@ import ai.chat2db.server.web.api.http.response.ApiKeyResponse; import ai.chat2db.server.web.api.http.response.InviteQrCodeResponse; import ai.chat2db.server.web.api.http.response.QrCodeResponse; -import ai.chat2db.server.web.api.util.OpenAIClient; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -51,6 +54,11 @@ public class AiConfigController { */ @GetMapping("/getLoginQrCode") public DataResult getLoginQrCode() { + LoginUser loginUser = ContextUtils.getLoginUser(); + if (RoleCodeEnum.USER.getCode().equals(loginUser.getRoleCode())) { + return DataResult.of( + QrCodeResponse.builder().tip(I18nUtils.getMessage("settings.permissionDeniedForAiConfig")).build()); + } return gatewayClientService.getLoginQrCode(); } @@ -62,12 +70,16 @@ public DataResult getLoginQrCode() { */ @GetMapping("/getLoginStatus") public DataResult getLoginStatus(@RequestParam(required = false) String token) { + LoginUser loginUser = ContextUtils.getLoginUser(); + if (RoleCodeEnum.USER.getCode().equals(loginUser.getRoleCode())) { + return DataResult.of(QrCodeResponse.builder().build()); + } DataResult dataResult = gatewayClientService.getLoginStatus(token); QrCodeResponse qrCodeResponse = dataResult.getData(); // Representative successfully logged in if (StringUtils.isNotBlank(qrCodeResponse.getApiKey())) { SystemConfigParam sqlSourceParam = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE) - .content(AiSqlSourceEnum.CHAT2DBAI.getCode()).build(); + .content(AiSqlSourceEnum.CHAT2DBAI.getCode()).build(); configService.createOrUpdate(sqlSourceParam); SystemConfigParam param = SystemConfigParam.builder() .code(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).content(qrCodeResponse.getApiKey()) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/QrCodeResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/QrCodeResponse.java index 29f7b8528..a03e557dd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/QrCodeResponse.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/QrCodeResponse.java @@ -1,8 +1,14 @@ package ai.chat2db.server.web.api.http.response; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; @Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor public class QrCodeResponse { /** * When logging in for the first time, the token will be returned, and subsequent services need to poll the token to From ffef89245b17e48b65e8708b75c16333615981bc Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 9 Sep 2023 17:59:12 +0800 Subject: [PATCH 0747/1069] feat: env tag --- .../ConnectionEdit/config/dataSource.ts | 44 ++++++--- .../src/components/ConnectionEdit/index.tsx | 7 +- .../components/WorkspaceHeader/index.less | 26 +++-- .../components/WorkspaceHeader/index.tsx | 96 ++++++++++--------- chat2db-client/src/typings/connection.ts | 2 +- 5 files changed, 104 insertions(+), 71 deletions(-) diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index 1cf5ad093..bbe45b307 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -130,6 +130,19 @@ export const sshConfig: IConnectionConfig['ssh'] = { ] } +const envItem = { + defaultValue: '', + inputType: InputType.SELECT, + labelNameCN: '环境', + labelNameEN: 'Env', + name: 'environmentId', + required: true, + selects: [], + styles: { + width: '50%', + } +} + export const dataSourceFormConfigs: IConnectionConfig[] = [ // MYSQL { @@ -143,18 +156,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ name: 'alias', required: true, }, - { - defaultValue: '', - inputType: InputType.SELECT, - labelNameCN: '环境', - labelNameEN: 'Env', - name: 'environmentId', - required: true, - selects: [], - styles: { - width: '50%', - } - }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -263,8 +265,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -373,8 +375,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -514,8 +516,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ labelNameEN: 'Name', name: 'alias', required: true, - }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -644,6 +646,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -764,6 +767,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, }, + envItem, { defaultValue: 'identifier.sqlite', inputType: InputType.INPUT, @@ -802,6 +806,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -913,6 +918,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1024,6 +1030,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ required: true, }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1139,6 +1146,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1260,6 +1268,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1381,6 +1390,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1502,6 +1512,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1623,6 +1634,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1744,6 +1756,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, @@ -1865,6 +1878,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ width: '100%', } }, + envItem, { defaultValue: 'localhost', inputType: InputType.INPUT, diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 3bf4af9ec..8251d617d 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -61,7 +61,7 @@ const CreateConnection = forwardRef(function (props: IProps, ref: ForwardedRef { return { value: t.id, - label: t.name + label: t.shortName } })); }, [connectionEnvList]); @@ -195,6 +195,11 @@ const CreateConnection = forwardRef(function (props: IProps, ref: ForwardedRef((props) => { getDatabaseList(isRefresh); setIsRefresh(false); } - }, [connectionList, curConnection, curPage]) - - useUpdateEffect(() => { - // connectionList转换成可用的ConnectionOptions - setConnectionOptions(connectionList?.map(t => { + // connectionList转换成可用的ConnectionOptions + setConnectionOptions(connectionList?.map(t => { return { value: t.id, - label: t.alias + label: t.alias, } })); + }, [connectionList, curConnection, curPage]) + + useUpdateEffect(() => { if (!connectionList.length) { dispatch({ type: 'workspace/setCurWorkspaceParams', @@ -222,52 +222,56 @@ const WorkspaceHeader = memo((props) => { { !!connectionList.length &&
- -
-
{curWorkspaceParams.dataSourceName}
- -
-
- - { - !!curDBOptions?.length && - -
-
{curWorkspaceParams.databaseName}
- { - !!curSchemaOptions.length && - } -
-
- } - { - !!curSchemaOptions.length && +
-
{curWorkspaceParams.schemaName}
+
+ {curConnection?.id && {curConnection?.environment?.shortName}} +
+
{curWorkspaceParams.dataSourceName}
- } -
- {cascaderLoading ? : } + + { + !!curDBOptions?.length && + +
+
{curWorkspaceParams.databaseName}
+
+
+ } + { + !!curSchemaOptions.length && + } + { + !!curSchemaOptions.length && + +
+
{curWorkspaceParams.schemaName}
+
+
+ } +
+ {cascaderLoading ? : } +
} diff --git a/chat2db-client/src/typings/connection.ts b/chat2db-client/src/typings/connection.ts index 5b6917676..fde31fb0f 100644 --- a/chat2db-client/src/typings/connection.ts +++ b/chat2db-client/src/typings/connection.ts @@ -32,5 +32,5 @@ export interface IConnectionEnv { id: number; name: string; shortName: string; - style: string; + color: string; } From 017d167a09a9622b8d8613fd01b48f92a860c6d1 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 9 Sep 2023 18:23:12 +0800 Subject: [PATCH 0748/1069] fix: More bug fix --- .../src/blocks/Setting/AiSetting/index.tsx | 37 +++++++++++++++---- .../src/components/Popularize/index.tsx | 10 ++++- chat2db-client/src/i18n/en-us/setting.ts | 1 + chat2db-client/src/i18n/zh-cn/setting.ts | 3 +- chat2db-client/src/layouts/index.tsx | 2 +- chat2db-client/src/pages/login/index.tsx | 4 +- chat2db-client/src/pages/main/index.tsx | 13 ++++++- chat2db-client/src/typings/user.ts | 7 +++- 8 files changed, 63 insertions(+), 14 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index c59f93006..8aafb01fe 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -1,11 +1,13 @@ import React, { useEffect, useState } from 'react'; import configService from '@/service/config'; import { AiSqlSourceType } from '@/typings/ai'; -import { Alert, Button, Input, Radio, RadioChangeEvent } from 'antd'; +import { Alert, Button, Input, Radio, RadioChangeEvent, Spin } from 'antd'; import i18n from '@/i18n'; import classnames from 'classnames'; import { IAiConfig } from '@/typings/setting'; import styles from './index.less'; +import { getUser } from '@/service/user'; +import { ILoginUser, IRole } from '@/typings/user'; interface IProps { handleApplyAiConfig: (aiConfig: IAiConfig) => void; @@ -14,16 +16,37 @@ interface IProps { // openAI 的设置项 export default function SettingAI(props: IProps) { - const [aiConfig, setAiConfig] = useState(props?.aiConfig); + const [aiConfig, setAiConfig] = useState(); + const [userInfo, setUserInfo] = useState(); + const [loading, setLoading] = useState(false); + + const queryUserInfo = async () => { + setLoading(true); + try { + const res = await getUser(); + setUserInfo(res); + } finally { + setLoading(false); + } + }; - if (!aiConfig) { - return ; - } + useEffect(() => { + queryUserInfo(); + }, []); useEffect(() => { setAiConfig(props.aiConfig); }, [props.aiConfig]); + if (!aiConfig) { + return ; + } + + if (userInfo?.roleCode === IRole.USER) { + // 如果是用户,不能配置ai + return ; + } + const handleAiTypeChange = async (e: RadioChangeEvent) => { const aiSqlSource = e.target.value; @@ -50,7 +73,7 @@ export default function SettingAI(props: IProps) { }; return ( - <> +
{i18n('setting.title.aiSource')}:
@@ -201,6 +224,6 @@ export default function SettingAI(props: IProps) {
{/* {aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI && !aiConfig.apiKey && } */} - +
); } diff --git a/chat2db-client/src/components/Popularize/index.tsx b/chat2db-client/src/components/Popularize/index.tsx index 9d631a524..b955fe658 100644 --- a/chat2db-client/src/components/Popularize/index.tsx +++ b/chat2db-client/src/components/Popularize/index.tsx @@ -26,10 +26,18 @@ export default memo(function Popularize(props) { } return dom; }; + + const renderImage = () => { + if (!props.source && !props.imageUrl) { + return null; + } + return ; + }; + return (
{/*
获取更多次数
*/} - + {renderImage()}
{renderTip()}
); diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 5f36202dd..2012975c2 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -33,4 +33,5 @@ export default { 'setting.placeholder.azureEndpoint': 'Get Azure OpenAI endpoint from the Azure Portal', 'setting.placeholder.azureDeployment': 'Deployment id of the deployed model', 'setting.ai.tips': 'Please log in and select AI configuration', + 'setting.ai.user.hidden': 'Please contact the administrator to set ApiKey in "Settings ->Custom Ai"', }; diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 88f9caac4..a011bc77c 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -33,4 +33,5 @@ export default { 'setting.placeholder.azureEndpoint': '从Azure门户获取Azure OpenA端口', 'setting.placeholder.azureDeployment': '部署模型的部署id', 'setting.ai.tips': '请登录后选择AI配置', -} \ No newline at end of file + 'setting.ai.user.hidden': '请联系管理员在"设置->自定义AI"中设置ApiKey', +}; diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 462a8ad0e..1a2643a3f 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -163,7 +163,7 @@ function AppContainer() { {startSchedule === 1 && ( <>
- + {/*
*/} diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 828e01efa..5dce52eff 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -7,6 +7,7 @@ import styles from './index.less'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import i18n from '@/i18n'; +import { useNavigate } from 'react-router-dom'; interface IFormData { userName: string; @@ -14,10 +15,11 @@ interface IFormData { } const App: React.FC = () => { + const navigate = useNavigate(); const handleLogin = async (formData: { userName: string; password: string }) => { let res = await userLogin(formData); if (res) { - window.location.href = location.origin; + navigate('/'); } }; diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 76dea5ad1..359498ea3 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -19,6 +19,7 @@ import { ILoginUser } from '@/typings/user'; import { Dropdown } from 'antd'; import Team from './team'; import i18n from '@/i18n'; +import { useNavigate } from 'react-router-dom'; let navConfig: INavItem[] = [ { @@ -63,6 +64,7 @@ interface IProps { } function MainPage(props: IProps) { + const navigate = useNavigate(); const { mainModel, dispatch } = props; const { curPage } = mainModel; const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); @@ -73,7 +75,8 @@ function MainPage(props: IProps) { getUser().then((res) => { if (res) { setUserInfo(res); - if (res.admin) { + const hasTeamIcon = navConfig.find((i) => i.key === 'team') + if (res.admin && !hasTeamIcon) { navConfig.splice(3, 0, { key: 'team', icon: '\ue64b', @@ -85,6 +88,12 @@ function MainPage(props: IProps) { setActiveNav(navConfig[3]); } } + if(!res.admin && hasTeamIcon){ + navConfig.splice(3, 1); + if (localStorage.getItem('curPage') === 'team') { + setActiveNav(navConfig[2]); + } + } } }); }, []); @@ -134,7 +143,7 @@ function MainPage(props: IProps) { const handleLogout = () => { userLogout().then((res) => { setUserInfo(undefined); - window.location.href = '/#/login'; + navigate('/login'); }); }; diff --git a/chat2db-client/src/typings/user.ts b/chat2db-client/src/typings/user.ts index 7c11a48c0..e8dcc238e 100644 --- a/chat2db-client/src/typings/user.ts +++ b/chat2db-client/src/typings/user.ts @@ -1,4 +1,8 @@ -export type IRole = 'admin' | 'normal'; +export enum IRole { + 'ADMIN' = 'ADMIN', + 'USER' = 'USER', + 'DESKTOP' = 'DESKTOP', +} export interface IUser { id?: number; userName: string; @@ -22,4 +26,5 @@ export interface ILoginUser { * 昵称 */ nickName?: string; + roleCode: IRole; } From c22e432a8f56766be1266d9389479b7b99c8e61f Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 19:04:04 +0800 Subject: [PATCH 0749/1069] Add Publishing Branch --- CHANGELOG.md | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ef22df44..98ca12f9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,22 @@ -# 2.0.14 +# 2.1.0 + +## ⭐ New Features + +- 🔥The team function is newly launched, supporting team collaboration. R&D does not require knowing the online database + password, solving the security issue of enterprise database accounts. It is recommended to directly deploy the team + function using 'docker' +- Added support for environment selection, better distinguishing between online and daily ## 🐞 Bug Fixes - Fix the issue of 'Oracle' query 'Blob' reporting errors - Modify the paging logic and fix some SQL queries that cannot be queried +## ⭐ 新特性 + +- 🔥团队功能全新上线,支持团队协作,研发无需知道线上数据库密码,解决企业数据库账号安全问题,团队功能建议直接使用 `docker` 部署 +- 新增支持环境选择,更好的区分线上、日常环境 + ## 🐞 问题修复 - 修复 `Oracle` 查询 `Blob` 报错的问题 @@ -12,23 +24,18 @@ # 2.0.13 -## ⭐ New Features - ## 🐞 Bug Fixes -- Fixed a bug where sql formatting was not selected -- Fixed open view lag issue -- Solve the white screen problem of connected non-relational databases (non-relational databases are not supported) - -## ⭐ 新特性 - +- Fixed a bug where sql formatting was not selected +- Fixed open view lag issue +- Solve the white screen problem of connected non-relational databases (non-relational databases are not supported) ## 🐞 问题修复 + - 修复不选中sql格式化的bug - 修复打开视图卡顿问题 - 解决已连接的非关系型数据库打开白屏问题(暂不支持非关系性数据库) - # 2.0.12 ## ⭐ New Features From 8bca8ccd75da5c147101bc579a1fb5ac0ed87600 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 9 Sep 2023 19:08:57 +0800 Subject: [PATCH 0750/1069] feat:env tag and Default account password prompt --- .../src/blocks/Setting/ProxySetting/index.tsx | 40 +++++++++++-------- .../src/components/ConnectionEdit/index.tsx | 2 +- chat2db-client/src/i18n/en-us/login.ts | 1 + chat2db-client/src/i18n/zh-cn/login.ts | 1 + chat2db-client/src/pages/login/index.less | 7 +++- chat2db-client/src/pages/login/index.tsx | 3 +- .../src/pages/main/connection/index.less | 4 ++ .../src/pages/main/connection/index.tsx | 5 ++- chat2db-client/src/service/base.ts | 8 +++- chat2db-client/src/service/outside.ts | 5 +++ 10 files changed, 54 insertions(+), 22 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx b/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx index 13403b569..e25974079 100644 --- a/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/ProxySetting/index.tsx @@ -3,6 +3,8 @@ import i18n from '@/i18n'; import { Button, Input, message } from 'antd'; import classnames from 'classnames'; import styles from './index.less'; +import outSideService from '@/service/outside'; + // 代理设置 export default function ProxyBody() { const [apiPrefix, setApiPrefix] = useState(window._BaseURL); @@ -15,22 +17,28 @@ export default function ProxyBody() { if (!apiPrefix) { return; } - try { - const xhr = new XMLHttpRequest(); - xhr.withCredentials = true; - xhr.open('GET', `${apiPrefix}/api/system/get-version-a`); - xhr.onload = function () { - if (xhr.status === 200) { - localStorage.setItem('_BaseURL', apiPrefix); - location.reload(); - } else { - message.error(i18n('setting.message.urlTestError')); - } - }; - xhr.send(); - } catch { - message.error(i18n('setting.message.urlTestError')); - } + outSideService.dynamicUrl(`${apiPrefix}/api/system/get-version-a`).then((res: any) => { + localStorage.setItem('_BaseURL', apiPrefix); + location.reload(); + }).catch((err: any) => { + message.error(i18n('setting.message.urlTestError')) + }); + // try { + // const xhr = new XMLHttpRequest(); + // xhr.withCredentials = true; + // xhr.open('GET', `${apiPrefix}/api/system/get-version-a`); + // xhr.onload = function () { + // if (xhr.status === 200) { + // localStorage.setItem('_BaseURL', apiPrefix); + // location.reload(); + // } else { + // message.error(i18n('setting.message.urlTestError')); + // } + // }; + // xhr.send(); + // } catch { + // message.error(i18n('setting.message.urlTestError')); + // } } return ( diff --git a/chat2db-client/src/components/ConnectionEdit/index.tsx b/chat2db-client/src/components/ConnectionEdit/index.tsx index 8251d617d..830892342 100644 --- a/chat2db-client/src/components/ConnectionEdit/index.tsx +++ b/chat2db-client/src/components/ConnectionEdit/index.tsx @@ -61,7 +61,7 @@ const CreateConnection = forwardRef(function (props: IProps, ref: ForwardedRef { return { value: t.id, - label: t.shortName + label: t.name } })); }, [connectionEnvList]); diff --git a/chat2db-client/src/i18n/en-us/login.ts b/chat2db-client/src/i18n/en-us/login.ts index e455739f0..4167bb653 100644 --- a/chat2db-client/src/i18n/en-us/login.ts +++ b/chat2db-client/src/i18n/en-us/login.ts @@ -9,4 +9,5 @@ export default { 'login.form.password': 'Password', 'login.form.password.placeholder': 'Please enter your password', 'login.button.login': 'Login', + 'login.tips.defaultPassword': 'The default user name and password are: chat2db', }; diff --git a/chat2db-client/src/i18n/zh-cn/login.ts b/chat2db-client/src/i18n/zh-cn/login.ts index 9ea27fe10..e64b1338b 100644 --- a/chat2db-client/src/i18n/zh-cn/login.ts +++ b/chat2db-client/src/i18n/zh-cn/login.ts @@ -9,4 +9,5 @@ export default { 'login.form.password': '密码', 'login.form.password.placeholder': '请输入密码', 'login.button.login': '登 录', + 'login.tips.defaultPassword': '默认用户名密码为: chat2db', }; diff --git a/chat2db-client/src/pages/login/index.less b/chat2db-client/src/pages/login/index.less index c8df43fdc..97cdc73a2 100644 --- a/chat2db-client/src/pages/login/index.less +++ b/chat2db-client/src/pages/login/index.less @@ -45,12 +45,13 @@ text-align: center; } .whyLogin { - margin-top: 8px; + margin: 8px auto 0px; text-align: center; font-size: 12px; line-height: 24px; opacity: 0.4; cursor: pointer; + max-width: fit-content; &:hover { opacity: 0.6; @@ -60,6 +61,10 @@ } } +.defaultPasswordTips{ + opacity: 0.6; +} + .loginForm { margin-top: 36px; .loginFormSubmit { diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 828e01efa..2235bdd9c 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -40,7 +40,7 @@ const App: React.FC = () => { >
{i18n('login.text.tips.title')}
- +
{ +
{i18n('login.tips.defaultPassword')}
diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index d1a7f7aa2..918e64218 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -58,6 +58,10 @@ .f-single-line(); } + .envTag{ + margin: 0px 4px; + } + .moreButton { flex-shrink: 0; display: none; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 94ebd3bb3..9b8e7db2a 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -7,7 +7,7 @@ import RefreshLoadingButton from '@/components/RefreshLoadingButton'; import connectionService from '@/service/connection'; import { DatabaseTypeCode, databaseMap, databaseTypeList, ConnectionKind } from '@/constants'; import { IDatabase, IConnectionDetails, IConnectionEnv } from '@/typings'; -import { Button, Dropdown, Modal } from 'antd'; +import { Button, Dropdown, Modal, Tag } from 'antd'; import styles from './index.less'; import { connect, history, Dispatch } from 'umi'; import { IConnectionModelType } from '@/models/connection'; @@ -162,7 +162,8 @@ function Connections(props: IProps) { >
{icon} - {label} + {t.env.shortName} + {label}
{ }); export default function createRequest

(url: string, options?: IOptions) { - const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside, isFullPath } = options || {}; + const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside, isFullPath, dynamicUrl } = options || {}; // 是否需要mock let _baseURL = (mock ? mockUrl : baseURL) || ''; @@ -166,6 +167,11 @@ export default function createRequest

(url: string, options?: I let eventualUrl = outside ? `${outsideUrlPrefix}${_url}` : `${_baseURL}${_url}`; eventualUrl = isFullPath ? url : eventualUrl; + // 动态的url + if(dynamicUrl){ + eventualUrl = params as string; + } + request[method](eventualUrl, { [dataName]: params }) .then((res) => { if (!res) return; diff --git a/chat2db-client/src/service/outside.ts b/chat2db-client/src/service/outside.ts index 9a0a46e77..b22973f49 100644 --- a/chat2db-client/src/service/outside.ts +++ b/chat2db-client/src/service/outside.ts @@ -6,6 +6,11 @@ const checkVersion = createRequest('/api/client/version/ outside: true, }); +const dynamicUrl = createRequest('', { + dynamicUrl: true, +}); + export default { + dynamicUrl, checkVersion, }; From 54558e894c87735f84f527582be5b6c0a630ec54 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 9 Sep 2023 19:43:36 +0800 Subject: [PATCH 0751/1069] Desktop cookie --- chat2db-client/src/service/base.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 25dd3b920..11039f392 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -101,8 +101,8 @@ request.interceptors.request.use((url, options) => { ...options.headers, }, }; - if (localStorage.getItem('DBHUB')) { - myOptions.headers.DBHUB = localStorage.getItem('DBHUB'); + if (localStorage.getItem('Chat2db')) { + myOptions.headers.Chat2db = localStorage.getItem('Chat2db'); } return { options: myOptions, @@ -112,9 +112,9 @@ request.interceptors.request.use((url, options) => { request.interceptors.response.use(async (response, options) => { const res = await response.clone().json(); if (__ENV__ === 'desktop') { - const DBHUB = response.headers.get('DBHUB') || ''; - if (DBHUB) { - localStorage.setItem('DBHUB', DBHUB); + const Chat2db = response.headers.get('Chat2db') || ''; + if (Chat2db) { + localStorage.setItem('Chat2db', Chat2db); } } const { errorCode, codeMessage } = res; From 16b042b5733dead676c5359a3e194b5bd3a3fd67 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 20:19:26 +0800 Subject: [PATCH 0752/1069] Fix some team project bugs --- .../src/main/resources/mapper/DataSourceCustomMapper.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml index fda92baff..3c01bac27 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/DataSourceCustomMapper.xml @@ -20,7 +20,7 @@ or exists ( select 1 from DATA_SOURCE_ACCESS dsa - LEFT JOIN TEAM_USER tu on tu.ID = dsa.ACCESS_OBJECT_ID and dsa.ACCESS_OBJECT_TYPE = 'TEAM' + LEFT JOIN TEAM_USER tu on tu.TEAM_ID = dsa.ACCESS_OBJECT_ID and dsa.ACCESS_OBJECT_TYPE = 'TEAM' left join TEAM t on t.id = tu.TEAM_ID where tu.USER_ID = #{userId} and dsa.DATA_SOURCE_ID = ds.ID From b708e9a2437b16a45f507179fe06b40642a98f0e Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 20:31:22 +0800 Subject: [PATCH 0753/1069] Fix some team project bugs --- .../resources/i18n/messages_zh_CN.properties | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index 212a36c00..90c313c23 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -1,30 +1,30 @@ -common.businessError=????????????? -common.systemError=??????????????????????? -common.needLoggedIn=?????? -common.redirect=????? -common.paramError=???????? -common.paramDetailError=??????:{0},???? -common.paramCheckError=???????: -common.maxUploadSize=???????????? -common.permissionDenied=?????????? -common.dataNotFound=????????? -common.dataAlreadyExists=??????????? -common.dataAlreadyExistsWithParam=???????????,{0}:{1} +common.businessError=볢ύˢҳ +common.systemError=ϵͳ쳣ڰе־鿴쳣顣 +common.needLoggedIn=Ҫ½ҳ +common.redirect=ضҳ +common.paramError=IJ쳣 +common.paramDetailError=IJ:{0},쳣 +common.paramCheckError=²: +common.maxUploadSize=ļ +common.permissionDenied=ûȨ޷ʸҳ +common.dataNotFound=ʵݲ +common.dataAlreadyExists=ݿѾڸ +common.dataAlreadyExistsWithParam=ݿѾڸ,{0}:{1} -oauth.userNameNotExits=??????? -oauth.IllegalUserName=??????????????? -oauth.passwordIncorrect=???????? -oauth.invalidUserName=?????????? +oauth.userNameNotExits=ǰ˺Ų +oauth.IllegalUserName=ǰ˺޷¼뻻һ˺ +oauth.passwordIncorrect= +oauth.invalidUserName=˺ѾʧЧ -dataSource.sqlAnalysisError=???????? -connection.error=???????????????? -connection.ssh.error=SSH ????????SSH?? -connection.driver.load.error=????????????????? +dataSource.sqlAnalysisError=Ϸִ +connection.error=ݿ쳣ݿ +connection.ssh.error=SSH 쳣SSH +connection.driver.load.error=ݿ쳣 # sqlResult -sqlResult.rowNumber=?? -sqlResult.success=???? +sqlResult.rowNumber=к +sqlResult.success=ִгɹ -user.canNotOperateSystemAccount=???????? -execute.exportCsv=?????????csv +user.canNotOperateSystemAccount=ܲϵͳ˺ +execute.exportCsv=csv -settings.permissionDeniedForAiConfig=??????? ???->???AI? ????ApiKey \ No newline at end of file +settings.permissionDeniedForAiConfig=ϵԱ ->ԶAI ApiKey \ No newline at end of file From 3b72309861b4ee5b72675375abe44f03b2331f0b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 9 Sep 2023 20:33:34 +0800 Subject: [PATCH 0754/1069] fix:bug --- chat2db-client/src/i18n/en-us/team.ts | 1 + .../src/pages/main/workspace/components/TableList/index.tsx | 5 +++-- .../main/workspace/components/WorkspaceHeader/index.tsx | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/i18n/en-us/team.ts b/chat2db-client/src/i18n/en-us/team.ts index af0a6ca10..2abac67ee 100644 --- a/chat2db-client/src/i18n/en-us/team.ts +++ b/chat2db-client/src/i18n/en-us/team.ts @@ -8,6 +8,7 @@ export default { 'team.action.editDatasource': 'Edit DataSource', 'team.action.addDatasource': 'Add DataSource', 'team.action.addDatasource.placeholder': 'Search DataSource', + 'team.action.editUser': 'Edit user', 'team.action.addUser': 'Add User', 'team.action.addUser.placeholder': 'Search User', 'team.action.editTeam': 'Edit Team', 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 479d4ef0f..1889d9a0c 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -35,7 +35,6 @@ const dvaModel = connect( ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ connectionModel: connection, workspaceModel: workspace, - tableLoading: loading.effects['workspace/fetchGetCurTableList'], databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'], }), ); @@ -59,7 +58,7 @@ const TableList = dvaModel(function (props: any) { useEffect(() => { setCurList([]); if (isReady) { - setCurType({...optionsList[0]}); + setCurType({ ...optionsList[0] }); } }, [curWorkspaceParams]); @@ -88,6 +87,8 @@ const TableList = dvaModel(function (props: any) { payload: res, }) } + }).catch(() => { + setTableLoading(false); }) } 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 761b52712..3d5e3a976 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -70,8 +70,8 @@ const WorkspaceHeader = memo((props) => { getDatabaseList(isRefresh); setIsRefresh(false); } - // connectionList转换成可用的ConnectionOptions - setConnectionOptions(connectionList?.map(t => { + // connectionList转换成可用的ConnectionOptions + setConnectionOptions(connectionList?.map(t => { return { value: t.id, label: t.alias, @@ -232,7 +232,7 @@ const WorkspaceHeader = memo((props) => { >

- {curConnection?.id && {curConnection?.environment?.shortName}} + {(curConnection?.id && curConnection?.environment?.shortName) && {curConnection?.environment?.shortName}}
{curWorkspaceParams.dataSourceName}
From 7eafdb0b36f36b82512b7ecfd61ef9b5df5ec3e9 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 20:34:19 +0800 Subject: [PATCH 0755/1069] Fix some team project bugs --- .../resources/i18n/messages_zh_CN.properties | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index 90c313c23..ee403cef8 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -1,30 +1,30 @@ -common.businessError=볢ύˢҳ -common.systemError=ϵͳ쳣ڰе־鿴쳣顣 -common.needLoggedIn=Ҫ½ҳ -common.redirect=ضҳ -common.paramError=IJ쳣 -common.paramDetailError=IJ:{0},쳣 -common.paramCheckError=²: -common.maxUploadSize=ļ -common.permissionDenied=ûȨ޷ʸҳ -common.dataNotFound=ʵݲ -common.dataAlreadyExists=ݿѾڸ -common.dataAlreadyExistsWithParam=ݿѾڸ,{0}:{1} +common.businessError=请尝试重新提交或者刷新页面 +common.systemError=系统发生异常,可在帮助中点击日志查看异常详情。 +common.needLoggedIn=需要登陆页面 +common.redirect=重定向页面 +common.paramError=您输入的参数异常 +common.paramDetailError=您输入的参数:{0},存在异常 +common.paramCheckError=请检查以下参数: +common.maxUploadSize=您输入的文件超过最大限制 +common.permissionDenied=您没有权限访问该页面 +common.dataNotFound=您访问的数据不存在 +common.dataAlreadyExists=数据库总已经存在该数据 +common.dataAlreadyExistsWithParam=数据库总已经存在该数据,{0}:{1} -oauth.userNameNotExits=ǰ˺Ų -oauth.IllegalUserName=ǰ˺޷¼뻻һ˺ -oauth.passwordIncorrect= -oauth.invalidUserName=˺ѾʧЧ +oauth.userNameNotExits=当前账号不存在 +oauth.IllegalUserName=当前账号无法登录,请换一个账号 +oauth.passwordIncorrect=您输入的密码有误 +oauth.invalidUserName=您输入的账号已经失效 -dataSource.sqlAnalysisError=Ϸִ -connection.error=ݿ쳣ݿ -connection.ssh.error=SSH 쳣SSH -connection.driver.load.error=ݿ쳣 +dataSource.sqlAnalysisError=不合法的执行语句 +connection.error=数据库链接异常,请检查数据库配置 +connection.ssh.error=SSH 链接异常,请检查SSH配置 +connection.driver.load.error=数据库驱动加载异常,请检查驱动配置 # sqlResult -sqlResult.rowNumber=к -sqlResult.success=ִгɹ +sqlResult.rowNumber=行号 +sqlResult.success=执行成功 -user.canNotOperateSystemAccount=ܲϵͳ˺ -execute.exportCsv=csv +user.canNotOperateSystemAccount=不能操作系统账号 +execute.exportCsv=更多数据请点击导出csv -settings.permissionDeniedForAiConfig=ϵԱ ->ԶAI ApiKey \ No newline at end of file +settings.permissionDeniedForAiConfig=请联系管理员在 “设置->自定义AI” 里面设置ApiKey \ No newline at end of file From a237f2e82788860d52044f890955f029fb069f3f Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 21:13:47 +0800 Subject: [PATCH 0756/1069] Fix some team project bugs --- .../chat2db/server/start/config/config/AutomaticUpgrade.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java index ef70f39b4..af9f3c96a 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java @@ -112,6 +112,9 @@ private boolean needUpdate(Upgrade upgrade, String localVersion) { String[] versionArray = upgrade.getVersion().split("\\."); String[] localVersionArray = localVersion.split("\\."); for (int i = 0; i < versionArray.length; i++) { + if (Long.parseLong(versionArray[i]) < Long.parseLong(localVersionArray[i])) { + return false; + } if (Long.parseLong(versionArray[i]) > Long.parseLong(localVersionArray[i])) { return true; } From bddf0e44240a21ab0d3440c86f17c96287cedf48 Mon Sep 17 00:00:00 2001 From: JiaJu Zhuang <5152853@qq.com> Date: Sat, 9 Sep 2023 21:50:30 +0800 Subject: [PATCH 0757/1069] Add Publishing Branch --- docker/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 9991ee1d0..b3d40f26f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -8,4 +8,5 @@ ADD chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db- ADD chat2db-server/chat2db-server-start/target/lib lib # 让当前容器暴露10824 EXPOSE 10824 -# 运行jar包ENTRYPOINT ["java","-Dloader.path=lib","-Dspring.profiles.active=release","-jar","chat2db-server-start.jar"] +# 运行jar包 +ENTRYPOINT ["java","-Dloader.path=lib","-Dspring.profiles.active=release","-jar","chat2db-server-start.jar"] From 53c48328b47219c3de5ed421a54ba8117bbe5b5c Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 10 Sep 2023 10:16:10 +0800 Subject: [PATCH 0758/1069] feat: Optimize code --- chat2db-client/src/i18n/zh-cn/login.ts | 2 +- chat2db-client/src/pages/login/index.less | 5 +++-- chat2db-client/src/pages/login/index.tsx | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/i18n/zh-cn/login.ts b/chat2db-client/src/i18n/zh-cn/login.ts index e64b1338b..81e1d446e 100644 --- a/chat2db-client/src/i18n/zh-cn/login.ts +++ b/chat2db-client/src/i18n/zh-cn/login.ts @@ -9,5 +9,5 @@ export default { 'login.form.password': '密码', 'login.form.password.placeholder': '请输入密码', 'login.button.login': '登 录', - 'login.tips.defaultPassword': '默认用户名密码为: chat2db', + 'login.tips.defaultPassword': '默认用户名和密码均为: chat2db', }; diff --git a/chat2db-client/src/pages/login/index.less b/chat2db-client/src/pages/login/index.less index 97cdc73a2..882be143d 100644 --- a/chat2db-client/src/pages/login/index.less +++ b/chat2db-client/src/pages/login/index.less @@ -14,7 +14,7 @@ position: fixed; top: 32px; left: 32px; - app-region: 'no-drag'; + // app-region: 'no-drag'; display: flex; align-items: center; @@ -62,7 +62,8 @@ } .defaultPasswordTips{ - opacity: 0.6; + opacity: 0.3; + text-align: center; } .loginForm { diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 4e7848303..9f67ed49b 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -42,7 +42,7 @@ const App: React.FC = () => { >
{i18n('login.text.tips.title')}
- + Date: Tue, 12 Sep 2023 15:36:13 +0800 Subject: [PATCH 0759/1069] =?UTF-8?q?=E8=A1=A8=E7=BB=93=E6=9E=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chat2db-client/src/i18n/en-us/common.ts | 5 + chat2db-client/src/i18n/zh-cn/common.ts | 5 + .../workspace/components/TableList/index.tsx | 73 +++++++- .../components/WorkspaceLeft/index.tsx | 14 +- chat2db-client/src/typings/resultTable.ts | 5 + .../domain/api/enums/ExportFileSuffix.java | 33 ++++ .../domain/api/enums/ExportTypeEnum.java | 27 ++- .../server/domain/api/model/IndexInfo.java | 34 ++++ .../domain/api/model/TableParameter.java | 47 +++++ .../resources/i18n/messages_en_US.properties | 15 ++ .../resources/i18n/messages_zh_CN.properties | 15 ++ .../resources/template/sub_template_diy.docx | Bin 0 -> 17955 bytes .../src/main/resources/template/template.html | 39 ++++ .../main/resources/template/template_diy.docx | Bin 0 -> 17570 bytes .../tools/common/config/GlobalDict.java | 24 +++ .../server/tools/common/util/ConfigUtils.java | 45 ++++- .../chat2db-server-web-api/pom.xml | 17 ++ .../api/controller/rdb/RdbDocController.java | 90 ++++++++++ .../rdb/converter/RdbWebConverter.java | 15 ++ .../rdb/doc/DatabaseExportService.java | 167 ++++++++++++++++++ .../adaptive/CustomCellWriteHeightConfig.java | 46 +++++ .../adaptive/CustomCellWriteWidthConfig.java | 79 +++++++++ .../rdb/doc/conf/ExportOptions.java | 25 +++ .../rdb/doc/constant/CommonConstant.java | 31 ++++ .../rdb/doc/constant/PatternConstant.java | 51 ++++++ .../rdb/doc/export/ExportExcelService.java | 54 ++++++ .../rdb/doc/export/ExportHtmlService.java | 109 ++++++++++++ .../rdb/doc/export/ExportMarkdownService.java | 93 ++++++++++ .../rdb/doc/export/ExportPdfService.java | 115 ++++++++++++ .../doc/export/ExportWordSuperService.java | 122 +++++++++++++ .../rdb/doc/merge/MyMergeExcel.java | 47 +++++ .../rdb/doc/style/CustomExcelStyle.java | 60 +++++++ .../rdb/factory/ExportServiceFactory.java | 45 +++++ .../server/web/api/util/AddToTopic.java | 45 +++++ .../server/web/api/util/StringUtils.java | 96 ++++++++++ 35 files changed, 1676 insertions(+), 12 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/ExportFileSuffix.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/IndexInfo.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/model/TableParameter.java create mode 100644 chat2db-server/chat2db-server-start/src/main/resources/template/sub_template_diy.docx create mode 100644 chat2db-server/chat2db-server-start/src/main/resources/template/template.html create mode 100644 chat2db-server/chat2db-server-start/src/main/resources/template/template_diy.docx create mode 100644 chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/config/GlobalDict.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteHeightConfig.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteWidthConfig.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportHtmlService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportPdfService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportWordSuperService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/merge/MyMergeExcel.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/style/CustomExcelStyle.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/factory/ExportServiceFactory.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/AddToTopic.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/StringUtils.java diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 96bf9faf3..a681065c3 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -45,6 +45,11 @@ export default { 'common.button.copy': 'Copy', 'common.button.copySuccessfully': 'Copy Successfully', 'common.button.createConsole': 'Create Console', + 'common.button.exportWord': 'Export to Word', + 'common.button.exportExcel': 'Export to Excel', + 'common.button.exportHtml': 'Export to Html', + 'common.button.exportMarkdown': 'Export to Markdown', + 'common.button.exportPdf': 'Export to Pdf', 'common.text.successfulExecution': 'Successful Execution', 'common.text.result': 'Result', 'common.text.timeConsuming': 'Time Consuming', diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 4610e36ae..46950fb78 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -45,6 +45,11 @@ export default { 'common.button.copy': '复制', 'common.button.copySuccessfully': '复制成功', 'common.button.createConsole': '新建控制台', + 'common.button.exportWord': '导出到Word', + 'common.button.exportExcel': '导出到Excel', + 'common.button.exportHtml': '导出到Html', + 'common.button.exportMarkdown': '导出到Markdown', + 'common.button.exportPdf': '导出到Pdf', 'common.text.successfulExecution': '执行成功', 'common.text.result': '结果', 'common.text.timeConsuming': '耗时', 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 479d4ef0f..3a9243430 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -2,18 +2,20 @@ import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'r import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Input, Cascader } from 'antd'; +import { Input, Cascader, Button, Space, Dropdown, MenuProps } from 'antd'; 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 { ITreeNode } from '@/typings'; +import { IManageResultData, IResultConfig, ITreeNode } from '@/typings'; import { TreeNodeType } from '@/constants'; import styles from './index.less'; import { approximateTreeNode } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; +import { DownOutlined } from '@ant-design/icons'; +import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; interface IOption { value: TreeNodeType; @@ -40,8 +42,12 @@ const dvaModel = connect( }), ); -const TableList = dvaModel(function (props: any) { - const { workspaceModel, dispatch } = props; +interface IDocProps { + onExport: (exportType: ExportTypeEnum) => void; +} + +const TableList = dvaModel(function (props: IDocProps) { + const { workspaceModel, dispatch, onExport } = props; const { curWorkspaceParams, curTableList, curViewList } = workspaceModel; const [searching, setSearching] = useState(false); const inputRef = useRef(); @@ -51,6 +57,57 @@ const TableList = dvaModel(function (props: any) { const [curList, setCurList] = useState([]); const [tableLoading, setTableLoading] = useState(false); + // 导出表结构 + const handleExport = (exportType: ExportTypeEnum) => { + props.onExport && props.onExport(exportType); + }; + + const items: MenuProps['items'] = useMemo( + () => [ + { + label: i18n('common.button.exportWord'), + key: '1', + // icon: , + onClick: () => { + handleExport(ExportTypeEnum.WORD); + }, + }, + { + label: i18n('common.button.exportExcel'), + key: '2', + // icon: , + onClick: () => { + handleExport(ExportTypeEnum.EXCEL); + }, + }, + { + label: i18n('common.button.exportHtml'), + key: '3', + // icon: , + onClick: () => { + handleExport(ExportTypeEnum.HTML); + }, + }, + { + label: i18n('common.button.exportMarkdown'), + key: '4', + // icon: , + onClick: () => { + handleExport(ExportTypeEnum.MARKDOWN); + }, + }, + { + label: i18n('common.button.exportPdf'), + key: '5', + // icon: , + onClick: () => { + handleExport(ExportTypeEnum.PDF); + }, + }, + ], + [curWorkspaceParams] + ); + useUpdateEffect(() => { setCurList([]); getList(); @@ -147,6 +204,14 @@ const TableList = dvaModel(function (props: any) {
+ + +
refreshTableList()}>
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 7fff45c33..fdf713659 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -16,6 +16,9 @@ import { approximateTreeNode, approximateList } from '@/utils'; import historyService from '@/service/history'; import TableList from '../TableList'; import SaveList from '../SaveList'; +import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; +import { IExportParams } from '@/service/sql'; +import { downloadFile } from '@/utils/common'; interface IProps { className?: string; @@ -85,6 +88,13 @@ const WorkspaceLeft = memo(function (props) { addConsole(); } + const handleExportTableStructure = async ( + exportType: ExportTypeEnum, + ) => { + const params: IExportParams = { ...curWorkspaceParams, originalSql: '', exportType, exportSize: ExportSizeEnum.ALL }; + downloadFile(window._BaseURL + '/api/rdb/doc/export', params); + }; + useEffect(() => { document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 't') { @@ -98,7 +108,9 @@ const WorkspaceLeft = memo(function (props) {
- +
+ + \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/template/template_diy.docx b/chat2db-server/chat2db-server-start/src/main/resources/template/template_diy.docx new file mode 100644 index 0000000000000000000000000000000000000000..bf8c21e675714694272a068dff56d174c22c2cb8 GIT binary patch literal 17570 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 F{6FZR7!v>h literal 0 HcmV?d00001 diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/config/GlobalDict.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/config/GlobalDict.java new file mode 100644 index 000000000..45ae392e8 --- /dev/null +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/config/GlobalDict.java @@ -0,0 +1,24 @@ +package ai.chat2db.server.tools.common.config; + +import ai.chat2db.server.tools.common.util.ConfigUtils; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +/** + * 全局字典 + * + * @author lzy + */ +public interface GlobalDict { + /** + * 模板文件 + **/ + List TEMPLATE_FILE = Arrays.asList("template.html", "template_diy.docx", "sub_template_diy.docx"); + /** + * 模板存放目录 + **/ + String templateDir = ConfigUtils.CONFIG_BASE_PATH + File.separator + "template" + File.separator; + +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java index c34539ebe..9f09d226f 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java @@ -1,13 +1,17 @@ package ai.chat2db.server.tools.common.util; -import java.io.File; - -import com.alibaba.fastjson2.JSON; - +import ai.chat2db.server.tools.common.config.GlobalDict; import ai.chat2db.server.tools.common.model.ConfigJson; import cn.hutool.core.io.FileUtil; +import com.alibaba.fastjson2.JSON; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.DigestUtils; + +import java.io.File; +import java.util.Collections; +import java.util.List; /** * Configure information on the user side @@ -29,16 +33,18 @@ public class ConfigUtils { String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); if (APP_PATH != null) { versionFile = new File( - getAppPath() + File.separator + "versions" + File.separator + "version"); + getAppPath() + File.separator + "versions" + File.separator + "version"); if (!versionFile.exists()) { versionFile = null; } } configFile = new File( - CONFIG_BASE_PATH + File.separator + "config" + File.separator + "config_" + environment + ".json"); + CONFIG_BASE_PATH + File.separator + "config" + File.separator + "config_" + environment + ".json"); if (!configFile.exists()) { FileUtil.writeUtf8String(JSON.toJSONString(new ConfigJson()), configFile); } + //复制模板 + copyTemplateFile(); } public static void updateVersion(String version) { @@ -88,4 +94,31 @@ private static String getAppPath() { return null; } } + + public static void copyTemplateFile() { + try { + ClassPathResource resourceFolder = new ClassPathResource("template"); + // 复制文件夹到目标路径 + if (!getMD5(resourceFolder.getFile().getPath()).equals(getMD5(GlobalDict.templateDir))) { + File targetFolder = new File(CONFIG_BASE_PATH); + FileUtil.copy(resourceFolder.getFile(), targetFolder, true); + } + } catch (Exception e) { + log.error("copy error", e); + } + } + + public static String getMD5(String folderPath) { + File folder = new File(folderPath); + List files = FileUtil.loopFiles(folder); + // 对文件列表进行排序 + Collections.sort(files); + // 拼接文件内容 + StringBuilder stringBuilder = new StringBuilder(); + for (File file : files) { + stringBuilder.append(FileUtil.readUtf8String(file)); + } + // 计算 MD5 值 + return DigestUtils.md5DigestAsHex(stringBuilder.toString().getBytes()); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index dc601d55f..781ecd0b5 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -56,6 +56,23 @@ com.alibaba easyexcel + + + com.deepoove + poi-tl + 1.10.5 + + + + com.itextpdf + itext-asian + 5.2.0 + + + com.itextpdf + itextpdf + 5.5.13 + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java new file mode 100644 index 000000000..64f7acf94 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java @@ -0,0 +1,90 @@ +package ai.chat2db.server.web.api.controller.rdb; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.common.util.EasyEnumUtils; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.factory.ExportServiceFactory; +import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.util.JdbcUtils; +import cn.hutool.core.date.DatePattern; +import com.alibaba.druid.DbType; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +import java.lang.reflect.Constructor; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.util.List; + +/** + * RdbDocController + * + * @author lzy + **/ +@ConnectionInfoAspect +@RequestMapping("/api/rdb/doc") +@Controller +@Slf4j +public class RdbDocController { + + @Autowired + private TableService tableService; + + @Autowired + private RdbWebConverter rdbWebConverter; + + /** + * export data + * + * @param request + */ + @PostMapping("/export") + public void export(@Valid @RequestBody DataExportRequest request, HttpServletResponse response) throws Exception { + ExportTypeEnum exportType = EasyEnumUtils.getEnum(ExportTypeEnum.class, request.getExportType()); + response.setCharacterEncoding("utf-8"); + String fileName = URLEncoder.encode( + request.getDatabaseName() + "_" + LocalDateTime.now().format(DatePattern.PURE_DATETIME_FORMATTER), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"); + TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); + queryParam.setPageNo(1); + queryParam.setPageSize(Integer.MAX_VALUE); + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(true); + tableSelector.setIndexList(true); + PageResult
tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); + List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); + TableQueryParam param = rdbWebConverter.tableRequest2param(request); + for (TableVO tableVO: tableVOS) { + param.setTableName(tableVO.getName()); + tableVO.setColumnList(rdbWebConverter.columnDto2vo(tableService.queryColumns(param))); + tableVO.setIndexList(rdbWebConverter.indexDto2vo(tableService.queryIndexes(param))); + } + Class targetClass = ExportServiceFactory.get(exportType.getCode()); + Constructor constructor = targetClass.getDeclaredConstructor(); + DatabaseExportService databaseExportService = (DatabaseExportService) constructor.newInstance(); + response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + databaseExportService.getSuffix()); + response.setContentType(databaseExportService.getContentType()); + // 设置数据集合 + databaseExportService.setExportList(tableVOS); + databaseExportService.generate(request.getDatabaseName(), response.getOutputStream(), new ExportOptions()); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index 1af4e0ce7..872e03fb5 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -9,6 +9,7 @@ import ai.chat2db.server.domain.api.param.TablePageQueryParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; +import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; import ai.chat2db.server.web.api.controller.rdb.request.DdlRequest; @@ -100,6 +101,20 @@ public abstract class RdbWebConverter { * @return */ public abstract TablePageQueryParam tablePageRequest2param(TableBriefQueryRequest request); + /** + * 参数转换 + * + * @param request + * @return + */ + public abstract TablePageQueryParam tablePageRequest2param(DataExportRequest request); + /** + * 参数转换 + * + * @param request + * @return + */ + public abstract TableQueryParam tableRequest2param(DataExportRequest request); /** * 参数转换 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java new file mode 100644 index 000000000..9967369c7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java @@ -0,0 +1,167 @@ +package ai.chat2db.server.web.api.controller.rdb.doc; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; +import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; +import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.server.web.api.util.StringUtils; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.val; + +import java.io.OutputStream; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * DatabaseExportService + * + * @author lzy + **/ +public class DatabaseExportService { + protected ExportTypeEnum exportTypeEnum; + @Getter + public String suffix; + + @Getter + public String contentType; + /** + * 导出excel 集合 + **/ + @Setter + @Getter + public List exportList; + /** + * 导出word、excel 表信息 集合 + **/ + public static Map> listMap = new LinkedHashMap<>(); + /** + * 导出word 索引 集合 + **/ + public static Map> indexMap = new HashMap<>(0); + /** + * 连接符 + **/ + public final static String JOINER = "---"; + + private void init() { + listMap.clear(); + indexMap.clear(); + } + + public void generate(String databaseName, OutputStream outputStream, ExportOptions exportOptions) { + init(); + exportList.forEach(item -> { + dataAssemble(databaseName, exportOptions, item); + }); + try { + export(outputStream, exportOptions); + } catch (Exception e) { + throw new RuntimeException("导出失败!请联系开发者,邮箱:963565242@qq.com" + e); + } + init(); + } + + /** + * 数据处理 + * + * @param exportOptions 配置信息 + **/ + public void dataAssemble(String databaseName, ExportOptions exportOptions, TableVO item) { + boolean isExportIndex = Optional.ofNullable(exportOptions.getIsExportIndex()).orElse(false); + val t = new TableParameter(); + t.setFieldName(item.getName() + "[" + StringUtils.isNull(item.getComment()) + "]"); + List colForTable = new LinkedList<>(); + for (ColumnVO info : item.getColumnList()) { + val p = new TableParameter(); + p.setFieldName(info.getName()).setColumnDefault(info.getDefaultValue()) + .setColumnComment(info.getComment()) + .setColumnType(info.getColumnType()) + .setLength(String.valueOf(info.getCharacterMaximumLength())).setIsNullAble(String.valueOf(info.getNullable())) + .setDecimalPlaces(String.valueOf(info.getNumericPrecision())); + colForTable.add(p); + } + String key = databaseName + JOINER + t.getFieldName(); + listMap.put(key, colForTable); + if (isExportIndex) { + int index = key.lastIndexOf("["); + String str = key.substring(0, index); + indexMap.put(str, vo2Info(item.getIndexList())); + } + //赋值序号 + for (Map.Entry> map : listMap.entrySet()) { + //赋值序号 + List list = map.getValue(); + IntStream.range(0, list.size()).forEach(x -> { + list.get(x).setNo(String.valueOf(x + 1)); + }); + } + } + + private List vo2Info(List indexList) { + return indexList.stream().map(v -> { + IndexInfo info = new IndexInfo(); + info.setName(v.getName()); + info.setColumnName(v.getColumns()); + info.setIndexType(v.getType()); + info.setComment(v.getComment()); + return info; + }).collect(Collectors.toList()); + } + + /** + * 导出 + * + * @param outputStream 文件流 + **/ + public void export(OutputStream outputStream, ExportOptions exportOptions) { + + } + + /** + * 处理空串或null字符 + * + * @param source 源字符 + * @return java.lang.String + **/ + public String dealWith(String source) { + return StringUtils.isNullOrEmpty(source); + } + + @SneakyThrows + public Object[] getIndexValues(IndexInfo indexInfoVO) { + Object[] values = new Object[IndexInfo.class.getDeclaredFields().length]; + values[0] = dealWith(indexInfoVO.getName()); + values[1] = dealWith(indexInfoVO.getColumnName()); + values[2] = dealWith(indexInfoVO.getIndexType()); + values[3] = dealWith(indexInfoVO.getIndexMethod()); + values[4] = dealWith(indexInfoVO.getComment()); + return values; + } + + @SneakyThrows + public Object[] getColumnValues(TableParameter tableParameter) { + Object[] values = new Object[TableParameter.class.getDeclaredFields().length]; + values[0] = StringUtils.isNull(tableParameter.getNo()); + values[1] = StringUtils.isNull(tableParameter.getFieldName()); + values[2] = StringUtils.isNull(tableParameter.getColumnType()); + values[3] = StringUtils.isNull(tableParameter.getLength()); + values[4] = StringUtils.isNull(tableParameter.getIsNullAble()); + values[5] = StringUtils.isNull(tableParameter.getColumnDefault()); + values[6] = StringUtils.isNull(tableParameter.getDecimalPlaces()); + values[7] = StringUtils.isNull(tableParameter.getColumnComment()); + return values; + } + + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteHeightConfig.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteHeightConfig.java new file mode 100644 index 000000000..66044df5e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteHeightConfig.java @@ -0,0 +1,46 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.adaptive; + +import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.Row; + +import java.util.Iterator; + +/** + * CustomCellWriteHeightConfig + * + * @author lzy + **/ +public class CustomCellWriteHeightConfig extends AbstractRowHeightStyleStrategy { + /** + * 默认高度 + */ + private static final Integer DEFAULT_HEIGHT = 300; + + @Override + protected void setHeadColumnHeight(Row row, int relativeRowIndex) { + } + + @Override + protected void setContentColumnHeight(Row row, int relativeRowIndex) { + Iterator cellIterator = row.cellIterator(); + if (!cellIterator.hasNext()) { + return; + } + + // 默认为 1行高度 + int maxHeight = 1; + while (cellIterator.hasNext()) { + Cell cell = cellIterator.next(); + if (cell.getCellType() == CellType.STRING) { + if (cell.getStringCellValue().contains("\n")) { + int length = cell.getStringCellValue().split("\n").length; + maxHeight = Math.max(maxHeight, length); + } + } + } + + row.setHeight((short) (maxHeight * DEFAULT_HEIGHT)); + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteWidthConfig.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteWidthConfig.java new file mode 100644 index 000000000..a2059e1c3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/adaptive/CustomCellWriteWidthConfig.java @@ -0,0 +1,79 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.adaptive; + +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.metadata.data.CellData; +import com.alibaba.excel.metadata.data.WriteCellData; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy; +import org.apache.commons.collections.CollectionUtils; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Sheet; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * CustomCellWriteWidthConfig + * + * @author lzy + **/ +public class CustomCellWriteWidthConfig extends AbstractColumnWidthStyleStrategy { + private Map> CACHE = new HashMap<>(); + + @Override + protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) { + boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList); + if (needSetWidth) { + Map maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetName(), k -> new HashMap<>(0)); + + Integer columnWidth = this.dataLength(cellDataList, cell, isHead); + if (columnWidth >= 0) { + if (columnWidth > 119) { + columnWidth = 120; + } + + Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex()); + if (maxColumnWidth == null || columnWidth > maxColumnWidth) { + maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth); + Sheet sheet = writeSheetHolder.getSheet(); + sheet.setColumnWidth(cell.getColumnIndex(), Math.min(Double.valueOf(columnWidth * 256 * 1.8).intValue(), 16384)); + } + } + } + } + + /** + * 计算长度 + * @param cellDataList cell数据 + * @param cell cell + * @param isHead 是否是标题 + * @return + */ + private Integer dataLength(List> cellDataList, Cell cell, Boolean isHead) { + if (isHead) { + return cell.getStringCellValue().getBytes().length; + } else { + CellData cellData = cellDataList.get(0); + CellDataTypeEnum type = cellData.getType(); + if (type == null) { + return -1; + } else { + switch (type) { + case STRING: + // 换行符(数据需要提前解析好) + int index = cellData.getStringValue().indexOf("\n"); + return index != -1 ? + cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1; + case BOOLEAN: + return cellData.getBooleanValue().toString().getBytes().length; + case NUMBER: + return cellData.getNumberValue().toString().getBytes().length; + default: + return -1; + } + } + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java new file mode 100644 index 000000000..7f4151a96 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/conf/ExportOptions.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.conf; + +import lombok.Data; + +/** + * 生成选项 + * + * @author lzy + */ +@Data +public class ExportOptions { + /** + * 是否导出多sheet + */ + private Boolean isExportMoreSheet = Boolean.FALSE; + /** + * 是否导出索引 + */ + private Boolean isExportIndex = Boolean.FALSE; + + /** + * 导出文件后缀 + **/ + private String fileSuffix; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java new file mode 100644 index 000000000..b66f94752 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/CommonConstant.java @@ -0,0 +1,31 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.constant; + +import ai.chat2db.server.tools.common.util.I18nUtils; +import lombok.extern.java.Log; + +/** + * CommonConstant + * + * @author lzy + **/ +@Log +public final class CommonConstant { + /** + * 表head + **/ + public static String[] INDEX_HEAD_NAMES = + {I18nUtils.getMessage("main.indexName"), + I18nUtils.getMessage("main.indexFieldName"), + I18nUtils.getMessage("main.indexType"), + I18nUtils.getMessage("main.indexMethod"), + I18nUtils.getMessage("main.indexNote")}; + public static String[] COLUMN_HEAD_NAMES = + {I18nUtils.getMessage("main.fieldNo"), + I18nUtils.getMessage("main.fieldName"), + I18nUtils.getMessage("main.fieldType"), + I18nUtils.getMessage("main.fieldLength"), + I18nUtils.getMessage("main.fieldIfEmpty"), + I18nUtils.getMessage("main.fieldDefault"), + I18nUtils.getMessage("main.fieldDecimalPlaces"), + I18nUtils.getMessage("main.fieldNote")}; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java new file mode 100644 index 000000000..d117cbd2b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java @@ -0,0 +1,51 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.constant; + + +import ai.chat2db.server.tools.common.util.I18nUtils; + +/** + * PatternConstant + * + * @author lzy + **/ +public final class PatternConstant { + + /** + * 公共 + */ + public static final String MD_SPLIT = "|"; + + /** + * Markdown + */ + public static final String TITLE = "# %s"; + public static final String CATALOG = "## %s"; + public static final String ALL_TABLE_HEADER = MD_SPLIT + I18nUtils.getMessage("main.fieldNo") + MD_SPLIT + + I18nUtils.getMessage("main.fieldName") + MD_SPLIT + + I18nUtils.getMessage("main.fieldType") + MD_SPLIT + + I18nUtils.getMessage("main.fieldLength") + MD_SPLIT + + I18nUtils.getMessage("main.fieldIfEmpty") + MD_SPLIT + + I18nUtils.getMessage("main.fieldDefault") + MD_SPLIT + + I18nUtils.getMessage("main.fieldDecimalPlaces") + MD_SPLIT + + I18nUtils.getMessage("main.fieldNote") + MD_SPLIT; + public static String TABLE_BODY = "|%s|%s|%s|%s|%s|%s|%s|%s|"; + public static String TABLE_SEPARATOR = "|:----:|----|----|----|----|----|----|----|"; + public static final String ALL_INDEX_TABLE_HEADER = MD_SPLIT + I18nUtils.getMessage("main.indexName") + MD_SPLIT + + I18nUtils.getMessage("main.indexFieldName") + MD_SPLIT + + I18nUtils.getMessage("main.indexType") + MD_SPLIT + + I18nUtils.getMessage("main.indexMethod") + MD_SPLIT + + I18nUtils.getMessage("main.indexNote") + MD_SPLIT; + public static String INDEX_TABLE_BODY = "|%s|%s|%s|%s|"; + public static String INDEX_TABLE_SEPARATOR = "|:----:|----|----|----|"; + + /** + * Html + */ + public static final String HTML_TITLE = "

{0}

"; + public static final String HTML_CATALOG = "

{1}

"; + public static final String HTML_INDEX_ITEM = "{1}"; + public static String HTML_TABLE_HEADER = "
"; + public static String HTML_TABLE_BODY = ""; + public static String HTML_INDEX_TABLE_HEADER = ""; + public static String HTML_INDEX_TABLE_BODY = ""; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java new file mode 100644 index 000000000..29d10e6c9 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.export; + +import ai.chat2db.server.domain.api.enums.ExportFileSuffix; +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteHeightConfig; +import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteWidthConfig; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.doc.merge.MyMergeExcel; +import ai.chat2db.server.web.api.controller.rdb.doc.style.CustomExcelStyle; +import com.alibaba.excel.EasyExcel; +import com.alibaba.excel.write.style.HorizontalCellStyleStrategy; +import lombok.SneakyThrows; +import lombok.val; + +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * ExportExcelService + * + * @author lzy + **/ +public class ExportExcelService extends DatabaseExportService { + + public ExportExcelService() { + exportTypeEnum = ExportTypeEnum.EXCEL; + suffix = ExportFileSuffix.EXCEL.getSuffix(); + contentType = "text/csv"; + } + + @SneakyThrows + @Override + public void export(OutputStream outputStream, ExportOptions exportOptions) { + List export = new ArrayList<>(); + for (Map.Entry> item : listMap.entrySet()) { + val t = new TableParameter(); + t.setNo(item.getKey()).setColumnComment(MyMergeExcel.NAME); + export.add(t); + export.addAll(item.getValue()); + } + EasyExcel.write(outputStream) + .registerWriteHandler(new HorizontalCellStyleStrategy(CustomExcelStyle.getHeadStyle(), CustomExcelStyle.getContentWriteCellStyle())) + .registerWriteHandler(new CustomCellWriteHeightConfig()) + .registerWriteHandler(new CustomCellWriteWidthConfig()) + .registerWriteHandler(new MyMergeExcel()) + .sheet("表结构") + .doWrite(export); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportHtmlService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportHtmlService.java new file mode 100644 index 000000000..d11cd8947 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportHtmlService.java @@ -0,0 +1,109 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.export; + +import ai.chat2db.server.domain.api.enums.ExportFileSuffix; +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.tools.common.config.GlobalDict; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; +import ai.chat2db.server.web.api.util.StringUtils; +import lombok.SneakyThrows; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.text.MessageFormat; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * ExportHtmlService + * + * @author lzy + **/ +public class ExportHtmlService extends DatabaseExportService { + + public ExportHtmlService() { + exportTypeEnum = ExportTypeEnum.HTML; + suffix = ExportFileSuffix.HTML.getSuffix(); + contentType = "text/html"; + } + + @SneakyThrows + @Override + public void export(OutputStream outputStream, ExportOptions exportOptions) { + Map>>> allMap = listMap.entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + StringBuilder htmlText = new StringBuilder(); + StringBuilder catalogue = new StringBuilder(); + try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { + for (Map.Entry>>> myMap : allMap.entrySet()) { + //数据库名 + String database = myMap.getKey(); + String title = MessageFormat.format(PatternConstant.HTML_TITLE, I18nUtils.getMessage("main.databaseText") + database); + //数据库名-目录 + catalogue.append("
  • ").append(MessageFormat.format(PatternConstant.HTML_INDEX_ITEM, I18nUtils.getMessage("main.databaseText") + + database, I18nUtils.getMessage("main.databaseText") + database)).append("
      "); + htmlText.append(title).append("\n"); + for (Map.Entry> parameterMap : myMap.getValue()) { + //表名 + String tableName = parameterMap.getKey().split("---")[1]; + //表名-目录 + catalogue.append("
    1. ").append(MessageFormat.format(PatternConstant.HTML_INDEX_ITEM, database + tableName, tableName)); + htmlText.append(MessageFormat.format(PatternConstant.HTML_CATALOG, database + tableName, tableName)).append("\n

      "); + //索引Table + if (!indexMap.isEmpty()) { + htmlText.append("
  • 序号字段名类型长度是否为空默认值小数位注释
    %s%s%s%s%s%s%s%s
    名称字段DDL注释
    %s%s%s%s
    \n"); + htmlText.append(PatternConstant.HTML_INDEX_TABLE_HEADER); + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = indexMap.get(name); + for (IndexInfo indexInfo : indexInfoVOList) { + htmlText.append(String.format(PatternConstant.HTML_INDEX_TABLE_BODY, getIndexValues(indexInfo))); + } + htmlText.append("
    \n"); + htmlText.append("\n

    "); + } else { + htmlText.append(String.format(PatternConstant.HTML_INDEX_TABLE_BODY, getIndexValues(new IndexInfo()))); + } + //字段Table + htmlText.append("\n"); + htmlText.append(PatternConstant.HTML_TABLE_HEADER); + List exportList = parameterMap.getValue(); + for (TableParameter tableParameter : exportList) { + htmlText.append(String.format(PatternConstant.HTML_TABLE_BODY, getColumnValues(tableParameter))); + } + htmlText.append("
    \n"); + } + htmlText.append("

    "); + catalogue.append(""); + } + catalogue.append(""); + + String filePath = GlobalDict.templateDir + GlobalDict.TEMPLATE_FILE.get(0); + try (FileInputStream inputStream = new FileInputStream(filePath); + ByteArrayOutputStream result = new ByteArrayOutputStream()) { + byte[] buffer = new byte[1024]; + int length; + while ((length = inputStream.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + String str = result.toString(String.valueOf(StandardCharsets.UTF_8)); + + str = str.replace("${data}", htmlText).replace("${catalogue}", catalogue); + writer.write(str); + } + } + } + + @Override + public String dealWith(String source) { + return StringUtils.isNullForHtml(source); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java new file mode 100644 index 000000000..60978e9fe --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java @@ -0,0 +1,93 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.export; + +import ai.chat2db.server.domain.api.enums.ExportFileSuffix; +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; +import ai.chat2db.server.web.api.util.StringUtils; +import lombok.SneakyThrows; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * ExportMarkdownService + * + * @author lzy + **/ +public class ExportMarkdownService extends DatabaseExportService { + + public ExportMarkdownService() { + exportTypeEnum = ExportTypeEnum.MARKDOWN; + suffix = ExportFileSuffix.MARKDOWN.getSuffix(); + contentType = "text/plain"; + } + + @SneakyThrows + @Override + public void export(OutputStream outputStream, ExportOptions exportOptions) { + Map>>> allMap = listMap.entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + try (BufferedWriter fileWriter = new BufferedWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) { + for (Map.Entry>>> myMap : allMap.entrySet()) { + //数据库名 + String database = myMap.getKey(); + String title = String.format(PatternConstant.TITLE, "数据库:" + database); + fileWriter.write(title); + writeLineSeparator(fileWriter, 2); + for (Map.Entry> parameterMap : myMap.getValue()) { + //表名 + String tableName = parameterMap.getKey().split("---")[1]; + fileWriter.write(String.format(PatternConstant.CATALOG, tableName)); + writeLineSeparator(fileWriter, 1); + //索引Table + if (!indexMap.isEmpty()) { + fileWriter.write(PatternConstant.ALL_INDEX_TABLE_HEADER); + writeLineSeparator(fileWriter, 1); + fileWriter.write(PatternConstant.INDEX_TABLE_SEPARATOR); + writeLineSeparator(fileWriter, 1); + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = indexMap.get(name); + for (int j = 0; j < indexInfoVOList.size(); j++) { + fileWriter.write(String.format(PatternConstant.INDEX_TABLE_BODY, getIndexValues(indexInfoVOList.get(j)))); + writeLineSeparator(fileWriter, 1); + } + writeLineSeparator(fileWriter, 1); + } + writeLineSeparator(fileWriter, 2); + fileWriter.write(PatternConstant.ALL_TABLE_HEADER); + writeLineSeparator(fileWriter, 1); + fileWriter.write(PatternConstant.TABLE_SEPARATOR); + writeLineSeparator(fileWriter, 1); + //字段Table + List exportList = parameterMap.getValue(); + for (TableParameter tableParameter : exportList) { + fileWriter.write(String.format(PatternConstant.TABLE_BODY, getColumnValues(tableParameter))); + writeLineSeparator(fileWriter, 1); + } + writeLineSeparator(fileWriter, 2); + } + } + } + } + + private void writeLineSeparator(BufferedWriter fileWriter, int number) throws IOException { + for (int i = 0; i < number; i++) { + fileWriter.write(System.lineSeparator()); + } + } + + @Override + public String dealWith(String source) { + return StringUtils.isNullForHtml(source); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportPdfService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportPdfService.java new file mode 100644 index 000000000..01a7a7c8a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportPdfService.java @@ -0,0 +1,115 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.export; + +import ai.chat2db.server.domain.api.enums.ExportFileSuffix; +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.tools.common.util.I18nUtils; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; +import com.itextpdf.text.Document; +import com.itextpdf.text.Font; +import com.itextpdf.text.Paragraph; +import com.itextpdf.text.pdf.BaseFont; +import com.itextpdf.text.pdf.PdfPCell; +import com.itextpdf.text.pdf.PdfPTable; +import com.itextpdf.text.pdf.PdfWriter; +import lombok.SneakyThrows; + +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * ExportPdfService + * + * @author lzy + **/ +public class ExportPdfService extends DatabaseExportService { + + public ExportPdfService() { + exportTypeEnum = ExportTypeEnum.PDF; + suffix = ExportFileSuffix.PDF.getSuffix(); + contentType = "application/pdf"; + } + + @SneakyThrows + @Override + public void export(OutputStream outputStream, ExportOptions exportOptions) { + boolean isExportIndex = exportOptions.getIsExportIndex(); + Map>>> allMap = listMap.entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + Document document = new Document(); + PdfWriter pdfWriter = PdfWriter.getInstance(document, outputStream); + pdfWriter.setStrictImageSequence(true); + // 字体设置 + BaseFont baseFont =BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED); + // 创建字体对象 + Font font = new Font(baseFont, 10, Font.NORMAL); + Font headFont = new Font(baseFont, 12, Font.NORMAL); + Font titleFont = new Font(baseFont, 14, Font.BOLD); + document.open(); + //遍历数据 + for (Map.Entry>>> myMap : allMap.entrySet()) { + //数据库名 + String database = myMap.getKey(); + String title = I18nUtils.getMessage("main.databaseText") + database; + Paragraph p = new Paragraph(title, titleFont); + document.add(p); + for (Map.Entry> parameterMap : myMap.getValue()) { + //表名 + String tableName = parameterMap.getKey().split("---")[1]; + Paragraph tableParagraph = new Paragraph(tableName, font); + document.add(tableParagraph); + //索引Table + if (isExportIndex && !indexMap.isEmpty()) { + PdfPTable table = new PdfPTable(CommonConstant.INDEX_HEAD_NAMES.length); + process(table, CommonConstant.INDEX_HEAD_NAMES, font); + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = indexMap.get(name); + for (IndexInfo indexInfo : indexInfoVOList) { + process(table, getIndexValues(indexInfo), font); + } + table.setPaddingTop(5); + document.add(table); + } + document.add(new Paragraph()); + //字段Table + List exportList = parameterMap.getValue(); + PdfPTable table = new PdfPTable(CommonConstant.COLUMN_HEAD_NAMES.length); + //标题、内容 + process(table, CommonConstant.COLUMN_HEAD_NAMES, headFont); + for (TableParameter tableParameter : exportList) { + process(table, getColumnValues(tableParameter), font); + } + // 设置表格上方的空白间距,即向下移动的效果 + table.setSpacingBefore(10f); + table.setSpacingAfter(20f); + //居左对齐 + table.setHorizontalAlignment(PdfPTable.ALIGN_LEFT); + document.add(table); + //分页 + //document.newPage(); + } + } + document.close(); + } + + //设置表格内容 + public static void process(PdfPTable table, T[] line, Font font) { + for (T s : line) { + if (Objects.isNull(s)) { + return; + } + PdfPCell cell = new PdfPCell(new Paragraph(s.toString(), font)); + cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER); + cell.setVerticalAlignment(PdfPCell.ALIGN_CENTER); + cell.setPaddingTop(5); + cell.setPaddingBottom(5); + table.addCell(cell); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportWordSuperService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportWordSuperService.java new file mode 100644 index 000000000..b77c796ce --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportWordSuperService.java @@ -0,0 +1,122 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.export; + +import ai.chat2db.server.domain.api.enums.ExportFileSuffix; +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.domain.api.model.IndexInfo; +import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.tools.common.config.GlobalDict; +import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; +import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; +import ai.chat2db.server.web.api.controller.rdb.doc.constant.CommonConstant; +import ai.chat2db.server.web.api.util.AddToTopic; +import com.deepoove.poi.XWPFTemplate; +import com.deepoove.poi.data.Includes; +import com.deepoove.poi.data.RowRenderData; +import com.deepoove.poi.data.Rows; +import com.deepoove.poi.data.Tables; +import lombok.SneakyThrows; + +import java.io.File; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * WordSuperActionListener + * + * @author lzy + **/ +public class ExportWordSuperService extends DatabaseExportService { + + public ExportWordSuperService() { + exportTypeEnum = ExportTypeEnum.WORD; + suffix = ExportFileSuffix.WORD.getSuffix(); + contentType = "application/msword"; + } + + /** + * Word导出 + **/ + @SneakyThrows + @Override + public void export(OutputStream outputStream, ExportOptions exportOptions) { + boolean isExportIndex = exportOptions.getIsExportIndex(); + String filePath = GlobalDict.templateDir + GlobalDict.TEMPLATE_FILE.get(1); + String subFile = GlobalDict.templateDir + GlobalDict.TEMPLATE_FILE.get(2); + File importWordFile = new File(filePath); + Map>>> allMap = listMap.entrySet() + .stream().collect(Collectors.groupingBy(v -> v.getKey().split("---")[0])); + List> list = new ArrayList<>(); + Map myDataMap = new HashMap<>(2); + //索引表头 + RowRenderData indexHeaderRow = Rows.of(CommonConstant.INDEX_HEAD_NAMES).center().textBold().textColor("000000").bgColor("bfbfbf").create(); + //字段表头 + RowRenderData tableHeaderRow = Rows.of(CommonConstant.COLUMN_HEAD_NAMES).center().textBold().textColor("000000").bgColor("bfbfbf").create(); + for (Map.Entry>>> myMap : allMap.entrySet()) { + //数据库名 + String database = myMap.getKey(); + int i = 1; + for (Map.Entry> parameterMap : myMap.getValue()) { + //初始化容量 3/0.75 + 1 + Map tableData = new HashMap<>(8); + //索引Table + if (isExportIndex) { + String name = parameterMap.getKey().split("\\[")[0]; + List indexInfoVOList = indexMap.get(name); + List rowList = getIndexValues(indexInfoVOList, indexHeaderRow); + tableData.put("indexTable", Tables.create(rowList.toArray(new RowRenderData[0]))); + } + if (i == 1) { + Map map = new HashMap<>(2); + map.put("dataBase", database); + tableData.put("ifDatabase", map); + } + //表名 + String tableName = parameterMap.getKey().split("---")[1]; + tableData.put("number", i); + tableData.put("name", tableName); + List tableParameterList = parameterMap.getValue(); + List rowList = getColumnValues(tableParameterList, tableHeaderRow); + tableData.put("table", Tables.create(rowList.toArray(new RowRenderData[0]))); + i++; + list.add(tableData); + } + } + myDataMap.put("mydata", Includes.ofLocal(subFile).setRenderModel(list).create()); + /*根据模板生成文档*/ + XWPFTemplate template = XWPFTemplate.compile(importWordFile).render(myDataMap); + AddToTopic.generateTOC(template.getXWPFDocument(), outputStream); + } + + @SneakyThrows + public List getColumnValues(List list, RowRenderData tableHeaderRow) { + List rowRenderDataList = new ArrayList<>(); + rowRenderDataList.add(tableHeaderRow); + for (TableParameter tableParameter : list) { + String[] values = Arrays.stream(getColumnValues(tableParameter)).toArray(String[]::new); + rowRenderDataList.add(Rows.of(values).center().create()); + } + return rowRenderDataList; + } + + + @SneakyThrows + public List getIndexValues(List list, RowRenderData tableHeaderRow) { + List rowRenderDataList = new ArrayList<>(); + rowRenderDataList.add(tableHeaderRow); + if (list.isEmpty()) { + String[] values = Arrays.stream(getIndexValues(new IndexInfo())).toArray(String[]::new); + rowRenderDataList.add(Rows.of(values).center().create()); + return rowRenderDataList; + } + for (IndexInfo indexInfo : list) { + String[] values = Arrays.stream(getIndexValues(indexInfo)).toArray(String[]::new); + rowRenderDataList.add(Rows.of(values).center().create()); + } + return rowRenderDataList; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/merge/MyMergeExcel.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/merge/MyMergeExcel.java new file mode 100644 index 000000000..7546842be --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/merge/MyMergeExcel.java @@ -0,0 +1,47 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.merge; + +import com.alibaba.excel.metadata.Head; +import com.alibaba.excel.write.merge.AbstractMergeStrategy; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddress; + +/** + * MyMergeExcel + * + * @author lzy + **/ +public class MyMergeExcel extends AbstractMergeStrategy { + + public static final String NAME = "isTableNameBlank"; + + @Override + protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) { + if (NAME.equals(cell.getStringCellValue())) { + CellRangeAddress region = new CellRangeAddress(cell.getRowIndex(), cell.getRowIndex(), 0, 7); + sheet.addMergedRegion(region); + Row row = sheet.getRow(cell.getRowIndex()); + cell = row.getCell(0); + Workbook workbook = sheet.getWorkbook(); + // 生成一个样式 + CellStyle style = workbook.createCellStyle(); + // 设置这些样式 + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + //设置填充方案 + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + //设置自定义填充颜色 + style.setFillForegroundColor(IndexedColors.GREEN.getIndex()); + // 生成一个字体 + Font font = workbook.createFont(); + font.setBold(true); + font.setFontHeightInPoints((short) 14); + // 把字体应用到当前的样式 + style.setFont(font); + cell.setCellStyle(style); + } + } +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/style/CustomExcelStyle.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/style/CustomExcelStyle.java new file mode 100644 index 000000000..803d7b0ea --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/style/CustomExcelStyle.java @@ -0,0 +1,60 @@ +package ai.chat2db.server.web.api.controller.rdb.doc.style; + +import com.alibaba.excel.write.metadata.style.WriteCellStyle; +import com.alibaba.excel.write.metadata.style.WriteFont; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.VerticalAlignment; + +/** + * CustomExcelStyle + * + * @author lzy + **/ +public class CustomExcelStyle { + public static WriteCellStyle getContentWriteCellStyle() { + //内容样式策略 + WriteCellStyle contentWriteCellStyle = new WriteCellStyle(); + //垂直居中,水平居中 + contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); + contentWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex()); + contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + contentWriteCellStyle.setBorderLeft(BorderStyle.THIN); + contentWriteCellStyle.setBorderTop(BorderStyle.THIN); + contentWriteCellStyle.setBorderRight(BorderStyle.THIN); + contentWriteCellStyle.setBorderBottom(BorderStyle.THIN); + //设置 自动换行 + contentWriteCellStyle.setWrapped(true); + // 字体策略 + WriteFont contentWriteFont = new WriteFont(); + // 字体大小 + contentWriteFont.setFontHeightInPoints((short) 11); + contentWriteFont.setFontName("宋体"); + contentWriteFont.setColor(IndexedColors.BLACK.getIndex()); + contentWriteCellStyle.setWriteFont(contentWriteFont); + return contentWriteCellStyle; + } + + public static WriteCellStyle getHeadStyle() { + //头策略使用默认 设置字体大小 + WriteCellStyle headWriteCellStyle = new WriteCellStyle(); + + headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER); + headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER); + headWriteCellStyle.setBorderLeft(BorderStyle.THIN); + headWriteCellStyle.setBorderTop(BorderStyle.THIN); + headWriteCellStyle.setBorderRight(BorderStyle.THIN); + headWriteCellStyle.setBorderBottom(BorderStyle.THIN); + + headWriteCellStyle.setWrapped(false); + + WriteFont headWriteFont = new WriteFont(); + headWriteFont.setFontHeightInPoints((short) 14); + headWriteFont.setBold(true); + headWriteFont.setFontName("宋体"); + headWriteCellStyle.setWriteFont(headWriteFont); + headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex()); + return headWriteCellStyle; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/factory/ExportServiceFactory.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/factory/ExportServiceFactory.java new file mode 100644 index 000000000..579ee10f5 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/factory/ExportServiceFactory.java @@ -0,0 +1,45 @@ +package ai.chat2db.server.web.api.controller.rdb.factory; + +import ai.chat2db.server.domain.api.enums.ExportTypeEnum; +import ai.chat2db.server.web.api.controller.rdb.doc.export.*; +import lombok.SneakyThrows; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * ExportServiceFactory + * + * @author lzy + **/ +public class ExportServiceFactory { + + /** + * Export实现类缓存池 + */ + private static final Map> REPORT_POOL = new ConcurrentHashMap<>(8); + + static { + REPORT_POOL.put(ExportTypeEnum.EXCEL.name(), ExportExcelService.class); + REPORT_POOL.put(ExportTypeEnum.WORD.name(), ExportWordSuperService.class); + REPORT_POOL.put(ExportTypeEnum.MARKDOWN.name(), ExportMarkdownService.class); + REPORT_POOL.put(ExportTypeEnum.HTML.name(), ExportHtmlService.class); + REPORT_POOL.put(ExportTypeEnum.PDF.name(), ExportPdfService.class); + } + + /** + * 获取对应接口 + * + * @param type 报表类型 + * @return Class + */ + @SneakyThrows + public static Class get(String type) { + Class dataResult = REPORT_POOL.get(type); + if (dataResult == null) { + throw new ClassNotFoundException("no ExportUI was found"); + } else { + return dataResult; + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/AddToTopic.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/AddToTopic.java new file mode 100644 index 000000000..ed067d152 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/AddToTopic.java @@ -0,0 +1,45 @@ +package ai.chat2db.server.web.api.util; + +import org.apache.poi.xwpf.usermodel.XWPFDocument; +import org.apache.poi.xwpf.usermodel.XWPFParagraph; +import org.apache.poi.xwpf.usermodel.XWPFRun; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSimpleField; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STOnOff; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * AddToTopic + * + * @author lzy + **/ +public class AddToTopic { + + public static void generateTOC(XWPFDocument document, OutputStream out) throws IOException { + String findText = "目录哈哈"; + String replaceText = ""; + for (XWPFParagraph p : document.getParagraphs()) { + for (XWPFRun r : p.getRuns()) { + int pos = r.getTextPosition(); + String text = r.getText(pos); + if (text != null && text.contains(findText)) { + text = text.replace(findText, replaceText); + r.setText(text, 0); + addField(p); + // addField(p, "TOC \\h"); + break; + } + } + } + document.write(out); + } + + private static void addField(XWPFParagraph paragraph) { + CTSimpleField ctSimpleField = paragraph.getCTP().addNewFldSimple(); + ctSimpleField.setInstr("TOC \\o \"1-3\" \\h \\z \\u"); + ctSimpleField.setDirty(STOnOff.TRUE); + ctSimpleField.addNewR().addNewT().setStringValue("<>"); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/StringUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/StringUtils.java new file mode 100644 index 000000000..c2a1d4063 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/StringUtils.java @@ -0,0 +1,96 @@ +package ai.chat2db.server.web.api.util; + +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * 添加字符串工具类,为了兼容JB的各种产品,尽量不要用第三方工具包 + * + * @author lzy + */ +@SuppressWarnings("WeakerAccess") +public class StringUtils { + + private static final String EMPTY_STR = "null"; + + /** + * 首字母处理方法 + */ + private static final BiFunction, String> FIRST_CHAR_HANDLER_FUN = (str, firstCharFun) -> { + int strLen; + if (str == null || (strLen = str.length()) == 0) { + return str; + } + + final int firstCodepoint = str.codePointAt(0); + final int newCodePoint = firstCharFun.apply(firstCodepoint); + if (firstCodepoint == newCodePoint) { + // already capitalized + return str; + } + + // cannot be longer than the char array + final int[] newCodePoints = new int[strLen]; + int outOffset = 0; + // copy the first codepoint + newCodePoints[outOffset++] = newCodePoint; + for (int inOffset = Character.charCount(firstCodepoint); inOffset < strLen; ) { + final int codepoint = str.codePointAt(inOffset); + // copy the remaining ones + newCodePoints[outOffset++] = codepoint; + inOffset += Character.charCount(codepoint); + } + return new String(newCodePoints, 0, outOffset); + }; + + public static String isNullOrEmpty(String str) { + if (StringUtils.isEmpty(str) || EMPTY_STR.equals(str)) { + return ""; + } + return str; + } + + public static String isNull(String str) { + if (StringUtils.isEmpty(str) || EMPTY_STR.equals(str)) { + return "--"; + } + return str; + } + + public static String isNullForHtml(String str) { + if (StringUtils.isEmpty(str) || EMPTY_STR.equals(str)) { + return "
    "; + } + return str; + } + + /** + * 判断是空字符串 + * + * @param cs 字符串 + * @return 是否为空 + */ + public static boolean isEmpty(final CharSequence cs) { + return cs == null || cs.length() == 0; + } + + /** + * 首字母大写方法 + * + * @param str 字符串 + * @return 首字母大写结果 + */ + public static String capitalize(final String str) { + return FIRST_CHAR_HANDLER_FUN.apply(str, Character::toTitleCase); + } + + /** + * 首字母小写方法 + * + * @param str 字符串 + * @return 首字母小写结果 + */ + public static String uncapitalize(final String str) { + return FIRST_CHAR_HANDLER_FUN.apply(str, Character::toLowerCase); + } +} From fc72125624a44eab24a36529ef7b9c1c3786c0ad Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Tue, 12 Sep 2023 16:27:16 +0800 Subject: [PATCH 0760/1069] =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/i18n/messages_en_US.properties | 3 ++- .../resources/i18n/messages_zh_CN.properties | 1 + .../rdb/doc/constant/PatternConstant.java | 16 ++++++++++++++-- .../rdb/doc/export/ExportExcelService.java | 3 ++- .../rdb/doc/export/ExportMarkdownService.java | 3 ++- 5 files changed, 21 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index 46cafab16..610853115 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -29,4 +29,5 @@ main.fieldIfEmpty=Is Nullable main.fieldDefault=Column Default main.fieldDecimalPlaces=Decimal Places main.fieldNote=Column Comment -main.databaseText=Database: \ No newline at end of file +main.databaseText=Database: +main.sheetName=Table Structure \ No newline at end of file diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties index bb86d9832..c85640c41 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_zh_CN.properties @@ -32,3 +32,4 @@ main.fieldDefault=默认值 main.fieldDecimalPlaces=小数位 main.fieldNote=备注 main.databaseText=数据库: +main.sheetName=表结构 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java index d117cbd2b..aa051ecfa 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/constant/PatternConstant.java @@ -44,8 +44,20 @@ public final class PatternConstant { public static final String HTML_TITLE = "

    {0}

    "; public static final String HTML_CATALOG = "

    {1}

    "; public static final String HTML_INDEX_ITEM = "{1}"; - public static String HTML_TABLE_HEADER = "序号字段名类型长度是否为空默认值小数位注释"; + public static String HTML_TABLE_HEADER = "" + I18nUtils.getMessage("main.fieldNo") + + "" + I18nUtils.getMessage("main.fieldName") + + ""+ I18nUtils.getMessage("main.fieldType") + + "" + I18nUtils.getMessage("main.fieldLength") + + "" + I18nUtils.getMessage("main.fieldIfEmpty") + + "" + I18nUtils.getMessage("main.fieldDefault") + + "" + I18nUtils.getMessage("main.fieldDecimalPlaces") + + "" + I18nUtils.getMessage("main.fieldNote") + + ""; public static String HTML_TABLE_BODY = "%s%s%s%s%s%s%s%s"; - public static String HTML_INDEX_TABLE_HEADER = "名称字段DDL注释"; + public static String HTML_INDEX_TABLE_HEADER = "" + I18nUtils.getMessage("main.indexName") + + "" + I18nUtils.getMessage("main.indexFieldName") + + "" + I18nUtils.getMessage("main.indexType") + + "" + I18nUtils.getMessage("main.indexMethod") + + "" + I18nUtils.getMessage("main.indexNote") + ""; public static String HTML_INDEX_TABLE_BODY = "%s%s%s%s"; } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java index 29d10e6c9..79b97a271 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportExcelService.java @@ -3,6 +3,7 @@ import ai.chat2db.server.domain.api.enums.ExportFileSuffix; import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteHeightConfig; import ai.chat2db.server.web.api.controller.rdb.doc.adaptive.CustomCellWriteWidthConfig; @@ -47,7 +48,7 @@ public void export(OutputStream outputStream, ExportOptions exportOptions) { .registerWriteHandler(new CustomCellWriteHeightConfig()) .registerWriteHandler(new CustomCellWriteWidthConfig()) .registerWriteHandler(new MyMergeExcel()) - .sheet("表结构") + .sheet(I18nUtils.getMessage("main.sheetName")) .doWrite(export); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java index 60978e9fe..7c6de40de 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/export/ExportMarkdownService.java @@ -4,6 +4,7 @@ import ai.chat2db.server.domain.api.enums.ExportTypeEnum; import ai.chat2db.server.domain.api.model.IndexInfo; import ai.chat2db.server.domain.api.model.TableParameter; +import ai.chat2db.server.tools.common.util.I18nUtils; import ai.chat2db.server.web.api.controller.rdb.doc.DatabaseExportService; import ai.chat2db.server.web.api.controller.rdb.doc.conf.ExportOptions; import ai.chat2db.server.web.api.controller.rdb.doc.constant.PatternConstant; @@ -41,7 +42,7 @@ public void export(OutputStream outputStream, ExportOptions exportOptions) { for (Map.Entry>>> myMap : allMap.entrySet()) { //数据库名 String database = myMap.getKey(); - String title = String.format(PatternConstant.TITLE, "数据库:" + database); + String title = String.format(PatternConstant.TITLE, I18nUtils.getMessage("main.databaseText") + database); fileWriter.write(title); writeLineSeparator(fileWriter, 2); for (Map.Entry> parameterMap : myMap.getValue()) { From ac208f581e95f5871dacc213439e64e5c01daff6 Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Tue, 12 Sep 2023 16:31:12 +0800 Subject: [PATCH 0761/1069] =?UTF-8?q?=E5=9B=BD=E9=99=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/i18n/messages_en_US.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties index 610853115..013478d92 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties +++ b/chat2db-server/chat2db-server-start/src/main/resources/i18n/messages_en_US.properties @@ -25,7 +25,7 @@ main.fieldNo=No main.fieldName=Field Name main.fieldType=Column Type main.fieldLength=Length -main.fieldIfEmpty=Is Nullable +main.fieldIfEmpty=Nullable main.fieldDefault=Column Default main.fieldDecimalPlaces=Decimal Places main.fieldNote=Column Comment From 6c987524177926ba9f1a5793e36b111e904eb198 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 12 Sep 2023 23:47:07 +0800 Subject: [PATCH 0762/1069] feat:save edit table --- .../DatabaseTableEditor/BaseInfo/index.tsx | 7 +- .../DatabaseTableEditor/ColumnList/index.tsx | 75 ++++++----- .../DatabaseTableEditor/IncludeCol/index.tsx | 109 +++++++++------ .../DatabaseTableEditor/IndexList/index.less | 2 +- .../DatabaseTableEditor/IndexList/index.tsx | 126 +++++++++++------- .../src/blocks/DatabaseTableEditor/index.tsx | 37 +++-- chat2db-client/src/constants/editTable.ts | 11 ++ chat2db-client/src/constants/index.ts | 1 + chat2db-client/src/service/sql.ts | 8 +- chat2db-client/src/typings/database.ts | 31 ----- chat2db-client/src/typings/editTable.ts | 41 ++++++ chat2db-client/src/typings/index.ts | 3 +- 12 files changed, 281 insertions(+), 170 deletions(-) create mode 100644 chat2db-client/src/constants/editTable.ts create mode 100644 chat2db-client/src/typings/editTable.ts diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index 8d84aa0b0..1687b50b2 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -3,6 +3,8 @@ import styles from './index.less'; import classnames from 'classnames'; import { Form, Input } from 'antd'; import { Context } from '../index'; +import { IBaseInfo } from '@/typings'; + export interface IBaseInfoRef { getBaseInfo: () => IBaseInfo; @@ -12,11 +14,6 @@ interface IProps { className?: string; } -export interface IBaseInfo { - name: string; - comment: string; -} - const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => { const { className } = props; const { tableDetails } = useContext(Context); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 5e327b91a..bc409f2c3 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useContext, useEffect, useState } from 'react'; +import React, { memo, useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; @@ -15,20 +15,23 @@ import { } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import sqlService from '@/service/sql'; -import { Context } from '../index' - -interface Item { - key: string; - columnName: string; - length: number | null; - fieldType: string | null; - nullable: boolean; -} +import { Context } from '../index'; +import { IColumnItem } from '@/typings' interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } +interface IProps { + +} + +export type IColumnListInfo = IColumnItem[]; + +export interface IColumnListRef { + getColumnListInfo: () => IColumnListInfo; +} + const Row = ({ children, ...props }: RowProps) => { const { attributes, @@ -69,16 +72,16 @@ const Row = ({ children, ...props }: RowProps) => { ); }; -const ColumnList = memo(() => { +const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) => { const { dataSourceId, databaseName, tableDetails } = useContext(Context); - const [dataSource, setDataSource] = useState([]); + const [dataSource, setDataSource] = useState([]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(''); const [databaseFieldTypeList, setDatabaseFieldTypeList] = useState([]) - const isEditing = (record: Item) => record.key === editingKey; + const isEditing = (record: IColumnItem) => record.key === editingKey; - const edit = (record: Partial & { key: React.Key }) => { + const edit = (record: Partial & { key: React.Key }) => { form.setFieldsValue({ ...record }); setEditingKey(record.key); }; @@ -88,14 +91,14 @@ const ColumnList = memo(() => { const list = tableDetails?.columnList?.map(t => { return { key: uuidv4(), - columnName: t.name, + name: t.name, length: t.dataType, - fieldType: t.columnType, + columnType: t.columnType, nullable: t.nullable === 0, comment: t.comment, } }) || [] - setDataSource(list) + setDataSource(list as any) } }, [tableDetails]) @@ -115,13 +118,13 @@ const ColumnList = memo(() => { align: 'center' }, { - title: 'columnName', - dataIndex: 'columnName', - render: (text: string, record: Item) => { + title: 'name', + dataIndex: 'name', + render: (text: string, record: IColumnItem) => { const editable = isEditing(record); return editable ? ( @@ -140,7 +143,7 @@ const ColumnList = memo(() => { title: 'length', dataIndex: 'length', editable: true, - render: (text: string, record: Item) => { + render: (text: string, record: IColumnItem) => { const editable = isEditing(record); return editable ? ( { } }, { - title: 'fieldType', - dataIndex: 'fieldType', - render: (text: string, record: Item) => { + title: 'columnType', + dataIndex: 'columnType', + width: '140px', + render: (text: string, record: IColumnItem) => { const editable = isEditing(record); return editable ? ( ({ label: i, value: i }))} /> + @@ -205,13 +204,13 @@ export default memo(function IndexList(props) { }, { title: '索引类型', - dataIndex: 'indexesType', - width: '30%', - render: (text: string, record: Item) => { + dataIndex: 'type', + width: '130px', + render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( ({ label: i, value: i }))} + /> + + ) : ( +
    edit(record)} + > + {text} +
    + ) + } +
    + } + }, + { + title: 'nullable', + dataIndex: 'nullable', + width: '100px', + render: (nullable: number, record: IColumnItem) => { const editable = isEditing(record); return editable ? ( - ({ label: i.name, value: i.name }))} - /> + + { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> + : +
    + {t.prefixIcon && } +
    + {t.label} +
    +
    + } +
    + +
    + + } + + return
    +
    + { + !!internalTabs?.length && +
    + { + internalTabs.map((t, index) => { + return renderTabItem(t, index) + }) + } +
    + } + { + !hideAdd &&
    +
    + +
    +
    + } +
    +
    + { + internalTabs?.map(t => { + return
    + {t.children} +
    + }) + } +
    +
    +}) diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 8ed836440..40c5bcf3e 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -33,7 +33,7 @@ export enum OSType { RESTS = 'rests', } -export enum TabType { +export enum WorkspaceTabType { CONSOLE = 'console', FUNCTION = 'function', PROCEDURE = 'procedure', @@ -44,29 +44,29 @@ export enum TabType { } export const tabTypeConfig: { - [key in TabType]: { + [key in WorkspaceTabType]: { icon: string }; } = { - [TabType.CONSOLE]: { + [WorkspaceTabType.CONSOLE]: { icon: '\uec83' }, - [TabType.VIEW]: { + [WorkspaceTabType.VIEW]: { icon: '\ue70c' }, - [TabType.FUNCTION]: { + [WorkspaceTabType.FUNCTION]: { icon: '\ue76a' }, - [TabType.PROCEDURE]: { + [WorkspaceTabType.PROCEDURE]: { icon: '\ue73c' }, - [TabType.TRIGGER]: { + [WorkspaceTabType.TRIGGER]: { icon: '\ue64a' }, - [TabType.EditTable]: { + [WorkspaceTabType.EditTable]: { icon: '\ue6b6' }, - [TabType.OpenTable]: { + [WorkspaceTabType.OpenTable]: { icon: '\ue618' } } diff --git a/chat2db-client/src/constants/workspace.ts b/chat2db-client/src/constants/workspace.ts index 8b9ea89b2..a12c119ce 100644 --- a/chat2db-client/src/constants/workspace.ts +++ b/chat2db-client/src/constants/workspace.ts @@ -3,3 +3,44 @@ export enum CreateTabIntroType { OpenTable = 'openTable', } + +// 工作台Tab的类型 +export enum WorkspaceTabType { + CONSOLE = 'console', + FUNCTION = 'function', + PROCEDURE = 'procedure', + VIEW = 'view', + TRIGGER = 'trigger', + EditTable = 'editTable', + OpenTable = 'openTable', +} + +// 工作台Tab的类型对应的一些配置 +export const workspaceTabConfig: { + [key in WorkspaceTabType]: { + icon: string + }; +} = { + [WorkspaceTabType.CONSOLE]: { + icon: '\uec83' + }, + [WorkspaceTabType.VIEW]: { + icon: '\ue70c' + }, + [WorkspaceTabType.FUNCTION]: { + icon: '\ue76a' + }, + [WorkspaceTabType.PROCEDURE]: { + icon: '\ue73c' + }, + [WorkspaceTabType.TRIGGER]: { + icon: '\ue64a' + }, + [WorkspaceTabType.EditTable]: { + icon: '\ue6b6' + }, + [WorkspaceTabType.OpenTable]: { + icon: '\ue618' + } +} + diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 931cbffb9..750713367 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -25,7 +25,6 @@ export interface IWorkspaceModelState { curConsoleId: number | null; openConsoleList: IConsole[]; curTableList: ITreeNode[]; - curViewList: ITreeNode[]; // 触发tab编辑表或打开表 createTabIntro: ICreateTabIntro | undefined; } @@ -42,7 +41,6 @@ export interface IWorkspaceModelType { setOpenConsoleList: Reducer; setCurConsoleId: Reducer; setCurTableList: Reducer; - setCurViewList: Reducer; setCreateTabIntro: Reducer; }; effects: { @@ -64,7 +62,6 @@ const WorkspaceModel: IWorkspaceModelType = { consoleList: [], openConsoleList: [], curTableList: [], - curViewList: [], curConsoleId: null, createTabIntro: undefined, }, @@ -121,14 +118,6 @@ const WorkspaceModel: IWorkspaceModelType = { curTableList: payload, }; }, - - // 视图列表 - setCurViewList(state, { payload }) { - return { - ...state, - curViewList: payload, - }; - }, // 创建tab的引子 setCreateTabIntro(state, { payload }) { 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 479d4ef0f..9b8ad13d5 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -35,14 +35,12 @@ const dvaModel = connect( ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ connectionModel: connection, workspaceModel: workspace, - tableLoading: loading.effects['workspace/fetchGetCurTableList'], - databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'], }), ); const TableList = dvaModel(function (props: any) { const { workspaceModel, dispatch } = props; - const { curWorkspaceParams, curTableList, curViewList } = workspaceModel; + const { curWorkspaceParams } = workspaceModel; const [searching, setSearching] = useState(false); const inputRef = useRef(); const [searchedTableList, setSearchedTableList] = useState(); @@ -59,12 +57,10 @@ const TableList = dvaModel(function (props: any) { useEffect(() => { setCurList([]); if (isReady) { - setCurType({...optionsList[0]}); + setCurType({ ...optionsList[0] }); } }, [curWorkspaceParams]); - - useEffect(() => { if (searching) { inputRef.current!.focus({ diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 6a1c83a55..6b0291b91 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -4,7 +4,7 @@ import classnames from 'classnames'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import { MenuProps, message, Modal, Input, Dropdown, notification } from 'antd'; -import { TreeNodeType, CreateTabIntroType, TabType } from '@/constants'; +import { TreeNodeType, CreateTabIntroType, WorkspaceTabType } from '@/constants'; import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; import { ITreeNode } from '@/typings'; import connectionServer from '@/service/connection'; @@ -120,7 +120,7 @@ function TreeNodeRightClick(props: IProps) { type: 'workspace/setCreateTabIntro', payload: { type: CreateTabIntroType.EditorTable, - tabType: TabType.EditTable, + workspaceTabType: WorkspaceTabType.EditTable, treeNodeData: data, }, }) @@ -159,6 +159,7 @@ function TreeNodeRightClick(props: IProps) { payload: { ...curWorkspaceParams, extraParams: curWorkspaceParams, + refresh: true, }, callback: () => { message.success(i18n('common.text.submittedSuccessfully')) 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 501de1709..bc7e5e9c1 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -4,15 +4,11 @@ import i18n from '@/i18n'; import { connect } from 'umi'; import { Cascader, Divider, Input, Dropdown, Button, Spin } from 'antd'; import Iconfont from '@/components/Iconfont'; -import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; -import historyServer from '@/service/history'; -import Tree from '../Tree'; -import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus, TabType } from '@/constants'; +import { TreeNodeType, ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType } from '@/constants'; import { IConsole, ITreeNode, ICreateConsole } from '@/typings'; import styles from './index.less'; -import { approximateTreeNode, approximateList } from '@/utils'; import historyService from '@/service/history'; import TableList from '../TableList'; import SaveList from '../SaveList'; @@ -21,16 +17,12 @@ interface IProps { className?: string; workspaceModel: IWorkspaceModelType['state'], dispatch: any; - tableLoading: boolean; - databaseLoading: boolean; } const dvaModel = connect( ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ connectionModel: connection, workspaceModel: workspace, - tableLoading: loading.effects['workspace/fetchGetCurTableList'], - databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'], }), ); @@ -38,30 +30,9 @@ const WorkspaceLeft = memo(function (props) { const { className, workspaceModel, dispatch } = props; const { curWorkspaceParams, openConsoleList } = workspaceModel; - function getConsoleList() { - let p: any = { - pageNo: 1, - pageSize: 999, - orderByDesc: false, - tabOpened: ConsoleOpenedStatus.IS_OPEN, - ...curWorkspaceParams, - }; - - dispatch({ - type: 'workspace/fetchGetSavedConsole', - payload: p, - callback: (res: any) => { - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: res.data, - }) - } - }) - } - - const addConsole = (params?: ICreateConsole) => { + const addConsole = () => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams - let p = { + let params = { name: `new console${openConsoleList?.length || ''}`, ddl: '', dataSourceId: dataSourceId!, @@ -70,29 +41,30 @@ const WorkspaceLeft = memo(function (props) { type: databaseType, status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, - operationType: TabType.CONSOLE + operationType: WorkspaceTabType.CONSOLE, + tabType: WorkspaceTabType.CONSOLE, } - historyService.saveConsole(params || p).then(res => { + + historyService.saveConsole(params).then(res => { dispatch({ type: 'workspace/setCurConsoleId', payload: res, }); - getConsoleList(); + dispatch({ + type: 'workspace/setOpenConsoleList', + payload: [...openConsoleList, { ...params, id: res }], + }) }) } - function createConsole() { - addConsole(); - } - - useEffect(() => { - document.addEventListener('keydown', (e) => { - if ((e.ctrlKey || e.metaKey) && e.key === 't') { - e.preventDefault(); - addConsole(); - } - }, false) - }, []) + // useEffect(() => { + // document.addEventListener('keydown', (e) => { + // if ((e.ctrlKey || e.metaKey) && e.key === 't') { + // e.preventDefault(); + // addConsole(); + // } + // }, false) + // }, []) return (
    @@ -100,7 +72,7 @@ const WorkspaceLeft = memo(function (props) {
    - diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.less similarity index 79% rename from chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less rename to chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.less index 77a28144f..8237ebb8b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.less @@ -1,6 +1,6 @@ @import '../../../../../styles/var.less'; -.box { +.workspaceRight { position: absolute; top: 0; left: 0; @@ -14,19 +14,17 @@ } } -.consoleBox { - position: absolute; - top: 32px; - left: 0; - right: 0; - bottom: 0; - display: none; +.tabs { + height: 100%; + width: 100%; } .tabBox { - height: 32px; - background-color: var(--color-bg-base); - border-bottom: 1px solid var(--color-border); + position: absolute; + top: 0; + right: 0; + left: 0; + bottom: 0; } .activeConsoleBox { diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx similarity index 71% rename from chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx rename to chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index 343c2e637..8dde07c78 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -1,12 +1,13 @@ -import React, { memo, useRef, useEffect, useState } from 'react'; +import React, { memo, useRef, useEffect, useState, useMemo } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; -import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TreeNodeType, tabTypeConfig, TabType } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TreeNodeType } from '@/constants'; import { IConsole, ICreateConsole } from '@/typings'; import historyService from '@/service/history'; import sqlService from '@/service/sql'; import Tabs, { IOption } from '@/components/Tabs'; +import TabsNew, { ITabItem } from '@/components/TabsNew'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; @@ -16,6 +17,8 @@ import { IAIState } from '@/models/ai'; import { handleLocalStorageSavedConsole } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; +import { IWorkspaceTab } from '@/typings' +import { WorkspaceTabType, workspaceTabConfig } from '@/constants'; interface IProps { className?: string; @@ -24,42 +27,42 @@ interface IProps { dispatch: any; } -export interface ITab { - id: number; - tabType: TabType; - icon: string; - [key: string]: any; -} - const WorkspaceRight = memo(function (props) { - const [activeConsoleId, setActiveConsoleId] = useState(); const { className, aiModel, workspaceModel, dispatch } = props; + // 活跃的TabID + const [activeConsoleId, setActiveConsoleId] = useState(); + // 工作台tab列表 + const [workspaceTabList, setWorkspaceTabList] = useState([]); + const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, curConsoleId } = workspaceModel; const openConsoleListRef = useRef(openConsoleList); - const [tabList, setTabList] = useState([]); + // 根据保存的console列表生成tab列表 useEffect(() => { const newTabList = openConsoleList?.map(t => { return { - ...t, - tabType: TabType.CONSOLE, - icon: tabTypeConfig[t.operationType]?.icon || tabTypeConfig.console.icon, + id: t.id, + title: t.name, + type: WorkspaceTabType.CONSOLE, + uniqueData: t } }) - setTabList(newTabList || []) + setWorkspaceTabList(newTabList || []) }, [openConsoleList]) // 监听编辑表事件 useEffect(() => { if (createTabIntro) { - const id: any = uuidV4(); - setTabList([...tabList, { + const id = uuidV4(); + const newData = { id, - tabType: createTabIntro.tabType, - icon: tabTypeConfig[createTabIntro.tabType as TabType]?.icon, - name: `edit-${createTabIntro.treeNodeData.name}`, - tableName: createTabIntro.treeNodeData.name, - }]) + type: createTabIntro.workspaceTabType, + title: `edit-${createTabIntro.treeNodeData.name}`, + uniqueData: { + tableName: createTabIntro.treeNodeData.name, + } + } + setWorkspaceTabList([...workspaceTabList, newData]) setActiveConsoleId(id); // 用完之后就清掉createTabIntro @@ -76,11 +79,6 @@ const WorkspaceRight = memo(function (props) { return; } - dispatch({ - type: 'workspace/setConsoleList', - payload: [], - }) - // 打开视图 if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.VIEW) { const { extraParams } = doubleClickTreeNodeData; @@ -119,7 +117,7 @@ const WorkspaceRight = memo(function (props) { if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TRIGGER) { const { extraParams } = doubleClickTreeNodeData; - const { databaseName, schemaName, triggerName, dataSourceId, } = extraParams || {}; + const { databaseName, schemaName, triggerName, dataSourceId } = extraParams || {}; const name = doubleClickTreeNodeData.name const callback = (consoleId: number) => { sqlService.getTriggerDetail({ @@ -288,7 +286,7 @@ const WorkspaceRight = memo(function (props) { const { doubleClickTreeNodeData, name, callback, ddl } = params; const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {}; - let p: any = { + let newConsole: any = { name, type: databaseType!, dataSourceId: dataSourceId!, @@ -300,9 +298,16 @@ const WorkspaceRight = memo(function (props) { ddl: ddl || '', tabOpened: ConsoleOpenedStatus.IS_OPEN, }; - addConsole({ - newConsole: p, - callback, + historyService.saveConsole(newConsole).then(res => { + + setWorkspaceTabList([...workspaceTabList, { + id: res, + title: newConsole.name, + type: WorkspaceTabType.CONSOLE, + uniqueData: newConsole + }]) + callback?.(res); + setActiveConsoleId(res); }); } @@ -327,26 +332,28 @@ const WorkspaceRight = memo(function (props) { }); } - function onChange(key: any) { + // 切换tab + function onTabChange(key: string | number) { setActiveConsoleId(key); } - const onEdit = (action: 'add' | 'remove', key?: number) => { + // 删除 新增tab + const onEdit = (action: 'add' | 'remove', data: ITabItem) => { if (action === 'remove') { - closeWindowTab(key!); + const editData = workspaceTabList?.find(t => t.id === data.key); + if (editData?.type !== WorkspaceTabType.EditTable) { + closeWindowTab(data.key as number); + } } if (action === 'add') { addConsole(); } }; - const addConsole = (params?: { - newConsole?: ICreateConsole; - callback?: Function; - }) => { + const addConsole = () => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; - let p = { - name: `new console${openConsoleList?.length}`, + let params = { + name: `new console`, ddl: '', dataSourceId: dataSourceId!, databaseName: databaseName!, @@ -354,68 +361,42 @@ const WorkspaceRight = memo(function (props) { type: databaseType, status: ConsoleStatus.DRAFT, tabOpened: ConsoleOpenedStatus.IS_OPEN, - operationType: TabType.CONSOLE, + operationType: WorkspaceTabType.CONSOLE, }; - historyService.saveConsole(params?.newConsole || p).then((res) => { - params?.callback?.(res); - const callback = () => { - dispatch({ - type: 'workspace/setCurConsoleId', - payload: res, - }); - } - getConsoleList(callback); + historyService.saveConsole(params).then((res) => { + // const callback = () => { + // setActiveConsoleId(res); + // // dispatch({ + // // type: 'workspace/setCurConsoleId', + // // payload: res, + // // }); + // } + // getConsoleList(callback); }); }; const closeWindowTab = (key: number) => { - let newActiveKey = activeConsoleId; - let lastIndex = -1; - openConsoleList?.forEach((item, i) => { - if (item.id === key) { - lastIndex = i - 1; - } - }); - - const newPanes = openConsoleList?.filter((item) => item.id !== key) || []; - if (newPanes.length && newActiveKey === key) { - if (lastIndex >= 0) { - newActiveKey = newPanes[lastIndex].id; - } else { - newActiveKey = newPanes[0].id; - } - } - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: newPanes, - }); - setActiveConsoleId(newActiveKey); - let p: any = { id: key, tabOpened: 'n', }; - - const window = openConsoleList?.find((t) => t.id === key); - if (!window?.status) { - return; - } - // if (window!.status === 'DRAFT') { - // historyService.deleteSavedConsole({ id: window!.id }); - // } else { + // 这行干嘛的?TODO: + // const window = openConsoleList?.find((t) => t.id === key); + // if (!window?.status) { + // return; + // } historyService.updateSavedConsole(p).then(() => { handleLocalStorageSavedConsole(p.id, 'delete'); }); - // } }; function renderEmpty() { return
    ; } - function editableNameOnBlur(t: IOption) { + function editableNameOnBlur(t: ITabItem) { let p: any = { - id: t.value, + id: t.key, name: t.label } historyService.updateSavedConsole(p).then(() => { @@ -440,60 +421,56 @@ const WorkspaceRight = memo(function (props) { }); } + const tabsList = useMemo(() => { + return workspaceTabList.map(t => { + const { uniqueData } = t; + return { + prefixIcon: workspaceTabConfig[t.type]?.icon, + label: t.title, + key: t.id, + // 这里还缺一个参数 是否可编辑tab名称, 编辑表不可编辑名称 TODO: + children: <> + { + [WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, WorkspaceTabType.PROCEDURE, WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW].includes(t.type) && + } + { + t.type === WorkspaceTabType.EditTable && + } + + }; + }) + }, [workspaceTabList, workspaceModel, activeConsoleId, curWorkspaceParams, aiModel, dispatch]) + return ( -
    - +
    +
    - { - return { - prefixIcon: t.icon, - label: t.name, - value: t.id, - }; - })} + items={tabsList} />
    - {tabList?.map((t, index) => { - return ( -
    - { - [TabType.CONSOLE, TabType.FUNCTION, TabType.PROCEDURE, TabType.TRIGGER, TabType.VIEW].includes(t.tabType) && - } - { - t.tabType === TabType.EditTable && - } -
    - ); - })}
    ); diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index a0a6117e1..9383cdcd2 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -3,7 +3,7 @@ import { connect } from 'umi'; import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; import WorkspaceLeft from './components/WorkspaceLeft'; -import WorkspaceRight from './components/WorkspaceRight'; +import WorkspaceRightNew from './components/WorkspaceRightNew'; import WorkspaceHeader from './components/WorkspaceHeader'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; @@ -27,20 +27,13 @@ const dvaModel = connect( }), ); -interface Option { - value: string; - label: string; - children?: Option[]; -} - -const workspace = memo((props) => { +const workspacePage = memo((props) => { const draggableRef = useRef(); const { workspaceModel, connectionModel, dispatch, pageLoading } = props; const { curConnection } = connectionModel; const { curWorkspaceParams } = workspaceModel; const [loading, setLoading] = useState(true); const isReady = curWorkspaceParams?.dataSourceId && ((curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName) || (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null)) - useEffect(() => { if (pageLoading === true) { setLoading(true); @@ -107,7 +100,7 @@ const workspace = memo((props) => {
    - +
    @@ -115,4 +108,4 @@ const workspace = memo((props) => { ); }); -export default dvaModel(workspace) \ No newline at end of file +export default dvaModel(workspacePage) \ No newline at end of file diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 2c579e9e2..b25c338f5 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -195,7 +195,15 @@ const getTableDetails = createRequest<{ }, IEditTableInfo>('/api/rdb/table/query', { method: 'get' }); /** 数据库支持的数据类型 */ -const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); +const getModifyTableSql = createRequest<{ + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; + oldTable: string; + newTable: string; + refresh: boolean; +}, string>('/api/rdb/table/modify/sql', { method: 'post' }); export default { getModifyTableSql, diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 3eaf7cc38..589f02ffd 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -1,4 +1,4 @@ -import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TabType } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, WorkspaceTabType } from '@/constants'; export interface IPageResponse { data: T[]; @@ -27,7 +27,7 @@ export interface IConsole { status: ConsoleStatus; // 控制台状态 connectable: boolean; // 是否可连接 tabOpened?: ConsoleOpenedStatus; // 控制台tab是否打开 - operationType: TabType; // 操作类型 + operationType: WorkspaceTabType; // 操作类型 } export interface Option { diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index 8665b2ee9..8583d53dc 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -8,35 +8,53 @@ export interface IBaseInfo { // 编辑表时列的数据结构 export interface IColumnItem { - key: string; - name: string; - columnType: string | null; - columnSize: number; - // length: number | null; - nullable: number; - prefixLength?: number | null; - comment?: string; - primaryKey?: boolean; - defaultValue?: string; - // dataType: string; - // autoIncrement: boolean; - // numericPrecision: number; - // numericScale: number; - // characterMaximumLength: number; + key?: string; // 列的key 前端自己给的 + name: string | null; // 列名 + columnType: string | null; // 列的类型 比如 varchar(100) ,double(10,6) + columnSize: number | null; // 列的长度 + nullable: number | null; // 是否为空 + primaryKey: boolean | null; // 是否主键 + defaultValue: string | null; // 默认值 + dataType: string | null; // 数据类型 + autoIncrement: boolean | null; // 是否自增 + numericPrecision: number | null; // 数字精度 + numericScale: number | null; // 数字比例 + characterMaximumLength: number | null; // 字符串最大长度 + comment: string | null; // 注释 } +export interface IIndexIncludeColumnItem { + key?: string; // 列的key 前端自己给的 + ascOrDesc: string | null; // 升序还是降序 + cardinality: number | null; // 基数 + collation: string | null; // 排序规则 + columnName: string | null; // 列名 + comment: string | null; // 注释 + databaseName: string | null; // 数据库名 + filterCondition: string | null; // 过滤条件 + indexName: string | null; // 索引名 + indexQualifier: string | null; // 索引限定符 + nonUnique: boolean | null; // 是否唯一 + ordinalPosition: number | null; // 位置 + schemaName: string | null; // 模式名 + tableName: string | null; // 表名 + type: string | null; // 类型 + pages: number | null; // 页数 + prefixLength: number | null; // +} // 编辑表时索引的数据结构 export interface IIndexItem { - key: string; - name: string; - comment?: string; + key?: string; + name: string | null; + columns: string | null; + comment?: string | null; type: IndexesType | null; - columnList: IColumnItem[]; + columnList: IIndexIncludeColumnItem[]; } // 编辑表时整体的数据结构 export interface IEditTableInfo extends IBaseInfo { columnList: IColumnItem[]; indexList: IIndexItem[]; -} \ No newline at end of file +} diff --git a/chat2db-client/src/typings/workspace.ts b/chat2db-client/src/typings/workspace.ts index 19497051a..fb06436df 100644 --- a/chat2db-client/src/typings/workspace.ts +++ b/chat2db-client/src/typings/workspace.ts @@ -1,10 +1,17 @@ -import { CreateTabIntroType, TabType } from '@/constants'; +import { CreateTabIntroType, WorkspaceTabType } from '@/constants'; import { ITreeNode } from '@/typings'; export interface ICreateTabIntro { type: CreateTabIntroType; - tabType: TabType; + workspaceTabType: WorkspaceTabType; treeNodeData: ITreeNode; } +export interface IWorkspaceTab { + id: number | string; // Tab的id + type: WorkspaceTabType; // 工作区tab的类型 + title: string; // 工作区tab的名称 + uniqueData?: any; +} + From 33359bf0bd36d97a533c55341f483f18ab9d7b86 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 17 Sep 2023 10:21:37 +0800 Subject: [PATCH 0769/1069] open console reconstruction --- chat2db-client/src/models/workspace.ts | 13 +- .../workspace/components/SaveList/index.tsx | 63 +++--- .../components/WorkspaceLeft/index.tsx | 13 +- .../components/WorkspaceRightNew/index.tsx | 191 ++++++++++-------- 4 files changed, 146 insertions(+), 134 deletions(-) diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 750713367..a9b3755e9 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -3,7 +3,7 @@ import sqlService, { MetaSchemaVO } from '@/service/sql'; import historyService from '@/service/history'; import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants'; import { Effect, Reducer } from 'umi'; -import { ITreeNode, IConsole, IPageResponse, ICreateTabIntro } from '@/typings'; +import { ITreeNode, IConsole, IPageResponse, ICreateTabIntro, IWorkspaceTab } from '@/typings'; import { treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; export type ICurWorkspaceParams = { @@ -27,6 +27,8 @@ export interface IWorkspaceModelState { curTableList: ITreeNode[]; // 触发tab编辑表或打开表 createTabIntro: ICreateTabIntro | undefined; + // 触发新增console + createConsoleIntro: IWorkspaceTab | undefined; } export interface IWorkspaceModelType { @@ -42,6 +44,7 @@ export interface IWorkspaceModelType { setCurConsoleId: Reducer; setCurTableList: Reducer; setCreateTabIntro: Reducer; + setCreateConsoleIntro: Reducer; }; effects: { fetchDatabaseAndSchema: Effect; @@ -64,6 +67,7 @@ const WorkspaceModel: IWorkspaceModelType = { curTableList: [], curConsoleId: null, createTabIntro: undefined, + createConsoleIntro: undefined, }, reducers: { @@ -125,6 +129,13 @@ const WorkspaceModel: IWorkspaceModelType = { createTabIntro: payload, }; }, + // 创建console的引子 + setCreateConsoleIntro(state, { payload }) { + return { + ...state, + createConsoleIntro: payload, + }; + } }, 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 d24357809..25cf92b3d 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -1,14 +1,14 @@ -import React, { memo, useState, useEffect, useRef, useContext, useMemo } from 'react'; +import React, { memo, useState, useEffect, useRef } from 'react'; import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Cascader, Divider, Input, Dropdown, Button, Spin } from 'antd'; +import { Input, Dropdown } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import historyServer from '@/service/history'; -import { ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; +import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType } from '@/constants'; import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; @@ -17,16 +17,11 @@ interface IProps { className?: string; workspaceModel: IWorkspaceModelType['state'], dispatch: any; - tableLoading: boolean; - databaseLoading: boolean; } const dvaModel = connect( - ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ - connectionModel: connection, + ({ workspace }: { workspace: IWorkspaceModelType }) => ({ workspaceModel: workspace, - tableLoading: loading.effects['workspace/fetchGetCurTableList'], - databaseLoading: loading.effects['workspace/fetchDatabaseAndSchema'], }), ); @@ -85,31 +80,19 @@ const SaveList = dvaModel(function (props: any) { } function openConsole(data: IConsole) { - let p: any = { id: data.id, tabOpened: ConsoleOpenedStatus.IS_OPEN }; historyServer.updateSavedConsole(p).then((res) => { - - dispatch({ - type: 'workspace/setCurConsoleId', - payload: data.id, - }); - dispatch({ - type: 'workspace/fetchGetSavedConsole', + type: 'workspace/setCreateConsoleIntro', payload: { - orderByDesc: false, - tabOpened: ConsoleOpenedStatus.IS_OPEN, - ...curWorkspaceParams + id: data.id, + type: WorkspaceTabType.CONSOLE, + title: data.name, + uniqueData: data, }, - callback: (res: any) => { - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: res.data, - }) - } }) }); } @@ -119,20 +102,20 @@ const SaveList = dvaModel(function (props: any) { id: data.id, }; historyServer.deleteSavedConsole(p).then((res) => { - dispatch({ - type: 'workspace/fetchGetSavedConsole', - payload: { - orderByDesc: true, - tabOpened: ConsoleOpenedStatus.IS_OPEN, - ...curWorkspaceParams - }, - callback: (res: any) => { - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: res.data, - }) - } - }) + // dispatch({ + // type: 'workspace/fetchGetSavedConsole', + // payload: { + // orderByDesc: true, + // tabOpened: ConsoleOpenedStatus.IS_OPEN, + // ...curWorkspaceParams + // }, + // callback: (res: any) => { + // dispatch({ + // type: 'workspace/setOpenConsoleList', + // payload: res.data, + // }) + // } + // }) dispatch({ type: 'workspace/fetchGetSavedConsole', payload: { 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 bc7e5e9c1..a7d200abe 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -47,12 +47,13 @@ const WorkspaceLeft = memo(function (props) { historyService.saveConsole(params).then(res => { dispatch({ - type: 'workspace/setCurConsoleId', - payload: res, - }); - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: [...openConsoleList, { ...params, id: res }], + type: 'workspace/setCreateConsoleIntro', + payload: { + id: res, + type: WorkspaceTabType.CONSOLE, + title: params.name, + uniqueData: params, + }, }) }) } diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index 8dde07c78..b3ffdd862 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -1,12 +1,10 @@ -import React, { memo, useRef, useEffect, useState, useMemo } from 'react'; +import React, { memo, useEffect, useState, useMemo } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; -import { ConsoleOpenedStatus, ConsoleStatus, DatabaseTypeCode, TreeNodeType } from '@/constants'; -import { IConsole, ICreateConsole } from '@/typings'; +import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType } from '@/constants'; import historyService from '@/service/history'; import sqlService from '@/service/sql'; -import Tabs, { IOption } from '@/components/Tabs'; import TabsNew, { ITabItem } from '@/components/TabsNew'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; @@ -34,8 +32,7 @@ const WorkspaceRight = memo(function (props) { // 工作台tab列表 const [workspaceTabList, setWorkspaceTabList] = useState([]); - const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, curConsoleId } = workspaceModel; - const openConsoleListRef = useRef(openConsoleList); + const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, curConsoleId, createConsoleIntro } = workspaceModel; // 根据保存的console列表生成tab列表 useEffect(() => { @@ -43,13 +40,26 @@ const WorkspaceRight = memo(function (props) { return { id: t.id, title: t.name, - type: WorkspaceTabType.CONSOLE, + type: t.operationType, uniqueData: t } }) setWorkspaceTabList(newTabList || []) }, [openConsoleList]) + useEffect(() => { + if (createConsoleIntro) { + if (workspaceTabList.findIndex(t => t.id === createConsoleIntro.id) === -1) { + setWorkspaceTabList([...workspaceTabList, createConsoleIntro]) + } + setActiveConsoleId(createConsoleIntro.id) + } + dispatch({ + type: 'workspace/setCreateConsoleIntro', + payload: undefined, + }) + }, [createConsoleIntro]) + // 监听编辑表事件 useEffect(() => { if (createTabIntro) { @@ -83,7 +93,7 @@ const WorkspaceRight = memo(function (props) { if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.VIEW) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, tableName, dataSourceId } = extraParams || {}; - const callback = (consoleId: number) => { + const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { sqlService.getViewDetail({ dataSourceId: dataSourceId!, databaseName: databaseName!, @@ -91,25 +101,26 @@ const WorkspaceRight = memo(function (props) { schemaName, }).then(res => { // 更新ddl - const newList = openConsoleListRef.current?.map(t => { + const newList = workspaceTabList.map(t => { if (t.id === consoleId) { return { ...t, - ddl: res.ddl + uniqueData: { + ...t.uniqueData, + ddl: res.ddl + } } } return t }) - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: newList, - }); + setWorkspaceTabList(newList || []) }) } const name = doubleClickTreeNodeData.name; createConsole({ doubleClickTreeNodeData, + workSpaceTabType: WorkspaceTabType.VIEW, name, callback }); @@ -119,7 +130,7 @@ const WorkspaceRight = memo(function (props) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, triggerName, dataSourceId } = extraParams || {}; const name = doubleClickTreeNodeData.name - const callback = (consoleId: number) => { + const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { sqlService.getTriggerDetail({ dataSourceId: dataSourceId!, databaseName: databaseName!, @@ -127,23 +138,24 @@ const WorkspaceRight = memo(function (props) { schemaName, }).then(res => { // 更新ddl - const newList = openConsoleListRef.current?.map(t => { + const newList = workspaceTabList.map(t => { if (t.id === consoleId) { return { ...t, - ddl: res.triggerBody + uniqueData: { + ...t.uniqueData, + ddl: res.triggerBody + } } } return t }) - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: newList, - }); + setWorkspaceTabList(newList || []) }) } createConsole({ doubleClickTreeNodeData, + workSpaceTabType: WorkspaceTabType.TRIGGER, name, callback }); @@ -153,7 +165,7 @@ const WorkspaceRight = memo(function (props) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, procedureName, dataSourceId } = extraParams || {}; const name = doubleClickTreeNodeData.name - const callback = (consoleId: number) => { + const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { sqlService.getProcedureDetail({ dataSourceId: dataSourceId!, databaseName: databaseName!, @@ -161,23 +173,24 @@ const WorkspaceRight = memo(function (props) { schemaName, }).then(res => { // 更新ddl - const newList = openConsoleListRef.current?.map(t => { + const newList = workspaceTabList.map(t => { if (t.id === consoleId) { return { ...t, - ddl: res.procedureBody + uniqueData: { + ...t.uniqueData, + ddl: res.procedureBody + } } } return t }) - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: newList, - }); + setWorkspaceTabList(newList || []) }) } createConsole({ doubleClickTreeNodeData, + workSpaceTabType: WorkspaceTabType.PROCEDURE, name, callback }); @@ -187,7 +200,7 @@ const WorkspaceRight = memo(function (props) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, functionName } = extraParams || {}; const name = doubleClickTreeNodeData.name - const callback = (consoleId: number) => { + const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { sqlService.getFunctionDetail({ dataSourceId: dataSourceId!, databaseName: databaseName!, @@ -195,35 +208,37 @@ const WorkspaceRight = memo(function (props) { schemaName, }).then(res => { // 更新ddl - const newList = openConsoleListRef.current?.map(t => { + const newList = workspaceTabList?.map(t => { if (t.id === consoleId) { return { ...t, - ddl: res.functionBody + uniqueData: { + ...t.uniqueData, + ddl: res.functionBody + } } } return t }) - dispatch({ - type: 'workspace/setOpenConsoleList', - payload: newList, - }); + setWorkspaceTabList(newList || []) }) } createConsole({ doubleClickTreeNodeData, + workSpaceTabType: WorkspaceTabType.FUNCTION, name, callback }); } - if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE && !openConsoleList?.length) { + if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE && !workspaceTabList?.length) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, tableName, } = extraParams || {}; const ddl = `SELECT * FROM ${tableName};\n`; const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); createConsole({ doubleClickTreeNodeData, + workSpaceTabType: WorkspaceTabType.CONSOLE, name, ddl }); @@ -243,47 +258,48 @@ const WorkspaceRight = memo(function (props) { } }, [activeConsoleId]) - useEffect(() => { - openConsoleListRef.current = openConsoleList; - const newActiveConsoleId = curConsoleId || activeConsoleId || Number(localStorage.getItem('active-console-id') || 0); - // 用完之后就清掉curConsoleId - if (!openConsoleList?.length) { - setActiveConsoleId(undefined); - } else if (!newActiveConsoleId) { - setActiveConsoleId(openConsoleList[0].id); - } else { - // 如果你指定了让我打开哪个那我就打开哪个 - if (curConsoleId) { - setActiveConsoleId(curConsoleId); - dispatch({ - type: 'workspace/setCurConsoleId', - payload: null, - }); - return - } - - let flag = false; - openConsoleList?.forEach((t) => { - if (t.id === newActiveConsoleId) { - flag = true; - } - }); - if (flag) { - setActiveConsoleId(newActiveConsoleId); - } else { - // 如果发现当前列表里并没有newActiveConsoleId - setActiveConsoleId(openConsoleList?.[openConsoleList?.length - 1].id); - } - } - }, [openConsoleList]); + // useEffect(() => { + // openConsoleListRef.current = openConsoleList; + // const newActiveConsoleId = curConsoleId || activeConsoleId || Number(localStorage.getItem('active-console-id') || 0); + // // 用完之后就清掉curConsoleId + // if (!openConsoleList?.length) { + // setActiveConsoleId(undefined); + // } else if (!newActiveConsoleId) { + // setActiveConsoleId(openConsoleList[0].id); + // } else { + // // 如果你指定了让我打开哪个那我就打开哪个 + // if (curConsoleId) { + // setActiveConsoleId(curConsoleId); + // dispatch({ + // type: 'workspace/setCurConsoleId', + // payload: null, + // }); + // return + // } + + // let flag = false; + // openConsoleList?.forEach((t) => { + // if (t.id === newActiveConsoleId) { + // flag = true; + // } + // }); + // if (flag) { + // setActiveConsoleId(newActiveConsoleId); + // } else { + // // 如果发现当前列表里并没有newActiveConsoleId + // setActiveConsoleId(openConsoleList?.[openConsoleList?.length - 1].id); + // } + // } + // }, [openConsoleList]); function createConsole(params: { doubleClickTreeNodeData: any, + workSpaceTabType: WorkspaceTabType, name: string, callback?: Function, ddl?: string, }) { - const { doubleClickTreeNodeData, name, callback, ddl } = params; + const { doubleClickTreeNodeData, workSpaceTabType, name, callback, ddl } = params; const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {}; let newConsole: any = { @@ -294,19 +310,19 @@ const WorkspaceRight = memo(function (props) { schemaName: schemaName, dataSourceName: dataSourceName!, status: ConsoleStatus.DRAFT, - operationType: doubleClickTreeNodeData.treeNodeType, + operationType: workSpaceTabType, ddl: ddl || '', tabOpened: ConsoleOpenedStatus.IS_OPEN, }; historyService.saveConsole(newConsole).then(res => { - - setWorkspaceTabList([...workspaceTabList, { + const newList = [...workspaceTabList, { id: res, title: newConsole.name, - type: WorkspaceTabType.CONSOLE, + type: workSpaceTabType, uniqueData: newConsole - }]) - callback?.(res); + }] + setWorkspaceTabList(newList) + callback?.(res, newList); setActiveConsoleId(res); }); } @@ -340,6 +356,7 @@ const WorkspaceRight = memo(function (props) { // 删除 新增tab const onEdit = (action: 'add' | 'remove', data: ITabItem) => { if (action === 'remove') { + setWorkspaceTabList(workspaceTabList.filter(t => t.id !== data.key)); const editData = workspaceTabList?.find(t => t.id === data.key); if (editData?.type !== WorkspaceTabType.EditTable) { closeWindowTab(data.key as number); @@ -352,7 +369,7 @@ const WorkspaceRight = memo(function (props) { const addConsole = () => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; - let params = { + let newConsole = { name: `new console`, ddl: '', dataSourceId: dataSourceId!, @@ -363,15 +380,15 @@ const WorkspaceRight = memo(function (props) { tabOpened: ConsoleOpenedStatus.IS_OPEN, operationType: WorkspaceTabType.CONSOLE, }; - historyService.saveConsole(params).then((res) => { - // const callback = () => { - // setActiveConsoleId(res); - // // dispatch({ - // // type: 'workspace/setCurConsoleId', - // // payload: res, - // // }); - // } - // getConsoleList(callback); + historyService.saveConsole(newConsole).then((res) => { + const newList = [...workspaceTabList, { + id: res, + title: newConsole.name, + type: newConsole.operationType, + uniqueData: newConsole + }] + setWorkspaceTabList(newList) + setActiveConsoleId(res); }); }; @@ -455,7 +472,7 @@ const WorkspaceRight = memo(function (props) { }; }) - }, [workspaceTabList, workspaceModel, activeConsoleId, curWorkspaceParams, aiModel, dispatch]) + }, [workspaceTabList, activeConsoleId, curWorkspaceParams]) return (
    From 0b89eaa8068f14028fa3faa28c4deef10aa5c4a1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 17 Sep 2023 16:08:09 +0800 Subject: [PATCH 0770/1069] feat: create table --- .../src/blocks/DatabaseTableEditor/index.tsx | 38 ++++++++------- .../src/components/TabsNew/index.tsx | 15 +++--- .../workspace/components/SaveList/index.tsx | 1 - .../workspace/components/TableList/index.less | 20 +++++--- .../workspace/components/TableList/index.tsx | 46 +++++++++++++++++-- .../components/WorkspaceLeft/index.tsx | 2 +- .../components/WorkspaceRightNew/index.tsx | 16 ++++++- chat2db-client/src/service/sql.ts | 12 +++-- chat2db-client/src/styles/antd.less | 5 ++ 9 files changed, 110 insertions(+), 45 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 18d0a99c1..80b28b52d 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -5,14 +5,14 @@ import classnames from 'classnames'; import IndexList, { IIndexListRef } from './IndexList'; import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; -import sqlService from '@/service/sql'; +import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import { IEditTableInfo } from '@/typings'; interface IProps { dataSourceId: number, databaseName: string, schemaName: string | undefined, - tableName: string + tableName?: string } interface ITabItem { @@ -56,24 +56,26 @@ export default memo(function DatabaseTableEditor(props) { }, ] }, []) - const [currentTab, setCurrentTab] = useState(tabList[1]); + const [currentTab, setCurrentTab] = useState(tabList[0]); function changeTab(item: ITabItem) { setCurrentTab(item) } useEffect(() => { - let params = { - databaseName, - dataSourceId, - tableName, - schemaName, - refresh: true + if (tableName) { + let params = { + databaseName, + dataSourceId, + tableName, + schemaName, + refresh: true + } + sqlService.getTableDetails(params).then(res => { + setTableDetails(res || {}) + setOldTableDetails(res) + }) } - sqlService.getTableDetails(params).then(res => { - setTableDetails(res || {}) - setOldTableDetails(res) - }) }, []) function submit() { @@ -83,14 +85,18 @@ export default memo(function DatabaseTableEditor(props) { columnList: columnListRef.current.getColumnListInfo()!, indexList: indexListRef.current.getIndexListInfo()! } - let params = { + + let params: IModifyTableSqlParams = { databaseName, dataSourceId, - tableName, schemaName, refresh: true, newTable: JSON.stringify(newTable), - oldTable: JSON.stringify(oldTableDetails) + } + + if (tableName) { + params.tableName = tableName; + params.oldTable = JSON.stringify(oldTableDetails); } console.log(newTable); sqlService.getModifyTableSql(params).then(res => { diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 107095546..890092cb3 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -19,7 +19,7 @@ interface IProps { className?: string; items: ITabItem[] | undefined; activeKey?: number | string; - onChange?: (key: ITabItem['key']) => void; + onChange?: (key: string | number | undefined) => void; onEdit?: (action: 'add' | 'remove', data?: ITabItem) => void; hideAdd?: boolean; type?: 'line'; @@ -34,8 +34,7 @@ export default memo(function Tabs(props) { const [editingTab, setEditingTab] = useState(); useEffect(() => { - // 这里如果id为0就会有问题等待改造 - if (activeKey) { + if (activeKey !== null && activeKey !== undefined) { setInternalActiveTab(activeKey); } }, [activeKey]) @@ -59,14 +58,14 @@ export default memo(function Tabs(props) { activeKey = internalTabs[index - 1]?.key } } - setInternalActiveTab(activeKey); + changeTab(activeKey); setInternalTabs(newInternalTabs); onEdit?.('remove', data) } - function changeTab(data: ITabItem) { - setInternalActiveTab(data.key); - onChange?.(data.key); + function changeTab(key: string | number | undefined) { + setInternalActiveTab(key); + onChange?.(key); } function handelAdd() { @@ -106,7 +105,7 @@ export default memo(function Tabs(props) { t.key === editingTab ? { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> : -
    +
    {t.prefixIcon && }
    {t.label} 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 25cf92b3d..e01919f1e 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -54,7 +54,6 @@ const SaveList = dvaModel(function (props: any) { }); }, [curWorkspaceParams]); - useEffect(() => { if (searching) { inputRef.current!.focus({ diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index 35d173645..4586e1fdb 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -25,9 +25,15 @@ .iconBox { display: flex; align-items: center; + margin: 0px -5px; + } + .itemIcon { + margin: 0px 5px; } .refreshIcon { - margin-right: 10px; + } + .moreIcon { + transform: rotate(90deg); } .refreshIcon, .searchIcon { @@ -49,16 +55,16 @@ margin: 0px -10px; } -.refreshIconBox{ +.refreshIconBox { cursor: pointer; - &:hover{ - color: var(--color-primary) + &:hover { + color: var(--color-primary); } } -.cascaderPopup{ - :global{ - .ant-cascader-menu{ +.cascaderPopup { + :global { + .ant-cascader-menu { height: auto; } } 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 9b8ad13d5..3bd68fdd6 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -10,10 +10,11 @@ import { IWorkspaceModelType } from '@/models/workspace'; import Tree from '../Tree'; import { treeConfig } from '../Tree/treeConfig'; import { ITreeNode } from '@/typings'; -import { TreeNodeType } from '@/constants'; +import { TreeNodeType, CreateTabIntroType, WorkspaceTabType } from '@/constants'; import styles from './index.less'; import { approximateTreeNode } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; +import { v4 as uuidV4 } from 'uuid'; interface IOption { value: TreeNodeType; @@ -32,12 +33,18 @@ const optionsList: IOption[] = [ ] const dvaModel = connect( - ({ connection, workspace, loading }: { connection: IConnectionModelType; workspace: IWorkspaceModelType, loading: any }) => ({ + ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ connectionModel: connection, workspaceModel: workspace, }), ); +interface Option { + value: string; + label: string; + children?: Option[]; +} + const TableList = dvaModel(function (props: any) { const { workspaceModel, dispatch } = props; const { curWorkspaceParams } = workspaceModel; @@ -113,6 +120,27 @@ const TableList = dvaModel(function (props: any) { setCurType(selectedOptions[0]); } + const cascaderOnChange: any = (_: string[], selectedOptions: Option[]) => { + dispatch({ + type: 'workspace/setCreateConsoleIntro', + payload: { + id: uuidV4(), + type: WorkspaceTabType.EditTable, + title: 'create-table', + uniqueData: { + + } + }, + }) + }; + + const options = [ + { + value: 'createTable', + label: '新建表', + } + ] + return (
    @@ -143,12 +171,20 @@ const TableList = dvaModel(function (props: any) {
    -
    refreshTableList()}> +
    refreshTableList()}>
    -
    openSearch()}> +
    openSearch()}>
    + { + curType.value === TreeNodeType.TABLES && + +
    + +
    +
    + }
    } @@ -156,7 +192,7 @@ const TableList = dvaModel(function (props: any) { -
    +
    ); }); 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 a7d200abe..152940010 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -33,7 +33,7 @@ const WorkspaceLeft = memo(function (props) { const addConsole = () => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams let params = { - name: `new console${openConsoleList?.length || ''}`, + name: `new console`, ddl: '', dataSourceId: dataSourceId!, databaseName: databaseName!, diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index b3ffdd862..31685b752 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -63,6 +63,13 @@ const WorkspaceRight = memo(function (props) { // 监听编辑表事件 useEffect(() => { if (createTabIntro) { + // 如果已经打开了这个表的编辑页面,那么就切换到这个页面 + const flag = workspaceTabList?.find(t => t.uniqueData?.tableName === createTabIntro.treeNodeData.name); + if (flag) { + setActiveConsoleId(flag.id); + return + } + const id = uuidV4(); const newData = { id, @@ -231,7 +238,12 @@ const WorkspaceRight = memo(function (props) { }); } - if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE && !workspaceTabList?.length) { + if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { + // 如果workspaceTabList没有可以添加select * from table的地方那么才需要创建 + const flag = workspaceTabList.some(t => [WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, WorkspaceTabType.PROCEDURE, WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW].includes(t.type)) + if (flag) { + return + } const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, tableName, } = extraParams || {}; const ddl = `SELECT * FROM ${tableName};\n`; @@ -349,7 +361,7 @@ const WorkspaceRight = memo(function (props) { } // 切换tab - function onTabChange(key: string | number) { + function onTabChange(key: string | number | undefined) { setActiveConsoleId(key); } diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index b25c338f5..90f240275 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -194,16 +194,18 @@ const getTableDetails = createRequest<{ refresh: boolean; }, IEditTableInfo>('/api/rdb/table/query', { method: 'get' }); -/** 数据库支持的数据类型 */ -const getModifyTableSql = createRequest<{ +export interface IModifyTableSqlParams { dataSourceId: number; databaseName: string; schemaName?: string; - tableName: string; - oldTable: string; + tableName?: string; + oldTable?: string; newTable: string; refresh: boolean; -}, string>('/api/rdb/table/modify/sql', { method: 'post' }); +} + +/** 数据库支持的数据类型 */ +const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); export default { getModifyTableSql, diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index aa1b75b4c..342838f63 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -56,5 +56,10 @@ // top: 10px !important; // } // } + + .ant-cascader-dropdown .ant-cascader-menu { + height: auto; + max-height: 80vh; + } } } From 3dc35e33f429a17d1914aa6d5b131f3b78e8d545 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 17 Sep 2023 16:34:10 +0800 Subject: [PATCH 0771/1069] feat:edit table i18n --- .../DatabaseTableEditor/BaseInfo/index.tsx | 5 +++-- .../DatabaseTableEditor/ColumnList/index.tsx | 19 +++++++++-------- .../DatabaseTableEditor/IncludeCol/index.tsx | 14 ++++++------- .../DatabaseTableEditor/IndexList/index.tsx | 15 ++++++------- .../src/blocks/DatabaseTableEditor/index.tsx | 9 ++++---- chat2db-client/src/i18n/en-us/editTable.ts | 21 +++++++++++++++++++ chat2db-client/src/i18n/en-us/index.ts | 4 +++- chat2db-client/src/i18n/zh-cn/editTable.ts | 21 +++++++++++++++++++ chat2db-client/src/i18n/zh-cn/index.ts | 4 +++- chat2db-client/src/i18n/zh-cn/workspace.ts | 1 - .../workspace/components/TableList/index.tsx | 2 +- 11 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 chat2db-client/src/i18n/en-us/editTable.ts create mode 100644 chat2db-client/src/i18n/zh-cn/editTable.ts diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index 1687b50b2..9200b9d84 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -4,6 +4,7 @@ import classnames from 'classnames'; import { Form, Input } from 'antd'; import { Context } from '../index'; import { IBaseInfo } from '@/typings'; +import i18n from '@/i18n'; export interface IBaseInfoRef { @@ -38,10 +39,10 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) =>
    - + - + diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 22b7b5d15..d8dbb32ad 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -17,6 +17,7 @@ import { CSS } from '@dnd-kit/utilities'; import sqlService from '@/service/sql'; import { Context } from '../index'; import { IColumnItem } from '@/typings' +import i18n from '@/i18n'; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; @@ -131,7 +132,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) align: 'center' }, { - title: 'name', + title: i18n('editTable.label.columnName'), dataIndex: 'name', width: '160px', render: (text: string, record: IColumnItem) => { @@ -154,7 +155,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } }, { - title: 'columnSize', + title: i18n('editTable.label.columnSize'), dataIndex: 'columnSize', width: '120px', render: (text: string, record: IColumnItem) => { @@ -177,7 +178,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } }, { - title: 'columnType', + title: i18n('editTable.label.columnType'), dataIndex: 'columnType', width: '200px', render: (text: string, record: IColumnItem) => { @@ -207,7 +208,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } }, { - title: 'nullable', + title: i18n('editTable.label.nullable'), dataIndex: 'nullable', width: '100px', render: (nullable: number, record: IColumnItem) => { @@ -230,7 +231,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } }, { - title: 'comment', + title: i18n('editTable.label.comment'), dataIndex: 'comment', render: (text: string, record: IColumnItem) => { const editable = isEditing(record); @@ -326,10 +327,10 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) return (
    - - - - + + + +
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index f351ca7e0..97dd85f0b 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -1,13 +1,11 @@ import React, { memo, useMemo, useState, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import classnames from 'classnames'; -import { MenuOutlined } from '@ant-design/icons'; -import { CSS } from '@dnd-kit/utilities'; import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal, message } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItem, IIndexIncludeColumnItem } from '@/typings'; -import { use } from 'echarts'; +import i18n from '@/i18n'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; @@ -89,7 +87,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) const columns = [ { - title: 'index', + title: i18n('editTable.label.index'), dataIndex: 'index', width: '10%', render: (text: string, record: IIndexIncludeColumnItem) => { @@ -97,7 +95,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }, }, { - title: 'columnName', + title: i18n('editTable.label.columnName'), dataIndex: 'columnName', width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { @@ -114,7 +112,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }, }, { - title: 'prefixLength', + title: i18n('editTable.label.prefixLength'), dataIndex: 'prefixLength', width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { @@ -168,8 +166,8 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) return (
    - - + +
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 1690a1a2d..851dd0183 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -18,6 +18,7 @@ import IncludeCol, { IIncludeColRef } from '../IncludeCol'; import { IColumnItem, IIndexItem, IIndexIncludeColumnItem } from '@/typings'; import { IndexesType } from '@/constants'; import { Context } from '../index'; +import i18n from '@/i18n'; const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']] @@ -193,14 +194,14 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = width: '60px', }, { - title: 'index', + title: i18n('editTable.label.index'), width: '70px', render: (text: string, record: IIndexItem) => { return dataSource.findIndex(i => i.key === record.key) + 1 } }, { - title: '索引名称', + title: i18n('editTable.label.indexName'), dataIndex: 'name', width: '180px', render: (text: string, record: IIndexItem) => { @@ -221,7 +222,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = } }, { - title: '索引类型', + title: i18n('editTable.label.indexType'), dataIndex: 'type', width: '180px', render: (text: string, record: IIndexItem) => { @@ -244,7 +245,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = } }, { - title: '包含列', + title: i18n('editTable.label.includeColumn'), dataIndex: 'columnList', render: (columnList: IIndexIncludeColumnItem[], record: IIndexItem) => { const editable = isEditing(record); @@ -254,7 +255,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = console.log(text) return editable ? (
    - { setIncludeColModalOpen(true) }}>编辑 + { setIncludeColModalOpen(true) }}>{i18n('common.button.edit')} {text}
    ) : ( @@ -294,8 +295,8 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = return
    - - + + {/* */}
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 80b28b52d..0948c9f44 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -7,6 +7,7 @@ import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import { IEditTableInfo } from '@/typings'; +import i18n from '@/i18n'; interface IProps { dataSourceId: number, @@ -40,17 +41,17 @@ export default memo(function DatabaseTableEditor(props) { const tabList = useMemo(() => { return [ { - title: '基本信息', + title: i18n('editTable.tab.basicInfo'), key: 'basic', component: }, { - title: '列信息', + title: i18n('editTable.tab.columnInfo'), key: 'column', component: }, { - title: '索引信息', + title: i18n('editTable.tab.indexInfo'), key: 'index', component: }, @@ -128,7 +129,7 @@ export default memo(function DatabaseTableEditor(props) { }
    - +
    diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts new file mode 100644 index 000000000..659dda986 --- /dev/null +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -0,0 +1,21 @@ +export default { + 'editTable.tab.basicInfo': 'Basic info', + 'editTable.tab.columnInfo': 'Column info', + 'editTable.tab.indexInfo': 'Index info', + 'editTable.label.tableName': 'Table name', + 'editTable.label.comment': 'Comment', + 'editTable.button.add': 'Add', + 'editTable.button.delete': 'Delete', + 'editTable.button.up': 'Up', + 'editTable.button.down': 'Down', + 'editTable.label.indexName': 'Index name', + 'editTable.label.indexType': 'Index type', + 'editTable.label.includeColumn': 'Include column', + 'editTable.button.createTable': 'Create table', + 'editTable.label.index': 'Index', + 'editTable.label.columnName': 'Column name', + 'editTable.label.columnSize': 'Size', + 'editTable.label.columnType': 'Type', + 'editTable.label.nullable': 'Nullable', + 'editTable.label.prefixLength': 'Prefix length', +}; diff --git a/chat2db-client/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts index 256cc7a9a..b92a83429 100644 --- a/chat2db-client/src/i18n/en-us/index.ts +++ b/chat2db-client/src/i18n/en-us/index.ts @@ -5,6 +5,7 @@ import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; +import editTable from './editTable'; export default { lang: 'en', @@ -14,5 +15,6 @@ export default { ...workspace, ...menu, ...dashboard, - ...chat + ...chat, + ...editTable }; diff --git a/chat2db-client/src/i18n/zh-cn/editTable.ts b/chat2db-client/src/i18n/zh-cn/editTable.ts new file mode 100644 index 000000000..59a55d27e --- /dev/null +++ b/chat2db-client/src/i18n/zh-cn/editTable.ts @@ -0,0 +1,21 @@ +export default { + 'editTable.tab.basicInfo': '基本信息', + 'editTable.tab.columnInfo': '列信息', + 'editTable.tab.indexInfo': '索引信息', + 'editTable.label.tableName': '表名', + 'editTable.label.comment': '注释', + 'editTable.button.add': '新增', + 'editTable.button.delete': '删除', + 'editTable.button.up': '上移', + 'editTable.button.down': '下移', + 'editTable.label.indexName': '索引名称', + 'editTable.label.indexType': '索引类型', + 'editTable.label.includeColumn': '包含列', + 'editTable.button.createTable': '新建表', + 'editTable.label.index': '序号', + 'editTable.label.columnName': '列名', + 'editTable.label.columnSize': '长度', + 'editTable.label.columnType': '类型', + 'editTable.label.nullable': '可空', + 'editTable.label.prefixLength': '前缀长度', +}; diff --git a/chat2db-client/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts index 867621af0..fad984095 100644 --- a/chat2db-client/src/i18n/zh-cn/index.ts +++ b/chat2db-client/src/i18n/zh-cn/index.ts @@ -6,6 +6,7 @@ import setting from './setting'; import workspace from './workspace'; import dashboard from './dashboard'; import chat from './chat'; +import editTable from './editTable'; export default { lang: LangType.ZH_CN, @@ -16,5 +17,6 @@ export default { ...menu, ...connection, ...dashboard, - ...chat + ...chat, + ...editTable }; diff --git a/chat2db-client/src/i18n/zh-cn/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 88420177d..5d1f3d56b 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -19,5 +19,4 @@ export default { 'workspace.tree.trigger': '触发器', 'workspace.tree.function': '函数', 'workspace.tree.procedure': '存储过程', - }; 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 3bd68fdd6..ca430d2a0 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -137,7 +137,7 @@ const TableList = dvaModel(function (props: any) { const options = [ { value: 'createTable', - label: '新建表', + label: i18n('editTable.button.createTable'), } ] From beddd2524ce53098d366760bff8da941694a4e8b Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 17 Sep 2023 16:36:24 +0800 Subject: [PATCH 0772/1069] feat:edit table i18n --- .../src/blocks/DatabaseTableEditor/IndexList/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 851dd0183..d98b8a5fc 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -323,7 +323,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = { setIncludeColModalOpen(false) }} maskClosable={false} From a53dc0cd1d19fcabbf9eb3291cfadbbe2c6d3669 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 17 Sep 2023 23:22:37 +0800 Subject: [PATCH 0773/1069] style:env style --- chat2db-client/src/models/workspace.ts | 31 ------------------- .../src/pages/main/connection/index.less | 20 +++++++++--- .../src/pages/main/connection/index.tsx | 11 +++++-- .../src/pages/main/workspace/index.tsx | 15 ++------- 4 files changed, 26 insertions(+), 51 deletions(-) diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index a9b3755e9..cf2a3b5b8 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -47,8 +47,6 @@ export interface IWorkspaceModelType { setCreateConsoleIntro: Reducer; }; effects: { - fetchDatabaseAndSchema: Effect; - fetchDatabaseAndSchemaLoading: Effect; fetchGetSavedConsole: Effect; fetchGetCurTableList: Effect; fetchGetSavedConsoleLoading: Effect; @@ -140,35 +138,6 @@ const WorkspaceModel: IWorkspaceModelType = { }, effects: { - // 获取当前连接下的及联databaseAndSchema数据 - *fetchDatabaseAndSchema({ payload, callback }, { put }) { - try { - const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; - yield put({ - type: 'setDatabaseAndSchema', - payload: res, - }); - if (callback && typeof callback === 'function') { - callback(res); - } - } - catch { - - } - }, - // 获取当前连接下的及联databaseAndSchema数据Loading - *fetchDatabaseAndSchemaLoading({ payload }, { put }) { - try { - const res = (yield sqlService.getDatabaseSchemaList(payload)) as MetaSchemaVO; - yield put({ - type: 'setDatabaseAndSchema', - payload: res, - }); - } - catch { - - } - }, // 获取保存的控制台列表 *fetchGetSavedConsole({ payload, callback }, { put }) { try { diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 80030fd86..8b8f99058 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -55,11 +55,21 @@ .menuItemsTitle { flex: 1; width: 0; - .f-single-line(); - } - - .envTag{ - margin: 0px 4px; + display: flex; + align-items: center; + .name { + .f-single-line(); + } + .envTag { + width: 8px; + height: 8px; + border-radius: 50%; + background-color: var(--color-primary); + margin-right: 8px; + } + .databaseTypeIcon { + margin-right: 6px; + } } .moreButton { diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 9b8e7db2a..58f060476 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -161,9 +161,14 @@ function Connections(props: IProps) { }} >
    - {icon} - {t.env.shortName} - {label} + + + {icon} + + {/* + {t.env.shortName?.[0]} + */} + {label}
    ({ + ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ connectionModel: connection, workspaceModel: workspace, - pageLoading: loading.effects['workspace/fetchDatabaseAndSchemaLoading'] || loading.effects['workspace/fetchGetSavedConsoleLoading'], }), ); const workspacePage = memo((props) => { const draggableRef = useRef(); - const { workspaceModel, connectionModel, dispatch, pageLoading } = props; + const { workspaceModel, connectionModel, dispatch } = props; const { curConnection } = connectionModel; const { curWorkspaceParams } = workspaceModel; - const [loading, setLoading] = useState(true); const isReady = curWorkspaceParams?.dataSourceId && ((curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName) || (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null)) - useEffect(() => { - if (pageLoading === true) { - setLoading(true); - } else { - setLoading(false); - } - }, [pageLoading]) useEffect(() => { clearData(); @@ -94,7 +85,7 @@ const workspacePage = memo((props) => { return (
    - +
    From cd5b8fb68ce33d03048190b4fd7250b6514f4678 Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Mon, 18 Sep 2023 09:53:34 +0800 Subject: [PATCH 0774/1069] =?UTF-8?q?navicat=E9=93=BE=E6=8E=A5=E5=AF=BC?= =?UTF-8?q?=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ncx/ConverterController.java | 65 +++++++ .../controller/ncx/cipher/CommonCipher.java | 44 +++++ .../ncx/cipher/Navicat11Cipher.java | 177 ++++++++++++++++++ .../ncx/cipher/Navicat12Cipher.java | 50 +++++ .../controller/ncx/enums/DataBaseType.java | 87 +++++++++ .../api/controller/ncx/enums/VersionEnum.java | 17 ++ .../controller/ncx/factory/CipherFactory.java | 45 +++++ .../ncx/service/ConverterService.java | 16 ++ .../service/impl/ConverterServiceImpl.java | 156 +++++++++++++++ .../web/api/controller/ncx/vo/UploadVO.java | 19 ++ 10 files changed, 676 insertions(+) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java new file mode 100644 index 000000000..6bc522c48 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java @@ -0,0 +1,65 @@ +package ai.chat2db.server.web.api.controller.ncx; + +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; +import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.util.Objects; + +/** + * ConverterController + * + * @author lzy + **/ +@RequestMapping("/api/converter") +@RestController +@Slf4j +public class ConverterController { + + @Autowired + private ConverterService converterService; + + @SneakyThrows + @PostMapping("/ncx/upload") + public DataResult uploadFile(@RequestParam("file") MultipartFile file) { + // 验证文件后缀 + String fileExtension = getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); + if (!isAllowedExtension(fileExtension)) { + return DataResult.error("1", "上传的文件必须是ncx文件!"); + } + File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + "temp.tmp"); + file.transferTo(temp); + return DataResult.of(converterService.uploadFile(temp)); + } + + private String getFileExtension(String fileName) { + int dotIndex = fileName.lastIndexOf("."); + if (dotIndex > 0) { + return fileName.substring(dotIndex + 1).toLowerCase(); + } else { + return ""; + } + } + + private boolean isAllowedExtension(String extension) { + // 只允许上传的文件后缀 + String[] allowedExtensions = {"ncx"}; + for (String ext : allowedExtensions) { + if (ext.equalsIgnoreCase(extension)) { + return true; + } + } + return false; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java new file mode 100644 index 000000000..55fe1f86a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/CommonCipher.java @@ -0,0 +1,44 @@ +package ai.chat2db.server.web.api.controller.ncx.cipher; + +import java.util.Formatter; + +/** + * CommonCipher 公共加/解密 + * + * @author lzy + */ +public abstract class CommonCipher { + + public String encryptString(String plaintext) { + return null; + } + + public String decryptString(String ciphertext) { + return null; + } + + public String printHexBinary(byte[] data) { + StringBuilder hexBuilder = new StringBuilder(); + Formatter formatter = new Formatter(hexBuilder); + for (byte b : data) { + formatter.format("%02x", b); + } + return hexBuilder.toString(); + } + + public static byte[] parseHexBinary(String data) { + return hexStringToByteArray(data); + } + + public static byte[] hexStringToByteArray(String hex) { + if (hex.length() % 2 != 0) { + throw new IllegalArgumentException("Hex string length must be even"); + } + byte[] bytes = new byte[hex.length() / 2]; + for (int i = 0; i < hex.length(); i += 2) { + String byteString = hex.substring(i, i + 2); + bytes[i / 2] = (byte) Integer.parseInt(byteString, 16); + } + return bytes; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java new file mode 100644 index 000000000..dc3c053c2 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat11Cipher.java @@ -0,0 +1,177 @@ +package ai.chat2db.server.web.api.controller.ncx.cipher; + +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.util.Arrays; + +/** + * Navicat11及以下密码加密解密 + * + * @author lzy + */ +public class Navicat11Cipher extends CommonCipher { + public static final String DefaultUserKey = "3DC5CA39"; + private static byte[] IV; + + private static SecretKeySpec key; + private static Cipher encryptor; + private static Cipher decrypt; + + private static void initKey() { + try { + MessageDigest sha1 = MessageDigest.getInstance("SHA1"); + byte[] userKey_data = Navicat11Cipher.DefaultUserKey.getBytes(StandardCharsets.UTF_8); + sha1.update(userKey_data, 0, userKey_data.length); + key = new SecretKeySpec(sha1.digest(), "Blowfish"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void initCipherEncrypt() { + try { + // Must use NoPadding + encryptor = Cipher.getInstance("Blowfish/ECB/NoPadding"); + encryptor.init(Cipher.ENCRYPT_MODE, key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void initCipherDecrypt() { + try { + // Must use NoPadding + decrypt = Cipher.getInstance("Blowfish/ECB/NoPadding"); + decrypt.init(Cipher.DECRYPT_MODE, key); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void initIV() { + try { + byte[] initVec = parseHexBinary("FFFFFFFFFFFFFFFF"); + IV = encryptor.doFinal(initVec); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void xorBytes(byte[] a, byte[] b) { + for (int i = 0; i < a.length; i++) { + int aVal = a[i] & 0xff; // convert byte to integer + int bVal = b[i] & 0xff; + a[i] = (byte) (aVal ^ bVal); // xor aVal and bVal and typecast to byte + } + } + + private void xorBytes(byte[] a, byte[] b, int l) { + for (int i = 0; i < l; i++) { + int aVal = a[i] & 0xff; // convert byte to integer + int bVal = b[i] & 0xff; + a[i] = (byte) (aVal ^ bVal); // xor aVal and bVal and typecast to byte + } + } + + static { + initKey(); + initCipherEncrypt(); + initCipherDecrypt(); + initIV(); + } + + private byte[] Encrypt(byte[] inData) { + try { + byte[] CV = Arrays.copyOf(IV, IV.length); + byte[] ret = new byte[inData.length]; + + int blocks_len = inData.length / 8; + int left_len = inData.length % 8; + + for (int i = 0; i < blocks_len; i++) { + byte[] temp = Arrays.copyOfRange(inData, i * 8, (i * 8) + 8); + + xorBytes(temp, CV); + temp = encryptor.doFinal(temp); + xorBytes(CV, temp); + + System.arraycopy(temp, 0, ret, i * 8, 8); + } + + if (left_len != 0) { + CV = encryptor.doFinal(CV); + byte[] temp = Arrays.copyOfRange(inData, blocks_len * 8, (blocks_len * 8) + left_len); + xorBytes(temp, CV, left_len); + System.arraycopy(temp, 0, ret, blocks_len * 8, temp.length); + } + + return ret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String encryptString(String inputString) { + try { + byte[] inData = inputString.getBytes(StandardCharsets.UTF_8); + byte[] outData = Encrypt(inData); + return printHexBinary(outData); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private byte[] Decrypt(byte[] inData) { + try { + byte[] cv = Arrays.copyOf(IV, IV.length); + byte[] ret = new byte[inData.length]; + + int blocks_len = inData.length / 8; + int left_len = inData.length % 8; + + for (int i = 0; i < blocks_len; i++) { + byte[] temp = Arrays.copyOfRange(inData, i * 8, (i * 8) + 8); + + temp = decrypt.doFinal(temp); + xorBytes(temp, cv); + System.arraycopy(temp, 0, ret, i * 8, 8); + for (int j = 0; j < cv.length; j++) { + cv[j] = (byte) (cv[j] ^ inData[i * 8 + j]); + } + } + + if (left_len != 0) { + cv = encryptor.doFinal(cv); + byte[] temp = Arrays.copyOfRange(inData, blocks_len * 8, (blocks_len * 8) + left_len); + + xorBytes(temp, cv, left_len); + for (int j = 0; j < temp.length; j++) { + ret[blocks_len * 8 + j] = temp[j]; + } + } + + return ret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String decryptString(String hexString) { + if (StringUtils.isEmpty(hexString)) { + return ""; + } + try { + byte[] inData = parseHexBinary(hexString); + byte[] outData = Decrypt(inData); + return new String(outData, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java new file mode 100644 index 000000000..45fdd48b0 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/cipher/Navicat12Cipher.java @@ -0,0 +1,50 @@ +package ai.chat2db.server.web.api.controller.ncx.cipher; + +import org.apache.commons.lang3.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + +/** + * Navicat12及以上密码加密解密 + * + * @author lzy + */ +public class Navicat12Cipher extends CommonCipher { + private static final SecretKeySpec AES_KEY; + private static final IvParameterSpec AES_IV; + + static { + AES_KEY = new SecretKeySpec("libcckeylibcckey".getBytes(StandardCharsets.UTF_8), "AES"); + AES_IV = new IvParameterSpec("libcciv libcciv ".getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String encryptString(String plaintext) { + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, AES_KEY, AES_IV); + byte[] ret = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); + return printHexBinary(ret); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String decryptString(String ciphertext) { + if (StringUtils.isEmpty(ciphertext)) { + return ""; + } + try { + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, AES_KEY, AES_IV); + byte[] ret = cipher.doFinal(parseHexBinary(ciphertext)); + return new String(ret, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java new file mode 100644 index 000000000..3dacfeade --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java @@ -0,0 +1,87 @@ +package ai.chat2db.server.web.api.controller.ncx.enums; + +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; + +/** + * DataBaseType + * + * @author lzy + **/ +@Getter +public enum DataBaseType { + /** + * MYSQL + */ + MYSQL("jdbc:mysql://%s:%s"), + /** + * ORACLE + */ + ORACLE("jdbc:oracle:thin:@%s:%s:XE"), + /** + * SQL_SERVER + */ + SQLSERVER("jdbc:sqlserver://%s:%s"), + /** + * SQL_SERVER + */ + SQLITE("jdbc:sqlite:%s"), + /** + * POSTGRESQL + **/ + POSTGRESQL("jdbc:postgresql://%s:%s"), + /** + * DB2 + **/ + DB2("jdbc:db2://%s:%s"), + /** + * Mariadb + **/ + Mariadb("jdbc:mariadb://%s:%s"), + /** + * DM + **/ + DM("jdbc:dm://%s:%s"), + /** + * KINGBASE8 + **/ + KINGBASE8("jdbc:kingbase8://%s:%s"), + /** + * Presto + **/ + Presto("jdbc:presto://%s:%s"), + /** + * OceanBase + **/ + OceanBase("jdbc:oceanbase://%s:%s"), + /** + * Hive + **/ + Hive("jdbc:hive2://%s:%s"), + /** + * ClickHouse + **/ + ClickHouse("jdbc:clickhouse://%s:%s"); + + private String urlString; + + DataBaseType(String urlString) { + this.urlString = urlString; + } + + public void setUrlString(String urlString) { + this.urlString = urlString; + } + + public static DataBaseType matchType(String value) { + if (StringUtils.isNotEmpty(value)) { + for (DataBaseType dataBase : DataBaseType.values()) { + if (dataBase.name().equals(value.toUpperCase())) { + return dataBase; + } + } + } + return null; + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java new file mode 100644 index 000000000..18b34e71e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/VersionEnum.java @@ -0,0 +1,17 @@ +package ai.chat2db.server.web.api.controller.ncx.enums; + +/** + * navicat版本枚举(版本区分navicat加密算法) + * + * @author lzy + */ +public enum VersionEnum { + /** + * navicat11 + */ + native11, + /** + * navicat12+ + */ + navicat12more +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java new file mode 100644 index 000000000..53253d966 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/factory/CipherFactory.java @@ -0,0 +1,45 @@ +package ai.chat2db.server.web.api.controller.ncx.factory; + +import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; +import ai.chat2db.server.web.api.controller.ncx.cipher.Navicat11Cipher; +import ai.chat2db.server.web.api.controller.ncx.cipher.Navicat12Cipher; +import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; +import lombok.SneakyThrows; +import org.springframework.stereotype.Service; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * CipherFactory + * + * @author lzy + **/ +@Service +public class CipherFactory { + /** + * NavicatCipher缓存池 + */ + private static final Map REPORT_POOL = new ConcurrentHashMap<>(0); + + static { + REPORT_POOL.put(VersionEnum.native11.name(), new Navicat11Cipher()); + REPORT_POOL.put(VersionEnum.navicat12more.name(), new Navicat12Cipher()); + } + + /** + * 获取对应加/解密方法 + * + * @param type 类型 + * @return ITokenGranter + */ + @SneakyThrows + public static CommonCipher get(String type) { + CommonCipher cipher = REPORT_POOL.get(type); + if (cipher == null) { + throw new ClassNotFoundException("no CommonCipher was found"); + } else { + return cipher; + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java new file mode 100644 index 000000000..659581638 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.web.api.controller.ncx.service; + +import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; + +import java.io.File; +import java.io.InputStream; + +/** + * ConverterService + * + * @author lzy + **/ +public interface ConverterService { + + UploadVO uploadFile(File file); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java new file mode 100644 index 000000000..c3b882762 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -0,0 +1,156 @@ +package ai.chat2db.server.web.api.controller.ncx.service.impl; + +import ai.chat2db.server.domain.core.util.DesUtil; +import ai.chat2db.server.domain.repository.entity.DataSourceDO; +import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; +import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; +import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType; +import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; +import ai.chat2db.server.web.api.controller.ncx.factory.CipherFactory; +import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; +import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; +import ai.chat2db.spi.model.SSHInfo; +import com.alibaba.excel.util.FileUtils; +import com.alibaba.fastjson2.JSON; +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.File; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * ConverterServiceImpl + * + * @author lzy + **/ +@Service +@Transactional(rollbackFor = Exception.class) +public class ConverterServiceImpl implements ConverterService { + + private static final double NAVICAT11 = 1.1D; + + private static CommonCipher cipher; + + @Autowired + private DataSourceMapper dataSourceMapper; + + @Override + public UploadVO uploadFile(File file) { + + UploadVO vo = new UploadVO(); + try { + // List>> 要导入的连接 + List>> configMap = new ArrayList<>(); + //1、创建一个DocumentBuilderFactory的对象 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //2、创建一个DocumentBuilder的对象 + //创建DocumentBuilder对象 + DocumentBuilder db = dbf.newDocumentBuilder(); + //3、通过DocumentBuilder对象的parser方法加载xml文件到当前项目下 + Document document = db.parse(file); + //获取所有Connections节点的集合 + NodeList connectList = document.getElementsByTagName("Connection"); + + NodeList nodeList = document.getElementsByTagName("Connections"); + //选中第一个节点 + NamedNodeMap verMap = nodeList.item(0).getAttributes(); + double version = Double.parseDouble((verMap.getNamedItem("Ver").getNodeValue())); + if (version <= NAVICAT11) { + cipher = CipherFactory.get(VersionEnum.native11.name()); + } else { + cipher = CipherFactory.get(VersionEnum.navicat12more.name()); + } + //配置map + Map> connectionMap = new HashMap<>(); + //遍历每一个Connections节点 + for (int i = 0; i < connectList.getLength(); i++) { + //通过 item(i)方法 获取一个Connection节点,nodeList的索引值从0开始 + Node connect = connectList.item(i); + //获取Connection节点的所有属性集合 + NamedNodeMap attrs = connect.getAttributes(); + //遍历Connection的属性 + Map map = new HashMap<>(0); + for (int j = 0; j < attrs.getLength(); j++) { + //通过item(index)方法获取connect节点的某一个属性 + Node attr = attrs.item(j); + map.put(attr.getNodeName(), attr.getNodeValue()); + } + connectionMap.put(map.get("ConnectionName") + map.get("ConnType"), map); + } + configMap.add(connectionMap); + // 将获取到navicat导入的链接,写入chat2db的h2数据库 + insertDBConfig(configMap); + //删除临时文件 + FileUtils.delete(file); + } catch (Exception e) { + throw new RuntimeException(e); + } + return vo; + } + + /** + * 写入到数据库 + * + * @param list 读取ncx文件的数据 + */ + @SneakyThrows + public void insertDBConfig(List>> list) { + for (Map> map : list) { + for (Map.Entry> valueMap : map.entrySet()) { + Map resultMap = valueMap.getValue(); + // 解密密码 + String password = cipher.decryptString(resultMap.getOrDefault("Password", "")); + DataSourceDO dataSourceDO = new DataSourceDO(); + LocalDateTime dateTime = LocalDateTime.now(); + dataSourceDO.setGmtCreate(dateTime); + dataSourceDO.setGmtModified(dateTime); + dataSourceDO.setAlias(resultMap.get("ConnectionName")); + dataSourceDO.setHost(resultMap.get("Host")); + dataSourceDO.setPort(resultMap.get("Port")); + dataSourceDO.setUserName(resultMap.get("UserName")); + dataSourceDO.setType(resultMap.get("ConnType")); + // mysql的版本还无法区分 + dataSourceDO.setUrl(String.format(Objects.requireNonNull(DataBaseType.matchType(dataSourceDO.getType())).getUrlString(), dataSourceDO.getHost(), dataSourceDO.getPort())); + //password 为解密出来的密文,再使用chat2db的加密 + DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); + String encryptStr = desUtil.encrypt(password, "CBC"); + dataSourceDO.setPassword(encryptStr); + SSHInfo sshInfo = new SSHInfo(); + if ("false".equals(resultMap.get("SSH"))) { + sshInfo.setUse(false); + } else { + sshInfo.setUse(true); + sshInfo.setHostName(resultMap.get("SSH_Host")); + sshInfo.setPort(resultMap.get("SSH_Port")); + sshInfo.setUserName(resultMap.get("SSH_UserName")); + // 目前chat2DB只支持 password 和 Private key + boolean passwordType = "password".equalsIgnoreCase(resultMap.get("SSH_AuthenMethod")); + sshInfo.setAuthenticationType(passwordType ? "password" : "Private key"); + if (passwordType) { + // 解密密码 + String ssh_password = cipher.decryptString(resultMap.getOrDefault("SSH_Password", "")); + sshInfo.setPassword(ssh_password); + } else { + sshInfo.setKeyFile(resultMap.get("SSH_PrivateKey")); + sshInfo.setPassphrase(resultMap.get("SSH_Passphrase")); + } + } + dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); + dataSourceMapper.insert(dataSourceDO); + } + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java new file mode 100644 index 000000000..452c228cc --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/vo/UploadVO.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.web.api.controller.ncx.vo; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +/** + * UploadVO + * + * @author lzy + **/ +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class UploadVO { + private String result; +} From 999581232f60dce05b5e9919c31cbd397fa6c73e Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 19 Sep 2023 16:32:07 +0800 Subject: [PATCH 0775/1069] Add query column data type --- .../mysql/type/MysqlColumnTypeEnum.java | 65 +++++++++++++++++++ .../db/migration/V2_1_3__TableCache.sql | 38 +++++++++++ .../api/controller/rdb/TableController.java | 2 +- .../rdb/request/NewTableSqlRequest.java | 16 +++++ .../web/api/controller/rdb/vo/ColumnVO.java | 3 - .../main/java/ai/chat2db/spi/SqlBuilder.java | 28 ++++++++ .../ai/chat2db/spi/enums/ColumnTypeEnum.java | 49 -------------- .../chat2db/spi/jdbc/DefaultSqlBuilder.java | 63 ++++++++++++++++++ .../java/ai/chat2db/spi/model/ColumnType.java | 20 ++++++ 9 files changed, 231 insertions(+), 53 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java create mode 100644 chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableCache.sql create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/NewTableSqlRequest.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java delete mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/ColumnTypeEnum.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java new file mode 100644 index 000000000..7e9370c9a --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -0,0 +1,65 @@ +package ai.chat2db.plugin.mysql.type; + +import ai.chat2db.spi.model.ColumnType; + +public enum MysqlColumnTypeEnum { + + BIT("BIT", true, false, true, false, false, false, true, true, false), + + TINYINT("TINYINT", false, false, true, true, false, false, true, true, false), + + TINYINT_UNSIGNED("TINYINT UNSIGNED", false, false, true, true, false, false, true, true, false), + + SMALLINT("SMALLINT", false, false, true, true, false, false, true, true, false), + + SMALLINT_UNSIGNED("SMALLINT UNSIGNED", false, false, true, true, false, false, true, true, false), + + MEDIUMINT("MEDIUMINT", false, false, true, true, false, false, true, true, false), + + MEDIUMINT_UNSIGNED("MEDIUMINT UNSIGNED", false, false, true, true, false, false, true, true, false), + + INT("INT", false, false, true, true, false, false, true, true, false), + + + INT_UNSIGNED("INT UNSIGNED", false, false, true, true, false, false, true, true, false), + + BIGINT("BIGINT", false, false, true, true, false, false, true, true, false), + + + BIGINT_UNSIGNED("BIGINT UNSIGNED", false, false, true, true, false, false, true, true, false), + + + DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false), + + DECIMAL_UNSIGNED("DECIMAL UNSIGNED", true, true, true, false, false, false, true, true, false), + + + FLOAT("FLOAT", true, true, true, false, false, false, true, true, false), FLOAT_UNSIGNED("FLOAT UNSIGNED", true, true, true, false, false, false, true, true, false), + + DOUBLE("DOUBLE", true, true, true, false, false, false, true, true, false), + + DOUBLE_UNSIGNED("DOUBLE UNSIGNED", true, true, true, false, false, false, true, true, false), DATE("DATE", false, false, true, false, false, false, true, true, false), DATETIME("DATETIME", true, false, true, false, false, false, true, true, true), TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true), TIME("TIME", true, false, true, false, false, false, true, true, false), YEAR("YEAR", false, false, true, false, false, false, true, true, false), CHAR("CHAR", true, false, true, false, false, true, true, true, false), VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false), BINARY("BINARY", true, false, true, false, false, false, true, true, false), VARBINARY("VARBINARY", true, false, true, false, false, false, true, true, false), TINYBLOB("TINYBLOB", false, false, true, false, false, false, true, false, false), BLOB("BLOB", false, false, true, false, false, false, true, false, false), MEDIUMBLOB("MEDIUMBLOB", false, false, true, false, false, false, true, false, false), LONGBLOB("LONGBLOB", false, false, true, false, false, false, true, false, false), TINYTEXT("TINYTEXT", false, false, true, false, false, true, true, false, false), TEXT("TEXT", false, false, true, false, false, true, true, false, false), MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, false, true, true, false, false), LONGTEXT("LONGTEXT", false, false, true, false, false, true, true, false, false), ENUM("ENUM", false, false, true, false, false, true, true, true, true), + + + BOOL("BOOL", false, false, true, true, false, false, true, true, false), + + INTEGER("INTEGER", false, false, true, true, false, false, true, true, false), INTEGER_UNSIGNED("INTEGER UNSIGNED", false, false, true, true, false, false, true, true, false), + + REAL("REAL", true, true, true, false, false, false, true, true, false), + + SET("SET", false, false, true, false, false, true, true, true, true), + + + GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, false, false), POINT("POINT", false, false, true, false, false, false, true, false, false), LINESTRING("LINESTRING", false, false, true, false, false, false, true, false, false), POLYGON("POLYGON", false, false, true, false, false, false, true, false, false), MULTIPOINT("MULTIPOINT", false, false, true, false, false, false, true, false, false), MULTILINESTRING("MULTILINESTRING", false, false, true, false, false, false, true, false, false), MULTIPOLYGON("MULTIPOLYGON", false, false, true, false, false, false, true, false, false), GEOMETRYCOLLECTION("GEOMETRYCOLLECTION", false, false, true, false, false, false, true, false, false), + + JSON("JSON", false, false, true, false, false, false, true, false, false); + + private ColumnType columnType; + + + MysqlColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent); + } + + +} diff --git a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableCache.sql b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableCache.sql new file mode 100644 index 000000000..9e0504330 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableCache.sql @@ -0,0 +1,38 @@ +CREATE TABLE IF NOT EXISTS `table_cache_version` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', + `database_name` varchar(256) DEFAULT NULL COMMENT 'db名称', + `schema_name` varchar(256) DEFAULT NULL COMMENT 'schema名称', + `key` varchar(256) DEFAULT NULL COMMENT '唯一索引', + `version` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '版本', + `table_count` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '表数量', + `status` varchar(256) DEFAULT NULL COMMENT '状态', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='table cache version' +; +create INDEX idx_data_source_id on table_cache_version(data_source_id) ; +create UNIQUE INDEX uk_key on table_cache_version(key) ; + +CREATE TABLE IF NOT EXISTS `table_cache` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间', + `data_source_id` bigint(20) unsigned NOT NULL COMMENT '数据源连接ID', + `database_name` varchar(256) DEFAULT NULL COMMENT 'db名称', + `schema_name` varchar(256) DEFAULT NULL COMMENT 'schema名称', + `table_name` varchar(256) DEFAULT NULL COMMENT 'table名称', + `key` varchar(256) DEFAULT NULL COMMENT '唯一索引', + `version` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '版本', + `columns` varchar(2048) DEFAULT NULL COMMENT '表字段', + `extend_info` varchar(4096) NULL COMMENT '自定义扩展字段json'; + PRIMARY KEY (`id`) + ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='table cache' +; +create INDEX idx_data_source_id on table_cache(data_source_id) ; +create INDEX idx_key_version on table_cache(key,version) ; +create INDEX idx_key_table_name on table_cache(key,table_name) ; + + + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 826d7ec03..83a091ed5 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -66,7 +66,7 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } - + /** * 查询当前DB下的表columns diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/NewTableSqlRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/NewTableSqlRequest.java new file mode 100644 index 000000000..a2aeb6fcd --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/NewTableSqlRequest.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Data +public class NewTableSqlRequest extends DataSourceBaseRequest { + + /** + * 新的表结构 + */ + @NotNull + private TableRequest newTable; + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java index 0b12bc52f..fecb2d06c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java @@ -1,8 +1,5 @@ package ai.chat2db.server.web.api.controller.rdb.vo; -import ai.chat2db.spi.enums.ColumnTypeEnum; - -import com.fasterxml.jackson.annotation.JsonAlias; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java new file mode 100644 index 000000000..e34d0b1b0 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java @@ -0,0 +1,28 @@ +package ai.chat2db.spi; + +import ai.chat2db.spi.model.Table; + +public interface SqlBuilder { + + /** + * Generate create table sql + * + * @param databaseName + * @param schemaName + * @param table + * @return + */ + String generateCreateTableSql(String databaseName, String schemaName, Table table); + + + /** + * Generate modify table sql + * + * @param databaseName + * @param schemaName + * @param newTable + * @param oldTable + * @return + */ + String generateModifyTaleSql(String databaseName, String schemaName, Table newTable, Table oldTable); +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/ColumnTypeEnum.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/ColumnTypeEnum.java deleted file mode 100644 index 9254e2f76..000000000 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/ColumnTypeEnum.java +++ /dev/null @@ -1,49 +0,0 @@ -package ai.chat2db.spi.enums; - -import ai.chat2db.server.tools.base.enums.BaseEnum; -import lombok.Getter; - -/** - * 列的类型 - * - * @author Jiaju Zhuang - */ -@Getter -public enum ColumnTypeEnum implements BaseEnum { - /** - * BIGINT - */ - BIGINT, - - /** - * VARCHAR - */ - VARCHAR, - - /** - * TIMESTAMP - */ - TIMESTAMP, - - /** - * DATETIME - */ - DATETIME, - - /** - * INTEGER - */ - INTEGER, - - ; - - @Override - public String getCode() { - return this.name(); - } - - @Override - public String getDescription() { - return this.name(); - } -} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java new file mode 100644 index 000000000..43e669fe0 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -0,0 +1,63 @@ +package ai.chat2db.spi.jdbc; + +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; + +public class DefaultSqlBuilder implements SqlBuilder { + @Override + public String generateCreateTableSql(String databaseName, String schemaName, Table table) { + StringBuilder script = new StringBuilder(); + script.append("CREATE TABLE "); + script.append("`").append(table.getName()).append("`").append(" (").append("\n"); + for (ai.chat2db.spi.model.TableColumn column : table.getColumnList()) { + script.append("`").append(column.getName()).append("`").append(" "); + script.append(column.getDataType()).append(" "); + if (column.getColumnSize() != null) { + script.append("(").append(column.getColumnSize()).append(") "); + } +// if (column.getType().getDecimal() != null) { +// script.append("(").append(column.getType().getLength()).append(",").append(column.getType().getDecimal()).append(") "); +// } +// if (column.isNullable()) { +// script.append("NULL "); +// } else { +// script.append("NOT NULL "); +// } +// if (column.getDefaultValue() != null) { +// script.append("DEFAULT ").append(column.getDefaultValue()).append(" "); +// } +// if (column.getComment() != null) { +// script.append("COMMENT '").append(column.getComment()).append("' "); +// } + script.append(",").append("\n"); + } + return null; + } + + private String generateColumnSql(ai.chat2db.spi.model.TableColumn column) { + if(StringUtils.isEmpty(column.getColumnType())){ + return ""; + } + String type = column.getColumnType().toUpperCase(); + + StringBuilder script = new StringBuilder(); + script.append("`").append(column.getName()).append("`").append(" "); + script.append(type).append(" "); + if(Arrays.asList("","").contains(type)){ + script.append("(").append(column.getColumnSize()).append(") "); + } + + + + + return script.toString(); + } + + @Override + public String generateModifyTaleSql(String databaseName, String schemaName, Table newTable, Table oldTable) { + return null; + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java new file mode 100644 index 000000000..56b08f79f --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java @@ -0,0 +1,20 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ColumnType { + + private String dataTypeName; + private boolean supportLength; + private boolean supportScale; + private boolean supportNullable; + private boolean supportAutoIncrement; + private boolean supportCharset; + private boolean supportCollation; + private boolean supportComments; + private boolean supportDefaultValue; + private boolean supportExtent; +} From 456d655d2f6cecb8bd6dc6c24b69b297b9f0a891 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 19 Sep 2023 16:41:26 +0800 Subject: [PATCH 0776/1069] Add query column data type --- .../migration/{V2_1_3__TableCache.sql => V2_1_1__TableCache.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename chat2db-server/chat2db-server-start/src/main/resources/db/migration/{V2_1_3__TableCache.sql => V2_1_1__TableCache.sql} (100%) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableCache.sql b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_1__TableCache.sql similarity index 100% rename from chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableCache.sql rename to chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_1__TableCache.sql From 09543ae52dabb355b339b1b31dd4289e7fadb93f Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 20 Sep 2023 10:09:58 +0800 Subject: [PATCH 0777/1069] Add query column data type --- .../mysql/type/MysqlColumnTypeEnum.java | 153 +++++++++++++++++- .../db/migration/V2_1_1__TableCache.sql | 18 +-- .../java/ai/chat2db/spi/ColumnBuilder.java | 15 ++ 3 files changed, 172 insertions(+), 14 deletions(-) create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index 7e9370c9a..f1ee9cd98 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -1,8 +1,16 @@ package ai.chat2db.plugin.mysql.type; +import ai.chat2db.spi.ColumnBuilder; import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; -public enum MysqlColumnTypeEnum { +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum MysqlColumnTypeEnum implements ColumnBuilder { BIT("BIT", true, false, true, false, false, false, true, true, false), @@ -34,32 +42,167 @@ public enum MysqlColumnTypeEnum { DECIMAL_UNSIGNED("DECIMAL UNSIGNED", true, true, true, false, false, false, true, true, false), - FLOAT("FLOAT", true, true, true, false, false, false, true, true, false), FLOAT_UNSIGNED("FLOAT UNSIGNED", true, true, true, false, false, false, true, true, false), + FLOAT("FLOAT", true, true, true, false, false, false, true, true, false), + + FLOAT_UNSIGNED("FLOAT UNSIGNED", true, true, true, false, false, false, true, true, false), DOUBLE("DOUBLE", true, true, true, false, false, false, true, true, false), - DOUBLE_UNSIGNED("DOUBLE UNSIGNED", true, true, true, false, false, false, true, true, false), DATE("DATE", false, false, true, false, false, false, true, true, false), DATETIME("DATETIME", true, false, true, false, false, false, true, true, true), TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true), TIME("TIME", true, false, true, false, false, false, true, true, false), YEAR("YEAR", false, false, true, false, false, false, true, true, false), CHAR("CHAR", true, false, true, false, false, true, true, true, false), VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false), BINARY("BINARY", true, false, true, false, false, false, true, true, false), VARBINARY("VARBINARY", true, false, true, false, false, false, true, true, false), TINYBLOB("TINYBLOB", false, false, true, false, false, false, true, false, false), BLOB("BLOB", false, false, true, false, false, false, true, false, false), MEDIUMBLOB("MEDIUMBLOB", false, false, true, false, false, false, true, false, false), LONGBLOB("LONGBLOB", false, false, true, false, false, false, true, false, false), TINYTEXT("TINYTEXT", false, false, true, false, false, true, true, false, false), TEXT("TEXT", false, false, true, false, false, true, true, false, false), MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, false, true, true, false, false), LONGTEXT("LONGTEXT", false, false, true, false, false, true, true, false, false), ENUM("ENUM", false, false, true, false, false, true, true, true, true), + DOUBLE_UNSIGNED("DOUBLE UNSIGNED", true, true, true, false, false, false, true, true, false), + DATE("DATE", false, false, true, false, false, false, true, true, false), + DATETIME("DATETIME", true, false, true, false, false, false, true, true, true), + + TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true), + TIME("TIME", true, false, true, false, false, false, true, true, false), + YEAR("YEAR", false, false, true, false, false, false, true, true, false), + CHAR("CHAR", true, false, true, false, false, true, true, true, false), + + VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false), + + BINARY("BINARY", true, false, true, false, false, false, true, true, false), + + VARBINARY("VARBINARY", true, false, true, false, false, false, true, true, false), + + TINYBLOB("TINYBLOB", false, false, true, false, false, false, true, false, false), + + BLOB("BLOB", false, false, true, false, false, false, true, false, false), + + MEDIUMBLOB("MEDIUMBLOB", false, false, true, false, false, false, true, false, false), + + LONGBLOB("LONGBLOB", false, false, true, false, false, false, true, false, false), + + TINYTEXT("TINYTEXT", false, false, true, false, false, true, true, false, false), + + TEXT("TEXT", false, false, true, false, false, true, true, false, false), + + MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, false, true, true, false, false), + + LONGTEXT("LONGTEXT", false, false, true, false, false, true, true, false, false), + + + ENUM("ENUM", false, false, true, false, false, true, true, true, true), BOOL("BOOL", false, false, true, true, false, false, true, true, false), - INTEGER("INTEGER", false, false, true, true, false, false, true, true, false), INTEGER_UNSIGNED("INTEGER UNSIGNED", false, false, true, true, false, false, true, true, false), + INTEGER("INTEGER", false, false, true, true, false, false, true, true, false), + + INTEGER_UNSIGNED("INTEGER UNSIGNED", false, false, true, true, false, false, true, true, false), REAL("REAL", true, true, true, false, false, false, true, true, false), SET("SET", false, false, true, false, false, true, true, true, true), - GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, false, false), POINT("POINT", false, false, true, false, false, false, true, false, false), LINESTRING("LINESTRING", false, false, true, false, false, false, true, false, false), POLYGON("POLYGON", false, false, true, false, false, false, true, false, false), MULTIPOINT("MULTIPOINT", false, false, true, false, false, false, true, false, false), MULTILINESTRING("MULTILINESTRING", false, false, true, false, false, false, true, false, false), MULTIPOLYGON("MULTIPOLYGON", false, false, true, false, false, false, true, false, false), GEOMETRYCOLLECTION("GEOMETRYCOLLECTION", false, false, true, false, false, false, true, false, false), + GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, false, false), + + POINT("POINT", false, false, true, false, false, false, true, false, false), + + LINESTRING("LINESTRING", false, false, true, false, false, false, true, false, false), + + POLYGON("POLYGON", false, false, true, false, false, false, true, false, false), + + MULTIPOINT("MULTIPOINT", false, false, true, false, false, false, true, false, false), + + MULTILINESTRING("MULTILINESTRING", false, false, true, false, false, false, true, false, false), + + MULTIPOLYGON("MULTIPOLYGON", false, false, true, false, false, false, true, false, false), + + GEOMETRYCOLLECTION("GEOMETRYCOLLECTION", false, false, true, false, false, false, true, false, false), JSON("JSON", false, false, true, false, false, false, true, false, false); private ColumnType columnType; + public ColumnType getColumnType() { + return columnType; + } + MysqlColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent) { this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent); } + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (MysqlColumnTypeEnum value : MysqlColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getDataTypeName(), value); + } + } + + + @Override + public String generateColumnSql(TableColumn column) { + MysqlColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + if (type == null) { + return ""; + } + StringBuilder script = new StringBuilder(); + script.append("`").append(column.getName()).append("`").append(" "); + script.append(buildDataType(column, type)).append(" "); + + + return null; + } + + private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { + String columnType = type.columnType.getDataTypeName(); + if (Arrays.asList(BINARY, VARBINARY, VARCHAR, CHAR).contains(type)) { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + + if (BIT.equals(type)) { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + + if (Arrays.asList(TIME, DATETIME, TIMESTAMP).contains(type)) { + if (column.getColumnSize() == null || column.getColumnSize() == 0) { + return columnType; + } else { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + } + + + if (Arrays.asList(DECIMAL, FLOAT, DOUBLE).contains(type)) { + if (column.getColumnSize() == null || column.getDecimalDigits() == null) { + return columnType; + } + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); + } + if (column.getColumnSize() != null && column.getDecimalDigits() != null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); + } + } + + if (Arrays.asList(DECIMAL_UNSIGNED, FLOAT_UNSIGNED, DECIMAL_UNSIGNED).contains(type)) { + if (column.getColumnSize() == null || column.getDecimalDigits() == null) { + return columnType; + } + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + return unsignedDataType(columnType, "(" + column.getColumnSize() + ")"); + } + if (column.getColumnSize() != null && column.getDecimalDigits() != null) { + return unsignedDataType(columnType, "(" + column.getColumnSize() + "," + column.getDecimalDigits() + ")"); + } + } + + if(Arrays.asList(SET,ENUM).contains(type)){ + //List enumList = column. + } + + return columnType; + } + + private String unsignedDataType(String dataTypeName, String middle) { + String[] split = dataTypeName.split(" "); + if (split.length == 2) { + return StringUtils.join(split[0], middle, split[1]); + } + return StringUtils.join(dataTypeName, middle); + } + } diff --git a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_1__TableCache.sql b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_1__TableCache.sql index 9e0504330..0c7cf3382 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_1__TableCache.sql +++ b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_1__TableCache.sql @@ -6,14 +6,14 @@ CREATE TABLE IF NOT EXISTS `table_cache_version` ( `database_name` varchar(256) DEFAULT NULL COMMENT 'db名称', `schema_name` varchar(256) DEFAULT NULL COMMENT 'schema名称', `key` varchar(256) DEFAULT NULL COMMENT '唯一索引', - `version` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '版本', - `table_count` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '表数量', + `version` bigint(20) unsigned NOT NULL COMMENT '版本', + `table_count` bigint(20) unsigned NOT NULL COMMENT '表数量', `status` varchar(256) DEFAULT NULL COMMENT '状态', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='table cache version' ; -create INDEX idx_data_source_id on table_cache_version(data_source_id) ; -create UNIQUE INDEX uk_key on table_cache_version(key) ; +create INDEX idx_table_cache_version_data_source_id on table_cache_version(`data_source_id`) ; +create UNIQUE INDEX uk_table_cache_version_key on table_cache_version(`key`) ; CREATE TABLE IF NOT EXISTS `table_cache` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', @@ -24,15 +24,15 @@ CREATE TABLE IF NOT EXISTS `table_cache` ( `schema_name` varchar(256) DEFAULT NULL COMMENT 'schema名称', `table_name` varchar(256) DEFAULT NULL COMMENT 'table名称', `key` varchar(256) DEFAULT NULL COMMENT '唯一索引', - `version` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '版本', + `version` bigint(20) unsigned NOT NULL COMMENT '版本', `columns` varchar(2048) DEFAULT NULL COMMENT '表字段', - `extend_info` varchar(4096) NULL COMMENT '自定义扩展字段json'; + `extend_info` varchar(2048) NULL COMMENT '自定义扩展字段json', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='table cache' ; -create INDEX idx_data_source_id on table_cache(data_source_id) ; -create INDEX idx_key_version on table_cache(key,version) ; -create INDEX idx_key_table_name on table_cache(key,table_name) ; +create INDEX idx_table_cache_data_source_id on table_cache(`data_source_id`) ; +create INDEX idx_table_cache_key_version on table_cache(`key`,`version`) ; +create INDEX idx_table_cache_key_table_name on table_cache(`key`,`table_name`) ; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java new file mode 100644 index 000000000..906847816 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java @@ -0,0 +1,15 @@ +package ai.chat2db.spi; + +import ai.chat2db.spi.model.TableColumn; + +public interface ColumnBuilder { + + /** + * Generate column sql + * @param column + * @return + */ + String generateColumnSql(TableColumn column); + + +} From 0fff9ea92e71efccb0853fb608f87acf61414e6c Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 20 Sep 2023 16:12:12 +0800 Subject: [PATCH 0778/1069] support table online edit --- .../chat2db/plugin/mysql/MysqlMetaData.java | 6 + .../plugin/mysql/builder/MysqlSqlBuilder.java | 100 ++++++++++++++++ .../mysql/type/MysqlColumnTypeEnum.java | 100 +++++++++++++++- .../plugin/mysql/type/MysqlIndexTypeEnum.java | 109 ++++++++++++++++++ .../domain/core/impl/TableServiceImpl.java | 28 +++-- .../api/controller/rdb/TableController.java | 2 +- .../controller/rdb/request/TableRequest.java | 3 +- .../java/ai/chat2db/spi/ColumnBuilder.java | 8 +- .../main/java/ai/chat2db/spi/MetaData.java | 7 ++ .../main/java/ai/chat2db/spi/SqlBuilder.java | 8 +- .../java/ai/chat2db/spi/enums/EditStatus.java | 10 ++ .../chat2db/spi/jdbc/DefaultMetaService.java | 6 + .../chat2db/spi/jdbc/DefaultSqlBuilder.java | 52 +-------- .../main/java/ai/chat2db/spi/model/Table.java | 13 +++ .../ai/chat2db/spi/model/TableColumn.java | 4 + .../java/ai/chat2db/spi/model/TableIndex.java | 3 + .../ai/chat2db/spi/sql/Chat2DBContext.java | 5 + 17 files changed, 393 insertions(+), 71 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/EditStatus.java 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 da2679df3..15f8acf01 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 @@ -4,7 +4,9 @@ import java.util.ArrayList; import java.util.List; +import ai.chat2db.plugin.mysql.builder.MysqlSqlBuilder; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Function; import ai.chat2db.spi.model.Procedure; @@ -133,4 +135,8 @@ public Table view(Connection connection, String databaseName, String schemaName, return table; }); } + @Override + public SqlBuilder getSqlBuilder() { + return new MysqlSqlBuilder(); + } } 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 new file mode 100644 index 000000000..88d616f8d --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java @@ -0,0 +1,100 @@ +package ai.chat2db.plugin.mysql.builder; + +import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; +import ai.chat2db.plugin.mysql.type.MysqlIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import org.apache.commons.lang3.StringUtils; + + +public class MysqlSqlBuilder implements SqlBuilder { + @Override + public String buildCreateTableSql(Table table) { + StringBuilder script = new StringBuilder(); + script.append("CREATE TABLE "); + script.append("`").append(table.getName()).append("`").append(" (").append("\n"); + + // append column + for (TableColumn column : table.getColumnList()) { + MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + } + + // append primary key and index + for (TableIndex tableIndex : table.getIndexList()) { + MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append("\n)"); + + + if (StringUtils.isNotBlank(table.getEngine())) { + script.append(" ENGINE=").append(table.getEngine()); + } + + if (StringUtils.isNotBlank(table.getCharset())) { + script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); + } + + if (StringUtils.isNotBlank(table.getCollate())) { + script.append(" COLLATE=").append(table.getCollate()); + } + + if (table.getIncrementValue() != null) { + script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); + } + + if (StringUtils.isNotBlank(table.getComment())) { + script.append(" COMMENT='").append(table.getComment()).append("'"); + } + + if (StringUtils.isNotBlank(table.getPartition())) { + script.append(" \n").append(table.getPartition()); + } + script.append(";"); + + return script.toString(); + } + + @Override + public String buildModifyTaleSql(Table oldTable, Table newTable) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE ").append("`").append(oldTable.getName()).append("`").append("\n"); + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("\t").append("RENAME TO ").append("`").append(newTable.getName()).append("`").append(",\n"); + } + if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { + script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); + } + if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { + script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); + } + + // append modify column + for (TableColumn tableColumn : newTable.getColumnList()) { + if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { + MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + } + } + + // append modify index + for (TableIndex tableIndex : newTable.getIndexList()) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus())) { + MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); + } + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append(";"); + + return script.toString(); + } + + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index f1ee9cd98..cd74bb357 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -1,13 +1,13 @@ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; import ai.chat2db.spi.model.ColumnType; import ai.chat2db.spi.model.TableColumn; import com.google.common.collect.Maps; import org.apache.commons.lang3.StringUtils; import java.util.Arrays; -import java.util.List; import java.util.Map; public enum MysqlColumnTypeEnum implements ColumnBuilder { @@ -114,6 +114,10 @@ public enum MysqlColumnTypeEnum implements ColumnBuilder { private ColumnType columnType; + public static MysqlColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + public ColumnType getColumnType() { return columnType; } @@ -133,17 +137,104 @@ public ColumnType getColumnType() { @Override - public String generateColumnSql(TableColumn column) { + public String buildCreateColumnSql(TableColumn column) { MysqlColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); if (type == null) { return ""; } StringBuilder script = new StringBuilder(); + script.append("`").append(column.getName()).append("`").append(" "); + script.append(buildDataType(column, type)).append(" "); + script.append(buildNullable(column,type)).append(" "); + + script.append(buildDefaultValue(column,type)).append(" "); + + script.append(buildExt(column,type)).append(" "); + + script.append(buildAutoIncrement(column,type)).append(" "); + + script.append(buildComment(column,type)).append(" "); + + return script.toString(); + } + + @Override + public String buildModifyColumn(TableColumn tableColumn) { + + if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { + return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); + } + if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { + return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); + } + if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { + if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { + return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); + } else { + return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); + } + } + return ""; + } - return null; + private String buildAutoIncrement(TableColumn column, MysqlColumnTypeEnum type) { + if(!type.getColumnType().isSupportAutoIncrement()){ + return ""; + } + if (column.getAutoIncrement() != null && column.getAutoIncrement()) { + return "AUTO_INCREMENT"; + } + return ""; + } + + private String buildComment(TableColumn column, MysqlColumnTypeEnum type) { + if(!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())){ + return ""; + } + return StringUtils.join("COMMENT '",column.getComment(),"'"); + } + + private String buildExt(TableColumn column, MysqlColumnTypeEnum type) { + if(!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())){ + return ""; + } + return column.getComment(); + } + + private String buildDefaultValue(TableColumn column, MysqlColumnTypeEnum type) { + if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ + return ""; + } + if(Arrays.asList(CHAR,VARCHAR,BINARY,VARBINARY, SET,ENUM).contains(type)){ + return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); + } + + if(Arrays.asList(DATE,TIME,YEAR).contains(type)){ + return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); + } + + if(Arrays.asList(DATETIME,TIMESTAMP).contains(type)){ + if("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue())){ + return StringUtils.join("DEFAULT ",column.getDefaultValue()); + } + return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); + } + + return StringUtils.join("DEFAULT ",column.getDefaultValue()); + } + + private String buildNullable(TableColumn column,MysqlColumnTypeEnum type) { + if(!type.getColumnType().isSupportNullable()){ + return ""; + } + if (1==column.getNullable()) { + return "NULL"; + } else { + return "NOT NULL"; + } } private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { @@ -190,6 +281,9 @@ private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { } if(Arrays.asList(SET,ENUM).contains(type)){ + if(!StringUtils.isEmpty( column.getDefaultValue())){ + return StringUtils.join(columnType,"(",column.getDefaultValue(),")"); + } //List enumList = column. } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java new file mode 100644 index 000000000..c76752a3b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -0,0 +1,109 @@ +package ai.chat2db.plugin.mysql.type; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import org.apache.commons.lang3.StringUtils; + +public enum MysqlIndexTypeEnum { + + PRIMARY_KEY("Primary", "PRIMARY KEY"), + + NORMAL("Normal", "KEY"), + + UNIQUE("Unique", "UNIQUE KEY"), + + FULLTEXT("Fulltext", "FULLTEXT KEY"), + + SPATIAL("Spatial", "SPATIAL KEY"); + + public String getName() { + return name; + } + + private String name; + + + public String getKeyword() { + return keyword; + } + + private String keyword; + + MysqlIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + } + + + public static MysqlIndexTypeEnum getByType(String type) { + for (MysqlIndexTypeEnum value : MysqlIndexTypeEnum.values()) { + if (value.name.equals(type)) { + return value; + } + } + return null; + } + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + + script.append(keyword).append(" "); + + script.append(buildIndexName(tableIndex)).append(" "); + + script.append(buildIndexColumn(tableIndex)).append(" "); + + script.append(buildIndexComment(tableIndex)).append(" "); + + return script.toString(); + } + + private String buildIndexComment(TableIndex tableIndex) { + if(StringUtils.isBlank(tableIndex.getComment())){ + return ""; + }else { + return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); + } + + } + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + script.append("`").append(column.getColumnName()).append("`").append(","); + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + if(this.equals(PRIMARY_KEY)){ + return ""; + }else { + return "`"+tableIndex.getName()+"`"; + } + } + + public String buildModifyIndex(TableIndex tableIndex) { + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join("MODIFY ", buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (MysqlIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { + return StringUtils.join("DROP PRIMARY KEY"); + } + return StringUtils.join("DROP KEY `", tableIndex.getName()); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 25e77f7b3..022ed13d1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -18,6 +18,7 @@ import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.util.SqlUtils; @@ -52,7 +53,7 @@ public DataResult showCreateTable(ShowCreateTableParam param) { @Override public ActionResult drop(DropParam param) { DBManage metaSchema = Chat2DBContext.getDBManage(); - metaSchema.dropTable(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getTableSchema(), param.getTableName()); + metaSchema.dropTable(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getTableSchema(), param.getTableName()); return ActionResult.isSuccess(); } @@ -71,13 +72,13 @@ public DataResult alterTableExample(String dbType) { @Override public DataResult
    query(TableQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); - List
    tables = metaSchema.tables(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + List
    tables = metaSchema.tables(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); if (!CollectionUtils.isEmpty(tables)) { Table table = tables.get(0); table.setIndexList( - metaSchema.indexes(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); table.setColumnList( - metaSchema.columns(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); return DataResult.of(table); } return DataResult.of(null); @@ -85,18 +86,25 @@ public DataResult
    query(TableQueryParam param, TableSelector selector) { @Override public ListResult buildSql(Table oldTable, Table newTable) { - return ListResult.of(SqlUtils.buildSql(oldTable, newTable)); + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(oldTable.getDbType()); + List sqls = new ArrayList<>(); + if (oldTable == null) { + sqls.add(Sql.builder().sql(sqlBuilder.buildCreateTableSql(newTable)).build()); + } else { + sqls.add(Sql.builder().sql(sqlBuilder.buildModifyTaleSql(oldTable, newTable)).build()); + } + return ListResult.of(sqls); } @Override public PageResult
    pageQuery(TablePageQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); - String tableKey = getTableKey(param.getDataSourceId(),param.getDatabaseName(), param.getSchemaName()); + String tableKey = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); List
    list = CacheManage.getList(tableKey, Table.class, - (key) -> param.isRefresh(), (key) -> - metaSchema.tables(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + (key) -> param.isRefresh(), (key) -> + metaSchema.tables(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); list = pinTable(list, param); if (CollectionUtils.isEmpty(list)) { @@ -136,13 +144,13 @@ private List
    pinTable(List
    list, TablePageQueryParam param) { @Override public List queryColumns(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - return metaSchema.columns(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName(), null); + return metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName(), null); } @Override public List queryIndexes(TableQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); - return metaSchema.indexes(Chat2DBContext.getConnection(),param.getDatabaseName(), param.getSchemaName(), param.getTableName()); + return metaSchema.indexes(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 83a091ed5..4aaffa7d8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -167,7 +167,7 @@ public DataResult query(@Valid TableDetailQueryRequest request) { * @return */ @PostMapping("/modify/sql") - public ListResult modifySql(@Valid TableModifySqlRequest request) { + public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest request) { return tableService.buildSql( rdbWebConverter.tableRequest2param(request.getOldTable()), rdbWebConverter.tableRequest2param(request.getNewTable())) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java index eb09e014d..169ff4e2d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java @@ -2,6 +2,7 @@ import java.util.List; +import ai.chat2db.spi.model.TableColumn; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -30,7 +31,7 @@ public class TableRequest { /** * 列 */ - private List columnList; + private List columnList; /** * 索引 diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java index 906847816..6fec2695b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/ColumnBuilder.java @@ -9,7 +9,13 @@ public interface ColumnBuilder { * @param column * @return */ - String generateColumnSql(TableColumn column); + String buildCreateColumnSql(TableColumn column); + /** + * Build modify column sql + * @param tableColumn + * @return + */ + String buildModifyColumn(TableColumn tableColumn); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 3e7a85ef7..6ac13f537 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -180,4 +180,11 @@ List indexes(Connection connection, @NotEmpty String databaseName, S * @return */ List types(Connection connection); + + + /** + * Get sql builder. + * @return + */ + SqlBuilder getSqlBuilder(); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java index e34d0b1b0..a219a40b1 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java @@ -7,22 +7,18 @@ public interface SqlBuilder { /** * Generate create table sql * - * @param databaseName - * @param schemaName * @param table * @return */ - String generateCreateTableSql(String databaseName, String schemaName, Table table); + String buildCreateTableSql(Table table); /** * Generate modify table sql * - * @param databaseName - * @param schemaName * @param newTable * @param oldTable * @return */ - String generateModifyTaleSql(String databaseName, String schemaName, Table newTable, Table oldTable); + String buildModifyTaleSql(Table oldTable, Table newTable); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/EditStatus.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/EditStatus.java new file mode 100644 index 000000000..dc39dc646 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/EditStatus.java @@ -0,0 +1,10 @@ +package ai.chat2db.spi.enums; + +public enum EditStatus { + + DELETE, + + ADD, + + MODIFY; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 4dd49ee90..7d58cd26c 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -5,6 +5,7 @@ import java.util.Map; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; @@ -93,4 +94,9 @@ public Procedure procedure(Connection connection, String databaseName, String sc public List types(Connection connection) { return SQLExecutor.getInstance().types(connection); } + + @Override + public SqlBuilder getSqlBuilder() { + return new DefaultSqlBuilder(); + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java index 43e669fe0..d32c5629e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -2,62 +2,16 @@ import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.Table; -import org.apache.commons.lang3.StringUtils; - -import java.util.Arrays; public class DefaultSqlBuilder implements SqlBuilder { + @Override - public String generateCreateTableSql(String databaseName, String schemaName, Table table) { - StringBuilder script = new StringBuilder(); - script.append("CREATE TABLE "); - script.append("`").append(table.getName()).append("`").append(" (").append("\n"); - for (ai.chat2db.spi.model.TableColumn column : table.getColumnList()) { - script.append("`").append(column.getName()).append("`").append(" "); - script.append(column.getDataType()).append(" "); - if (column.getColumnSize() != null) { - script.append("(").append(column.getColumnSize()).append(") "); - } -// if (column.getType().getDecimal() != null) { -// script.append("(").append(column.getType().getLength()).append(",").append(column.getType().getDecimal()).append(") "); -// } -// if (column.isNullable()) { -// script.append("NULL "); -// } else { -// script.append("NOT NULL "); -// } -// if (column.getDefaultValue() != null) { -// script.append("DEFAULT ").append(column.getDefaultValue()).append(" "); -// } -// if (column.getComment() != null) { -// script.append("COMMENT '").append(column.getComment()).append("' "); -// } - script.append(",").append("\n"); - } + public String buildCreateTableSql(Table table) { return null; } - private String generateColumnSql(ai.chat2db.spi.model.TableColumn column) { - if(StringUtils.isEmpty(column.getColumnType())){ - return ""; - } - String type = column.getColumnType().toUpperCase(); - - StringBuilder script = new StringBuilder(); - script.append("`").append(column.getName()).append("`").append(" "); - script.append(type).append(" "); - if(Arrays.asList("","").contains(type)){ - script.append("(").append(column.getColumnSize()).append(") "); - } - - - - - return script.toString(); - } - @Override - public String generateModifyTaleSql(String databaseName, String schemaName, Table newTable, Table oldTable) { + public String buildModifyTaleSql(Table oldTable, Table newTable) { return null; } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java index bbd3dcb2d..4ee22fece 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Table.java @@ -76,5 +76,18 @@ public class Table { * ddl */ private String ddl; + + + private String engine; + + + private String charset; + + + private String collate; + + private Long incrementValue; + + private String partition; } 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 9a5db8867..6a67573cf 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 @@ -164,4 +164,8 @@ public class TableColumn { private Boolean generatedColumn; + private String extent; + + + private String editStatus; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java index 1122f1e49..18caddf1b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java @@ -60,4 +60,7 @@ public class TableIndex { * 索引包含的列 */ private List columnList; + + + private String editStatus; } 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 61f58d442..41638d0c5 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 @@ -11,6 +11,7 @@ import ai.chat2db.spi.DBManage; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.Plugin; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.config.DBConfig; import ai.chat2db.spi.config.DriverConfig; import com.jcraft.jsch.JSchException; @@ -42,6 +43,10 @@ public static DriverConfig getDefaultDriverConfig(String dbType) { return PLUGIN_MAP.get(dbType).getDBConfig().getDefaultDriverConfig(); } + public static SqlBuilder getSqlBuilder(String dbType) { + return PLUGIN_MAP.get(dbType).getMetaData().getSqlBuilder(); + } + /** * 获取当前线程的ContentContext * From f88014316e6676950cfff3bba5ae8e03e0d46cda Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 20 Sep 2023 16:53:15 +0800 Subject: [PATCH 0779/1069] support table online edit --- .../java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java | 2 +- .../src/main/java/ai/chat2db/spi/model/TableIndex.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index c76752a3b..48be0ef45 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -104,6 +104,6 @@ private String buildDropIndex(TableIndex tableIndex) { if (MysqlIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } - return StringUtils.join("DROP KEY `", tableIndex.getName()); + return StringUtils.join("DROP KEY `", tableIndex.getOldName()); } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java index 18caddf1b..3561490fc 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java @@ -19,6 +19,8 @@ @AllArgsConstructor public class TableIndex { + private String oldName; + /** * 索引名称 */ From 24a8534187132be221ffb4d20f5d2a9a9a653808 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 20 Sep 2023 16:56:57 +0800 Subject: [PATCH 0780/1069] support table online edit --- .../server/web/api/controller/rdb/TableController.java | 8 ++++---- .../server/web/api/controller/rdb/vo/ColumnVO.java | 5 +++++ .../chat2db/server/web/api/controller/rdb/vo/TableVO.java | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 4aaffa7d8..3fcbc0985 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -150,14 +150,14 @@ public DataResult updateExample(@Valid TableUpdateDdlQueryRequest reques * @return */ @GetMapping("/query") - public DataResult query(@Valid TableDetailQueryRequest request) { + public DataResult
    query(@Valid TableDetailQueryRequest request) { TableQueryParam queryParam = rdbWebConverter.tableRequest2param(request); TableSelector tableSelector = new TableSelector(); tableSelector.setColumnList(true); tableSelector.setIndexList(true); - DataResult
    tableDTODataResult = tableService.query(queryParam, tableSelector); - TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); - return DataResult.of(tableVO); + return tableService.query(queryParam, tableSelector); + //TableVO tableVO = rdbWebConverter.tableDto2vo(tableDTODataResult.getData()); + //return DataResult.of(tableVO); } /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java index fecb2d06c..241a1345c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ColumnVO.java @@ -150,4 +150,9 @@ public class ColumnVO { private Boolean generatedColumn; + private String extent; + + + private String editStatus; + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java index 13f49b684..fd18e242f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/TableVO.java @@ -2,6 +2,8 @@ import java.util.List; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; import lombok.Data; /** @@ -25,12 +27,12 @@ public class TableVO { /** * 列 */ - private List columnList; + private List columnList; /** * 索引 */ - private List indexList; + private List indexList; /** * 是否已经被固定 From 00f336a405bc8292d8624228b4f3224bba914163 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 20 Sep 2023 17:00:20 +0800 Subject: [PATCH 0781/1069] support table online edit --- .../src/main/java/ai/chat2db/spi/model/TableColumn.java | 7 ------- 1 file changed, 7 deletions(-) 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 6a67573cf..bf07225ee 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 @@ -118,13 +118,6 @@ public class TableColumn { @JsonAlias({"NUM_PREC_RADIX"}) private Integer numPrecRadix; - /** - * is NULL allowed. - * columnNoNulls - might not allow NULL values - * columnNullable - definitely allows NULL values - * columnNullableUnknown - nullability unknown - */ - private Integer nullableInt; /** * unused From b804a9b10d854d77de851793b6b85b61e7df8722 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 20 Sep 2023 23:03:28 +0800 Subject: [PATCH 0782/1069] add eslint edit table --- chat2db-client/.eslintrc.js | 127 +++ chat2db-client/.gitignore | 1 + chat2db-client/.vscode/settings.json | 11 +- chat2db-client/package.json | 11 + .../DatabaseTableEditor/BaseInfo/index.tsx | 5 +- .../DatabaseTableEditor/ColumnList/index.tsx | 289 +++---- .../DatabaseTableEditor/IncludeCol/index.tsx | 18 +- .../DatabaseTableEditor/IndexList/index.tsx | 284 +++--- .../src/blocks/DatabaseTableEditor/index.tsx | 137 +-- chat2db-client/src/blocks/Setting/index.tsx | 4 +- chat2db-client/src/constants/editTable.ts | 11 +- .../components/WorkspaceRightNew/index.tsx | 402 +++++---- chat2db-client/src/service/sql.ts | 4 +- chat2db-client/src/styles/global.less | 4 +- chat2db-client/src/typings/editTable.ts | 45 +- chat2db-client/yarn.lock | 808 +++++++++++++++++- 16 files changed, 1555 insertions(+), 606 deletions(-) create mode 100644 chat2db-client/.eslintrc.js diff --git a/chat2db-client/.eslintrc.js b/chat2db-client/.eslintrc.js new file mode 100644 index 000000000..f6cbe181a --- /dev/null +++ b/chat2db-client/.eslintrc.js @@ -0,0 +1,127 @@ +module.exports = { + parser: '@typescript-eslint/parser', + env: { + browser: true, + es2021: true, + }, + plugins: ['@typescript-eslint', 'babel', 'react-hooks', 'react'], + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:react/recommended', + // 'airbnb-base', // airbnb-base中已经包含了eslint-plugin-import + // 'prettier', // 使得eslint中的样式规范失效,遵循prettier中的样式规范 + // 'prettier/@typescript-eslint', // 使得@typescript-eslint中的样式规范失效,遵循prettier中的样式规范 + ], + overrides: [ + { + env: { + node: true, + }, + files: ['.eslintrc.{js,cjs}'], // + parserOptions: { + sourceType: 'script', + }, + }, + ], + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + }, + rules: { + 'func-names': 0, // 函数表达式必须有名字 + 'one-var': [1, 'never'], // 连续声明 + 'prefer-const': 1, // 首选const + 'no-unused-expressions': 0, // 禁止无用的表达式 + 'new-cap': 2, // 构造函数首字母大写 + 'prefer-arrow-callback': 2, // 首选箭头函数 + 'arrow-body-style': 0, // 箭头函数体使用大括号 + 'max-len': [ + // 一行最大长度 + 1, + { + code: 120, + ignoreStrings: true, + ignoreUrls: true, + ignoreRegExpLiterals: true, + }, + ], + 'consistent-return': 'off', // return 后面是否允许省略 + 'default-case': 2, // switch 语句必须有 default + 'prefer-rest-params': 2, // 必须使用解构 ...args 来代替 arguments + 'no-script-url': 0, // 禁止使用 javascript:void(0) + // 'no-console': [ // 禁止使用 console + // 2, + // { + // allow: ['info', 'error', 'warn'], + // }, + // ], + 'no-duplicate-imports': 2, // 禁止重复 import + 'newline-per-chained-call': 2, // 链式调用必须换行 + 'no-underscore-dangle': 2, // 禁止标识符中有悬空下划线 + 'eol-last': 2, // 文件以单一的换行符结束 + 'no-useless-rename': 2, // 禁止无用的重命名 + 'no-undef': 0, // 禁止使用未定义的变量 + 'class-methods-use-this': 0, // class 的非静态方法必须包含 this + 'prefer-destructuring': 0, // 优先使用数组和对象解构 + 'no-unused-vars': 0, // 禁止未使用过的变量 + '@typescript-eslint/no-unused-vars': 1, // 禁止未使用过的变量 + 'react/self-closing-comp': 2, // 非单行 JSX 必须使用括号包裹 + 'react/jsx-indent-props': [2, 2], // jsx props 缩进 + 'no-plusplus': 0, // 禁止使用 ++,-- + 'react/jsx-uses-vars': 1, // jsx 文件中禁止使用变量 + // 'react/no-multi-comp': [ // 禁止一个文件中定义多个组件 + // 2, + // { + // ignoreStateless: true, + // }, + // ], + 'react/jsx-uses-react': 2, // jsx 文件中禁止使用 React + 'react/react-in-jsx-scope': 2, // jsx 文件中禁止使用 React + 'react/sort-comp': 1, // 组件内方法顺序 + 'react/jsx-tag-spacing': 2, // jsx 中的属性禁止使用空格 + 'react/jsx-no-bind': 0, // jsx 中禁止使用 bind + 'react/jsx-closing-bracket-location': 2, // jsx 中的右括号必须换行 + 'react/prefer-stateless-function': 0, // 优先使用无状态组件 + 'react/display-name': 0, // 组件必须写 displayName + 'react/prop-types': 0, // 组件必须写 propTypes + 'import/prefer-default-export': 0, // 优先使用 export default + '@typescript-eslint/no-var-requires': 2, // 禁止 require() 使用表达式 + 'no-use-before-define': 0, // 禁止定义前使用 + '@typescript-eslint/no-use-before-define': [ + // 禁止定义前使用 + 0, + // { + // functions: false, + // }, + ], + '@typescript-eslint/explicit-function-return-type': 0, // 函数必须有返回值 + '@typescript-eslint/interface-name-prefix': 0, // 接口名称必须以 I 开头 + '@typescript-eslint/explicit-module-boundary-types': 0, // 导出函数和类的公共方法必须声明返回类型 + 'no-shadow': 0, // 禁止变量名与上层作用域内的定义过的变量重复 + '@typescript-eslint/no-shadow': 1, // 禁止变量名与上层作用域内的定义过的变量重复TODO: 为2是不是好点? + 'no-invalid-this': 0, // 禁止 this 关键字出现在类和类对象之外 + 'no-await-in-loop': 'off', // 禁止在循环中出现 await + 'array-callback-return': 'off', // 数组方法的回调函数中必须有 return 语句 + 'no-restricted-syntax': 'off', // 禁止使用特定的语法 + '@typescript-eslint/no-explicit-any': 0, // 禁止使用 any + 'import/no-extraneous-dependencies': 0, // 禁止使用无关的 package + 'import/no-unresolved': 0, // 禁止使用无关的 package + '@typescript-eslint/explicit-member-accessibility': 0, // 类的成员之间是否需要空行 + '@typescript-eslint/no-object-literal-type-assertion': 0, // 禁止使用 as Type + 'react/no-find-dom-node': 0, // 禁止使用 findDOMNode + 'no-param-reassign': [ + // 禁止对函数参数再赋值 + 2, + { + props: false, + }, + ], + 'arrow-parens': 0, // 箭头函数参数括号 + indent: 0, // 缩进 + 'operator-linebreak': [0], // 换行符位置 + 'max-classes-per-file': [2, 10], // 一个文件最多定义几个类 + '@typescript-eslint/no-empty-function': [0], // 禁止空函数 + 'import/extensions': 0, // 禁止导入文件时带上文件后缀 + }, +}; diff --git a/chat2db-client/.gitignore b/chat2db-client/.gitignore index c8122000c..529112d3f 100644 --- a/chat2db-client/.gitignore +++ b/chat2db-client/.gitignore @@ -7,6 +7,7 @@ /src/.umi-test /dist .swc +./yarn-error.log /release diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index e8f829bd6..f4f7285bb 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -8,7 +8,7 @@ "editor.formatOnSave": true, "prettier.bracketSpacing": true, // 在对象,数组括号与文字之间加空格 "{ foo: bar }" "[typescriptreact]": { - "editor.defaultFormatter": "vscode.typescript-language-features" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[less]": { "editor.defaultFormatter": "esbenp.prettier-vscode" @@ -22,6 +22,7 @@ "ahooks", "antd", "Appstore", + "asar", "AZUREAI", "bgcolor", "Cascader", @@ -40,27 +41,35 @@ "fulltext", "gtag", "hexi", + "icns", "Iconfont", "indexs", "JDBC", "KEYPAIR", "KINGBASE", + "linebreak", "lsof", "MARIADB", "Mddhhmmss", "Menlo", "netstat", + "nsis", "OCEANBASE", "OPENAI", + "packagejson", + "Parens", "pgsql", + "plusplus", "pnpm", "POSTGRESQL", + "Prec", "remaininguses", "RESTAI", "scrollbar", "Sercurity", "sortablejs", "SQLSERVER", + "tailwindcss", "Tigger", "ueabe", "ueabf", diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 067a1bec7..eeb3b5ea2 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -53,12 +53,23 @@ "@types/react": "^18.0.33", "@types/react-dom": "^18.0.11", "@types/uuid": "^9.0.1", + "@typescript-eslint/eslint-plugin": "^6.7.2", + "@typescript-eslint/parser": "^6.7.2", "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", + "eslint": "^8.49.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-config-prettier": "^9.0.0", + "eslint-import-resolver-webpack": "^0.13.7", + "eslint-plugin-babel": "^5.3.1", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-prettier": "^5.0.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", "is-electron": "^2.2.2", "prettier": "^2", "prettier-plugin-organize-imports": "^2", diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index 9200b9d84..e5d634500 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useState, useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef } from 'react'; +import React, { useContext, useEffect, useImperativeHandle, ForwardedRef, forwardRef } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Form, Input } from 'antd'; @@ -6,7 +6,6 @@ import { Context } from '../index'; import { IBaseInfo } from '@/typings'; import i18n from '@/i18n'; - export interface IBaseInfoRef { getBaseInfo: () => IBaseInfo; } @@ -51,4 +50,4 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => ); }); -export default BaseInfo +export default BaseInfo; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index d8dbb32ad..88cec6fef 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,46 +1,31 @@ -import React, { memo, useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; +import React, { useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; -import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; import type { DragEndEvent } from '@dnd-kit/core'; import { DndContext } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; -import { - arrayMove, - SortableContext, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; +import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; import sqlService from '@/service/sql'; import { Context } from '../index'; -import { IColumnItem } from '@/typings' +import { IColumnItemNew } from '@/typings'; import i18n from '@/i18n'; +import { EditColumnOperationType } from '@/constants'; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } -interface IProps { - -} +interface IProps {} export interface IColumnListRef { - getColumnListInfo: () => IColumnItem[]; + getColumnListInfo: () => IColumnItemNew[]; } const Row = ({ children, ...props }: RowProps) => { - const { - attributes, - listeners, - setNodeRef, - setActivatorNodeRef, - transform, - transition, - isDragging, - } = useSortable({ + const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ id: props['data-row-key'], }); @@ -57,11 +42,7 @@ const Row = ({ children, ...props }: RowProps) => { if ((child as React.ReactElement).key === 'sort') { return React.cloneElement(child as React.ReactElement, { children: ( - + ), }); } @@ -74,183 +55,177 @@ const Row = ({ children, ...props }: RowProps) => { const createInitialData = () => { return { key: uuidv4(), + oldName: null, name: null, - columnSize: null, + tableName: null, columnType: null, - nullable: null, - comment: null, - primaryKey: null, - defaultValue: null, dataType: null, + defaultValue: null, autoIncrement: null, - numericPrecision: null, - numericScale: null, - characterMaximumLength: null, + comment: null, + primaryKey: null, + schemaName: null, + databaseName: null, + typeName: null, + columnSize: null, + bufferLength: null, + decimalDigits: null, + numPrecRadix: null, + nullableInt: null, + sqlDataType: null, + sqlDatetimeSub: null, + charOctetLength: null, + ordinalPosition: null, + nullable: null, + generatedColumn: null, }; -} +}; const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) => { - const { dataSourceId, databaseName, tableDetails } = useContext(Context); - const [dataSource, setDataSource] = useState([createInitialData()]); + const { dataSourceId, databaseName, schemaName, tableDetails } = useContext(Context); + const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(''); - const [databaseFieldTypeList, setDatabaseFieldTypeList] = useState([]) + const [editingKey, setEditingKey] = useState(undefined); + const [databaseFieldTypeList, setDatabaseFieldTypeList] = useState([]); - const isEditing = (record: IColumnItem) => record.key === editingKey; + const isEditing = (record: IColumnItemNew) => record.key === editingKey; - const edit = (record: Partial & { key: React.Key }) => { + const edit = (record: IColumnItemNew) => { form.setFieldsValue({ ...record }); setEditingKey(record.key); }; useEffect(() => { if (tableDetails) { - const list = tableDetails?.columnList?.map(t => { - return { - ...t, - key: uuidv4(), - } - }) || [] - setDataSource(list) + const list = + tableDetails?.columnList?.map((t) => { + return { + ...t, + key: uuidv4(), + }; + }) || []; + setDataSource(list); } - }, [tableDetails]) + }, [tableDetails]); useEffect(() => { // 获取数据库字段类型列表 - sqlService.getDatabaseFieldTypeList({ - dataSourceId, - databaseName, - }).then(res => { - setDatabaseFieldTypeList(res.map(i => i.typeName)) - }) - }, []) + sqlService + .getDatabaseFieldTypeList({ + dataSourceId, + databaseName, + }) + .then((res) => { + setDatabaseFieldTypeList(res.map((i) => i.typeName)); + }); + }, []); const columns = [ { key: 'sort', width: '40px', - align: 'center' + align: 'center', + }, + { + title: 'O T', + dataIndex: 'operationType', + width: '120px', + align: 'center', + render: (text: EditColumnOperationType) => { + return text === EditColumnOperationType.Add ? ( + + ) : ( + + ); + }, }, { title: i18n('editTable.label.columnName'), dataIndex: 'name', width: '160px', - render: (text: string, record: IColumnItem) => { + render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return editable ? ( - + ) : ( -
    edit(record)} - > +
    edit(record)}> {text}
    ); - } + }, }, { title: i18n('editTable.label.columnSize'), dataIndex: 'columnSize', width: '120px', - render: (text: string, record: IColumnItem) => { + render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return editable ? ( - + ) : ( -
    edit(record)} - > +
    edit(record)}> {text}
    ); - } + }, }, { title: i18n('editTable.label.columnType'), dataIndex: 'columnType', width: '200px', - render: (text: string, record: IColumnItem) => { + render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); - return
    - { - editable ? ( - - ({ label: i, value: i }))} /> ) : ( -
    edit(record)} - > +
    edit(record)}> {text}
    - ) - } -
    - } + )} +
    + ); + }, }, { title: i18n('editTable.label.nullable'), dataIndex: 'nullable', width: '100px', - render: (nullable: number, record: IColumnItem) => { + render: (nullable: number, record: IColumnItemNew) => { const editable = isEditing(record); return editable ? ( - + ) : ( -
    edit(record)} - > +
    edit(record)}>
    ); - } + }, }, { title: i18n('editTable.label.comment'), dataIndex: 'comment', - render: (text: string, record: IColumnItem) => { + render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return editable ? ( - + ) : ( -
    edit(record)} - > +
    edit(record)}> {text}
    ); - } + }, }, ]; @@ -265,10 +240,11 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }; const handelFieldsChange = (field: any) => { - let { name: nameList, value } = field[0]; + let { value } = field[0]; + const { name: nameList } = field[0]; const name = nameList[0]; if (name === 'nullable') { - value = value ? 1 : 0 + value = value ? 1 : 0; } const newData = dataSource.map((item) => { if (item.key === editingKey) { @@ -280,44 +256,64 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) return item; }); setDataSource(newData); - } + }; const addData = () => { - const newData = createInitialData() - setDataSource([...dataSource, newData]) - edit(newData) - } + const newData = { + ...createInitialData(), + operationType: EditColumnOperationType.Add, + }; + setDataSource([...dataSource, newData]); + edit(newData); + }; const deleteData = () => { - setDataSource(dataSource.filter(i => i.key !== editingKey)) - } + setDataSource( + dataSource.map((i) => { + if (i.key === editingKey) { + return { + ...i, + operationType: EditColumnOperationType.Delete, + }; + } + return i; + }), + ); + }; const moveData = (action: 'up' | 'down') => { - const index = dataSource.findIndex(i => i.key === editingKey) + const index = dataSource.findIndex((i) => i.key === editingKey); if (index === -1) { - return + return; } if (action === 'up') { if (index === 0) { - return + return; } - const newData = [...dataSource] - newData[index] = dataSource[index - 1] - newData[index - 1] = dataSource[index] - setDataSource(newData) + const newData = [...dataSource]; + newData[index] = dataSource[index - 1]; + newData[index - 1] = dataSource[index]; + setDataSource(newData); } else { if (index === dataSource.length - 1) { - return + return; } - const newData = [...dataSource] - newData[index] = dataSource[index + 1] - newData[index + 1] = dataSource[index] - setDataSource(newData) + const newData = [...dataSource]; + newData[index] = dataSource[index + 1]; + newData[index + 1] = dataSource[index]; + setDataSource(newData); } - } + }; - function getColumnListInfo(): IColumnItem[] { - return dataSource + function getColumnListInfo(): IColumnItemNew[] { + return dataSource.map((i) => { + return { + ...i, + tableName: tableDetails?.name, + databaseName, + schemaName: schemaName || null, + }; + }); } useImperativeHandle(ref, () => ({ @@ -335,10 +331,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef)
    - i.key)} - strategy={verticalListSortingStrategy} - > + i.key)} strategy={verticalListSortingStrategy}>
    ) pagination={false} rowKey="key" columns={columns as any} - dataSource={dataSource} + dataSource={dataSource.filter((i) => i.operationType !== EditColumnOperationType.Delete)} /> @@ -356,6 +349,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ); -}) +}); export default ColumnList; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 97dd85f0b..f49f5ed67 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -1,10 +1,10 @@ -import React, { memo, useMemo, useState, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; +import React, { useMemo, useState, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import classnames from 'classnames'; -import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal, message } from 'antd'; +import { Table, InputNumber, Form, Select, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; -import { IColumnItem, IIndexIncludeColumnItem } from '@/typings'; +import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import i18n from '@/i18n'; interface IProps { @@ -29,7 +29,7 @@ const createInitialData = () => { cardinality: null, pages: null, filterCondition: null, - prefixLength: null + prefixLength: null, }; }; @@ -50,7 +50,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) useEffect(() => { if (includedColumnList.length) { setDataSource( - includedColumnList.map(t => { + includedColumnList.map((t) => { return { ...t, key: uuidv4(), @@ -60,8 +60,8 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) } }, [includedColumnList]); - const columnList: IColumnItem[] = useMemo(() => { - const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter(i => i.name); + const columnList: IColumnItemNew[] = useMemo(() => { + const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter((i) => i.name); return columnListInfo || []; }, []); @@ -145,8 +145,8 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) const getIncludeColInfo = () => { const includeColInfo: IIndexIncludeColumnItem[] = []; - dataSource.forEach(t => { - columnList.forEach(columnItem => { + dataSource.forEach((t) => { + columnList.forEach((columnItem) => { if (t.columnName === columnItem.name) { includeColInfo.push({ ...createInitialData(), diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 61d0dc7bf..e7fb003b7 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -1,30 +1,32 @@ -import React, { memo, useState, forwardRef, ForwardedRef, useImperativeHandle, useContext, useRef, useMemo, useEffect } from 'react'; +import React, { + useState, + forwardRef, + ForwardedRef, + useImperativeHandle, + useContext, + useRef, + useMemo, + useEffect, +} from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; import type { DragEndEvent } from '@dnd-kit/core'; import { DndContext } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; -import { - arrayMove, - SortableContext, - useSortable, - verticalListSortingStrategy, -} from '@dnd-kit/sortable'; +import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { Table, InputNumber, Input, Form, Select, Checkbox, Button, Modal } from 'antd'; +import { Table, Input, Form, Select, Button, Modal } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import IncludeCol, { IIncludeColRef } from '../IncludeCol'; -import { IColumnItem, IIndexItem, IIndexIncludeColumnItem } from '@/typings'; +import { IIndexItem, IIndexIncludeColumnItem } from '@/typings'; import { IndexesType } from '@/constants'; import { Context } from '../index'; import i18n from '@/i18n'; -const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']] +const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']]; -interface IProps { - -} +interface IProps {} export type IIndexListInfo = IIndexItem[]; @@ -40,15 +42,15 @@ const createInitialData = (): IIndexItem => { type: null, columns: null, comment: null, - } -} + }; +}; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } const IndexList = forwardRef((props: IProps, ref: ForwardedRef) => { - const { tableDetails, columnListRef } = useContext(Context); + const { tableDetails } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(dataSource[0]?.key); @@ -63,14 +65,14 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }; useEffect(() => { - const data = tableDetails.indexList?.map(i => { + const data = tableDetails.indexList?.map((i) => { return { ...i, key: uuidv4(), - } - }) - setDataSource(data || []) - }, [tableDetails]) + }; + }); + setDataSource(data || []); + }, [tableDetails]); const addData = () => { const newData = { @@ -79,44 +81,21 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = name: '', type: null, columns: null, - } - setDataSource([...dataSource, newData]) - edit(newData) - } + }; + setDataSource([...dataSource, newData]); + edit(newData); + }; const deleteData = () => { - setDataSource(dataSource.filter(i => i.key !== editingKey)) - } - - const moveData = (action: 'up' | 'down') => { - const index = dataSource.findIndex(i => i.key === editingKey) - if (index === -1) { - return - } - if (action === 'up') { - if (index === 0) { - return - } - const newData = [...dataSource] - newData[index] = dataSource[index - 1] - newData[index - 1] = dataSource[index] - setDataSource(newData) - } else { - if (index === dataSource.length - 1) { - return - } - const newData = [...dataSource] - newData[index] = dataSource[index + 1] - newData[index + 1] = dataSource[index] - setDataSource(newData) - } - } + setDataSource(dataSource.filter((i) => i.key !== editingKey)); + }; const handelFieldsChange = (field: any) => { - let { name: nameList, value } = field[0]; + let { value } = field[0]; + const { name: nameList } = field[0]; const name = nameList[0]; if (name === 'nullable') { - value = value ? 1 : 0 + value = value ? 1 : 0; } const newData = dataSource.map((item) => { if (item.key === editingKey) { @@ -128,7 +107,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = return item; }); setDataSource(newData); - } + }; const onDragEnd = ({ active, over }: DragEndEvent) => { if (active.id !== over?.id) { @@ -140,21 +119,13 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = } }; - const Row = ({ children, ...props }: RowProps) => { - const { - attributes, - listeners, - setNodeRef, - setActivatorNodeRef, - transform, - transition, - isDragging, - } = useSortable({ - id: props['data-row-key'], + const Row = ({ children, ...rowProps }: RowProps) => { + const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ + id: rowProps['data-row-key'], }); const style: React.CSSProperties = { - ...props.style, + ...rowProps.style, transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), transition, ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), @@ -181,7 +152,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }; function getIndexListInfo(): IIndexListInfo { - return dataSource + return dataSource; } useImperativeHandle(ref, () => ({ @@ -197,8 +168,8 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = title: i18n('editTable.label.index'), width: '70px', render: (text: string, record: IIndexItem) => { - return dataSource.findIndex(i => i.key === record.key) + 1 - } + return dataSource.findIndex((i) => i.key === record.key) + 1; + }, }, { title: i18n('editTable.label.indexName'), @@ -207,19 +178,15 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( - + - ) :
    edit(record)} - > - {text} -
    - } + ) : ( +
    edit(record)}> + {text} +
    + ); + }, }, { title: i18n('editTable.label.indexType'), @@ -228,110 +195,115 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( - + - ) :
    edit(record)} - > - {text} -
    - } + ) : ( +
    edit(record)}> + {text} +
    + ); + }, }, { title: i18n('editTable.label.includeColumn'), dataIndex: 'columnList', render: (columnList: IIndexIncludeColumnItem[], record: IIndexItem) => { const editable = isEditing(record); - const text = columnList?.map(t => { - return `${t.columnName}` - }).join(',') + const text = columnList + ?.map((t) => { + return `${t.columnName}`; + }) + .join(','); return editable ? (
    - { setIncludeColModalOpen(true) }}>{i18n('common.button.edit')} + { + setIncludeColModalOpen(true); + }} + > + {i18n('common.button.edit')} + {text} -
    + ) : ( -
    edit(record)} - > +
    edit(record)}> {text}
    ); - } + }, }, - ]; const getIncludeColInfo = () => { setDataSource( - dataSource.map(i => { - if (i.key === editingKey) { - i.columnList = includeColRef.current?.getIncludeColInfo()! + dataSource.map((i) => { + const columnList = includeColRef.current?.getIncludeColInfo(); + if (i.key === editingKey && columnList) { + i.columnList = columnList; } - return i - }) - ) - setIncludeColModalOpen(false) - } + return i; + }), + ); + setIncludeColModalOpen(false); + }; const indexIncludedColumnList: IIndexIncludeColumnItem[] = useMemo(() => { let data: IIndexIncludeColumnItem[] | null = []; - dataSource.forEach(i => { + dataSource.forEach((i) => { if (i.key === editingKey) { - data = i.columnList + data = i.columnList; } - }) - return data - }, [editingKey]) + }); + return data; + }, [editingKey]); - return
    -
    - - - {/* + return ( +
    +
    + + + {/* */} +
    + + + i.key)} strategy={verticalListSortingStrategy}> +
    + + + + { + setIncludeColModalOpen(false); + }} + maskClosable={false} + destroyOnClose={true} + > + + - - - i.key)} - strategy={verticalListSortingStrategy} - > -
    - - - - { setIncludeColModalOpen(false) }} - maskClosable={false} - destroyOnClose={true} - > - - - -}) - -export default IndexList + ); +}); +export default IndexList; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 0948c9f44..e6e4171eb 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -1,5 +1,5 @@ -import React, { memo, useRef, useState, createContext, useEffect, forwardRef, useMemo } from 'react'; -import { Button, Form } from 'antd'; +import React, { memo, useRef, useState, createContext, useEffect, useMemo } from 'react'; +import { Button } from 'antd'; import styles from './index.less'; import classnames from 'classnames'; import IndexList, { IIndexListRef } from './IndexList'; @@ -10,10 +10,10 @@ import { IEditTableInfo } from '@/typings'; import i18n from '@/i18n'; interface IProps { - dataSourceId: number, - databaseName: string, - schemaName: string | undefined, - tableName?: string + dataSourceId: number; + databaseName: string; + schemaName: string | undefined; + tableName?: string; } interface ITabItem { @@ -31,7 +31,7 @@ interface IContext extends IProps { export const Context = createContext({} as any); -export default memo(function DatabaseTableEditor(props) { +export default memo((props: IProps) => { const { databaseName, dataSourceId, tableName, schemaName } = props; const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); @@ -43,105 +43,110 @@ export default memo(function DatabaseTableEditor(props) { { title: i18n('editTable.tab.basicInfo'), key: 'basic', - component: + component: , }, { title: i18n('editTable.tab.columnInfo'), key: 'column', - component: + component: , }, { title: i18n('editTable.tab.indexInfo'), key: 'index', - component: + component: , }, - ] - }, []) + ]; + }, []); const [currentTab, setCurrentTab] = useState(tabList[0]); function changeTab(item: ITabItem) { - setCurrentTab(item) + setCurrentTab(item); } useEffect(() => { if (tableName) { - let params = { + const params = { databaseName, dataSourceId, tableName, schemaName, - refresh: true - } - sqlService.getTableDetails(params).then(res => { - setTableDetails(res || {}) - setOldTableDetails(res) - }) + refresh: true, + }; + sqlService.getTableDetails(params).then((res) => { + setTableDetails(res || {}); + setOldTableDetails(res); + }); } - }, []) + }, []); function submit() { if (baseInfoRef.current && columnListRef.current && indexListRef.current) { const newTable = { ...baseInfoRef.current.getBaseInfo(), columnList: columnListRef.current.getColumnListInfo()!, - indexList: indexListRef.current.getIndexListInfo()! - } + indexList: indexListRef.current.getIndexListInfo()!, + }; - let params: IModifyTableSqlParams = { + const params: IModifyTableSqlParams = { databaseName, dataSourceId, schemaName, refresh: true, - newTable: JSON.stringify(newTable), - } + newTable, + }; if (tableName) { params.tableName = tableName; - params.oldTable = JSON.stringify(oldTableDetails); + params.oldTable = oldTableDetails; } console.log(newTable); - sqlService.getModifyTableSql(params).then(res => { - console.log(res) - }) + sqlService.getModifyTableSql(params).then((res) => { + console.log(res); + }); } } - return -
    -
    -
    - { - tabList.map((item, index) => { - return
    - {item.title} -
    - }) - } + return ( + +
    +
    +
    + {tabList.map((item) => { + return ( +
    + {item.title} +
    + ); + })} +
    +
    + +
    -
    - +
    + {tabList.map((t) => { + return ( +
    + {t.component} +
    + ); + })}
    -
    - { - tabList.map(t => { - return
    - {t.component} -
    - }) - } -
    -
    -
    - -}) + + ); +}); diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 3e40e9fb2..032ef750a 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useEffect, useMemo, useState } from 'react'; +import React, { ReactNode, useEffect, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import { Modal } from 'antd'; @@ -19,7 +19,7 @@ interface IProps { className?: string; render?: ReactNode; // text?: string; - dispatch: Function; + dispatch: (params: any) => void; } function Setting(props: IProps) { diff --git a/chat2db-client/src/constants/editTable.ts b/chat2db-client/src/constants/editTable.ts index 84d7d232b..09ce4905e 100644 --- a/chat2db-client/src/constants/editTable.ts +++ b/chat2db-client/src/constants/editTable.ts @@ -8,4 +8,13 @@ export enum IndexesType { Fulltext = 'fulltext', // 空间索引 Spatial = 'spatial', -} \ No newline at end of file +} + +export enum EditColumnOperationType { + // 新增 + Add = 'add', + // 修改 + Modify = 'modify', + // 删除 + Delete = 'delete', +} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index 31685b752..1ab7e148f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -2,7 +2,7 @@ import React, { memo, useEffect, useState, useMemo } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; -import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType, WorkspaceTabType, workspaceTabConfig } from '@/constants'; import historyService from '@/service/history'; import sqlService from '@/service/sql'; import TabsNew, { ITabItem } from '@/components/TabsNew'; @@ -15,9 +15,7 @@ import { IAIState } from '@/models/ai'; import { handleLocalStorageSavedConsole } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; -import { IWorkspaceTab } from '@/typings' -import { WorkspaceTabType, workspaceTabConfig } from '@/constants'; - +import { IWorkspaceTab } from '@/typings'; interface IProps { className?: string; workspaceModel: IWorkspaceModelState; @@ -25,49 +23,56 @@ interface IProps { dispatch: any; } -const WorkspaceRight = memo(function (props) { - const { className, aiModel, workspaceModel, dispatch } = props; +const WorkspaceRight = memo((props: IProps) => { + const { className, workspaceModel, dispatch } = props; // 活跃的TabID const [activeConsoleId, setActiveConsoleId] = useState(); // 工作台tab列表 const [workspaceTabList, setWorkspaceTabList] = useState([]); - const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, curConsoleId, createConsoleIntro } = workspaceModel; + const { + curWorkspaceParams, + doubleClickTreeNodeData, + createTabIntro, + openConsoleList, + curConsoleId, + createConsoleIntro, + } = workspaceModel; // 根据保存的console列表生成tab列表 useEffect(() => { - const newTabList = openConsoleList?.map(t => { + const newTabList = openConsoleList?.map((t) => { return { id: t.id, title: t.name, type: t.operationType, - uniqueData: t - } - }) - setWorkspaceTabList(newTabList || []) - }, [openConsoleList]) + uniqueData: t, + }; + }); + setWorkspaceTabList(newTabList || []); + }, [openConsoleList]); useEffect(() => { if (createConsoleIntro) { - if (workspaceTabList.findIndex(t => t.id === createConsoleIntro.id) === -1) { - setWorkspaceTabList([...workspaceTabList, createConsoleIntro]) + if (workspaceTabList.findIndex((t) => t.id === createConsoleIntro.id) === -1) { + setWorkspaceTabList([...workspaceTabList, createConsoleIntro]); } - setActiveConsoleId(createConsoleIntro.id) + setActiveConsoleId(createConsoleIntro.id); } dispatch({ type: 'workspace/setCreateConsoleIntro', payload: undefined, - }) - }, [createConsoleIntro]) + }); + }, [createConsoleIntro]); // 监听编辑表事件 useEffect(() => { if (createTabIntro) { // 如果已经打开了这个表的编辑页面,那么就切换到这个页面 - const flag = workspaceTabList?.find(t => t.uniqueData?.tableName === createTabIntro.treeNodeData.name); + const flag = workspaceTabList?.find((t) => t.uniqueData?.tableName === createTabIntro.treeNodeData.name); if (flag) { setActiveConsoleId(flag.id); - return + return; } const id = uuidV4(); @@ -77,18 +82,18 @@ const WorkspaceRight = memo(function (props) { title: `edit-${createTabIntro.treeNodeData.name}`, uniqueData: { tableName: createTabIntro.treeNodeData.name, - } - } - setWorkspaceTabList([...workspaceTabList, newData]) + }, + }; + setWorkspaceTabList([...workspaceTabList, newData]); setActiveConsoleId(id); // 用完之后就清掉createTabIntro dispatch({ type: 'workspace/setCreateTabIntro', payload: null, - }) + }); } - }, [createTabIntro]) + }, [createTabIntro]); // 监听双击树节点事件 生成console useEffect(() => { @@ -101,158 +106,174 @@ const WorkspaceRight = memo(function (props) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, tableName, dataSourceId } = extraParams || {}; const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { - sqlService.getViewDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - tableName: tableName!, - schemaName, - }).then(res => { - // 更新ddl - const newList = workspaceTabList.map(t => { - if (t.id === consoleId) { - return { - ...t, - uniqueData: { - ...t.uniqueData, - ddl: res.ddl - } - } - } - return t + sqlService + .getViewDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + tableName: tableName!, + schemaName, }) - setWorkspaceTabList(newList || []) - }) - } + .then((res) => { + // 更新ddl + const newList = workspaceTabList.map((t) => { + if (t.id === consoleId) { + return { + ...t, + uniqueData: { + ...t.uniqueData, + ddl: res.ddl, + }, + }; + } + return t; + }); + setWorkspaceTabList(newList || []); + }); + }; const name = doubleClickTreeNodeData.name; createConsole({ doubleClickTreeNodeData, workSpaceTabType: WorkspaceTabType.VIEW, name, - callback + callback, }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TRIGGER) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, triggerName, dataSourceId } = extraParams || {}; - const name = doubleClickTreeNodeData.name + const name = doubleClickTreeNodeData.name; const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { - sqlService.getTriggerDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - triggerName: triggerName!, - schemaName, - }).then(res => { - // 更新ddl - const newList = workspaceTabList.map(t => { - if (t.id === consoleId) { - return { - ...t, - uniqueData: { - ...t.uniqueData, - ddl: res.triggerBody - } - } - } - return t + sqlService + .getTriggerDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + triggerName: triggerName!, + schemaName, }) - setWorkspaceTabList(newList || []) - }) - } + .then((res) => { + // 更新ddl + const newList = workspaceTabList.map((t) => { + if (t.id === consoleId) { + return { + ...t, + uniqueData: { + ...t.uniqueData, + ddl: res.triggerBody, + }, + }; + } + return t; + }); + setWorkspaceTabList(newList || []); + }); + }; createConsole({ doubleClickTreeNodeData, workSpaceTabType: WorkspaceTabType.TRIGGER, name, - callback + callback, }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.PROCEDURE) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, procedureName, dataSourceId } = extraParams || {}; - const name = doubleClickTreeNodeData.name + const name = doubleClickTreeNodeData.name; const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { - sqlService.getProcedureDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - procedureName: procedureName!, - schemaName, - }).then(res => { - // 更新ddl - const newList = workspaceTabList.map(t => { - if (t.id === consoleId) { - return { - ...t, - uniqueData: { - ...t.uniqueData, - ddl: res.procedureBody - } - } - } - return t + sqlService + .getProcedureDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + procedureName: procedureName!, + schemaName, }) - setWorkspaceTabList(newList || []) - }) - } + .then((res) => { + // 更新ddl + const newList = workspaceTabList.map((t) => { + if (t.id === consoleId) { + return { + ...t, + uniqueData: { + ...t.uniqueData, + ddl: res.procedureBody, + }, + }; + } + return t; + }); + setWorkspaceTabList(newList || []); + }); + }; createConsole({ doubleClickTreeNodeData, workSpaceTabType: WorkspaceTabType.PROCEDURE, name, - callback + callback, }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.FUNCTION) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, functionName } = extraParams || {}; - const name = doubleClickTreeNodeData.name + const name = doubleClickTreeNodeData.name; const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { - sqlService.getFunctionDetail({ - dataSourceId: dataSourceId!, - databaseName: databaseName!, - functionName: functionName!, - schemaName, - }).then(res => { - // 更新ddl - const newList = workspaceTabList?.map(t => { - if (t.id === consoleId) { - return { - ...t, - uniqueData: { - ...t.uniqueData, - ddl: res.functionBody - } - } - } - return t + sqlService + .getFunctionDetail({ + dataSourceId: dataSourceId!, + databaseName: databaseName!, + functionName: functionName!, + schemaName, }) - setWorkspaceTabList(newList || []) - }) - } + .then((res) => { + // 更新ddl + const newList = workspaceTabList?.map((t) => { + if (t.id === consoleId) { + return { + ...t, + uniqueData: { + ...t.uniqueData, + ddl: res.functionBody, + }, + }; + } + return t; + }); + setWorkspaceTabList(newList || []); + }); + }; createConsole({ doubleClickTreeNodeData, workSpaceTabType: WorkspaceTabType.FUNCTION, name, - callback + callback, }); } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { // 如果workspaceTabList没有可以添加select * from table的地方那么才需要创建 - const flag = workspaceTabList.some(t => [WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, WorkspaceTabType.PROCEDURE, WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW].includes(t.type)) + const flag = workspaceTabList.some((t) => + [ + WorkspaceTabType.CONSOLE, + WorkspaceTabType.FUNCTION, + WorkspaceTabType.PROCEDURE, + WorkspaceTabType.TRIGGER, + WorkspaceTabType.VIEW, + ].includes(t.type), + ); if (flag) { - return + return; } const { extraParams } = doubleClickTreeNodeData; - const { databaseName, schemaName, tableName, } = extraParams || {}; + const { databaseName, schemaName, tableName } = extraParams || {}; const ddl = `SELECT * FROM ${tableName};\n`; const name = [databaseName, schemaName, 'console'].filter((t) => t).join('-'); createConsole({ doubleClickTreeNodeData, workSpaceTabType: WorkspaceTabType.CONSOLE, name, - ddl + ddl, }); } @@ -264,11 +285,11 @@ const WorkspaceRight = memo(function (props) { useUpdateEffect(() => { if (activeConsoleId) { - localStorage.setItem('active-console-id', activeConsoleId.toString()) + localStorage.setItem('active-console-id', activeConsoleId.toString()); } else { - localStorage.removeItem('active-console-id') + localStorage.removeItem('active-console-id'); } - }, [activeConsoleId]) + }, [activeConsoleId]); // useEffect(() => { // openConsoleListRef.current = openConsoleList; @@ -305,16 +326,16 @@ const WorkspaceRight = memo(function (props) { // }, [openConsoleList]); function createConsole(params: { - doubleClickTreeNodeData: any, - workSpaceTabType: WorkspaceTabType, - name: string, - callback?: Function, - ddl?: string, + doubleClickTreeNodeData: any; + workSpaceTabType: WorkspaceTabType; + name: string; + callback?: Function; + ddl?: string; }) { const { doubleClickTreeNodeData, workSpaceTabType, name, callback, ddl } = params; const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {}; - let newConsole: any = { + const newConsole: any = { name, type: databaseType!, dataSourceId: dataSourceId!, @@ -326,21 +347,24 @@ const WorkspaceRight = memo(function (props) { ddl: ddl || '', tabOpened: ConsoleOpenedStatus.IS_OPEN, }; - historyService.saveConsole(newConsole).then(res => { - const newList = [...workspaceTabList, { - id: res, - title: newConsole.name, - type: workSpaceTabType, - uniqueData: newConsole - }] - setWorkspaceTabList(newList) + historyService.saveConsole(newConsole).then((res) => { + const newList = [ + ...workspaceTabList, + { + id: res, + title: newConsole.name, + type: workSpaceTabType, + uniqueData: newConsole, + }, + ]; + setWorkspaceTabList(newList); callback?.(res, newList); setActiveConsoleId(res); }); } function getConsoleList(callback?: Function) { - let p: any = { + const p: any = { pageNo: 1, pageSize: 999, tabOpened: ConsoleOpenedStatus.IS_OPEN, @@ -368,8 +392,8 @@ const WorkspaceRight = memo(function (props) { // 删除 新增tab const onEdit = (action: 'add' | 'remove', data: ITabItem) => { if (action === 'remove') { - setWorkspaceTabList(workspaceTabList.filter(t => t.id !== data.key)); - const editData = workspaceTabList?.find(t => t.id === data.key); + setWorkspaceTabList(workspaceTabList.filter((t) => t.id !== data.key)); + const editData = workspaceTabList?.find((t) => t.id === data.key); if (editData?.type !== WorkspaceTabType.EditTable) { closeWindowTab(data.key as number); } @@ -381,7 +405,7 @@ const WorkspaceRight = memo(function (props) { const addConsole = () => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; - let newConsole = { + const newConsole = { name: `new console`, ddl: '', dataSourceId: dataSourceId!, @@ -393,13 +417,16 @@ const WorkspaceRight = memo(function (props) { operationType: WorkspaceTabType.CONSOLE, }; historyService.saveConsole(newConsole).then((res) => { - const newList = [...workspaceTabList, { - id: res, - title: newConsole.name, - type: newConsole.operationType, - uniqueData: newConsole - }] - setWorkspaceTabList(newList) + const newList = [ + ...workspaceTabList, + { + id: res, + title: newConsole.name, + type: newConsole.operationType, + uniqueData: newConsole, + }, + ]; + setWorkspaceTabList(newList); setActiveConsoleId(res); }); }; @@ -420,14 +447,18 @@ const WorkspaceRight = memo(function (props) { }; function renderEmpty() { - return
    ; + return ( +
    + +
    + ); } function editableNameOnBlur(t: ITabItem) { let p: any = { id: t.key, - name: t.label - } + name: t.label, + }; historyService.updateSavedConsole(p).then(() => { getConsoleList(); dispatch({ @@ -443,48 +474,55 @@ const WorkspaceRight = memo(function (props) { dispatch({ type: 'workspace/setConsoleList', payload: res.data, - }) - } + }); + }, }); - }); } const tabsList = useMemo(() => { - return workspaceTabList.map(t => { + return workspaceTabList.map((t) => { const { uniqueData } = t; return { prefixIcon: workspaceTabConfig[t.type]?.icon, label: t.title, key: t.id, // 这里还缺一个参数 是否可编辑tab名称, 编辑表不可编辑名称 TODO: - children: <> - { - [WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, WorkspaceTabType.PROCEDURE, WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW].includes(t.type) && - } - { - t.type === WorkspaceTabType.EditTable && - } - + children: ( + <> + {[ + WorkspaceTabType.CONSOLE, + WorkspaceTabType.FUNCTION, + WorkspaceTabType.PROCEDURE, + WorkspaceTabType.TRIGGER, + WorkspaceTabType.VIEW, + ].includes(t.type) && ( + + )} + {t.type === WorkspaceTabType.EditTable && ( + + )} + + ), }; - }) - }, [workspaceTabList, activeConsoleId, curWorkspaceParams]) + }); + }, [workspaceTabList, activeConsoleId, curWorkspaceParams]); return (
    @@ -501,7 +539,7 @@ const WorkspaceRight = memo(function (props) { />
    -
    +
    ); }); diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 90f240275..0b3fe6127 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -199,8 +199,8 @@ export interface IModifyTableSqlParams { databaseName: string; schemaName?: string; tableName?: string; - oldTable?: string; - newTable: string; + oldTable?: IEditTableInfo; + newTable: IEditTableInfo; refresh: boolean; } diff --git a/chat2db-client/src/styles/global.less b/chat2db-client/src/styles/global.less index f748ab607..af5aa84e6 100644 --- a/chat2db-client/src/styles/global.less +++ b/chat2db-client/src/styles/global.less @@ -13,8 +13,8 @@ body { // 修改账号密码自动回填后input背景变色问题 input:-webkit-autofill { -webkit-text-fill-color: var(--color-text) !important; // 填充状态下的字体颜色 - transition: background-color 0s 9999999999s; - caret-color: var(--color-text); // 光标的颜色 + transition: background-color 0s 9999999999s !important; + caret-color: var(--color-text) !important; // 光标的颜色 } // ::-webkit-scrollbar { diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index 8583d53dc..f78f663c9 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -1,4 +1,4 @@ -import { IndexesType } from '@/constants'; +import { IndexesType, EditColumnOperationType } from '@/constants'; // 编辑表时表的基础数据 export interface IBaseInfo { @@ -6,24 +6,37 @@ export interface IBaseInfo { comment?: string; } -// 编辑表时列的数据结构 -export interface IColumnItem { - key?: string; // 列的key 前端自己给的 +export interface IColumnItemNew { + operationType?: EditColumnOperationType; // 操作类型 + + key?: string; + oldName: string | null; // 老的列名 name: string | null; // 列名 + + databaseName: string | null; // 数据库名 + schemaName: string | null; // 模式名 + tableName: string | null; // 表名 + columnType: string | null; // 列的类型 比如 varchar(100) ,double(10,6) - columnSize: number | null; // 列的长度 - nullable: number | null; // 是否为空 - primaryKey: boolean | null; // 是否主键 + dataType: number | null; // 数据类型 defaultValue: string | null; // 默认值 - dataType: string | null; // 数据类型 - autoIncrement: boolean | null; // 是否自增 - numericPrecision: number | null; // 数字精度 - numericScale: number | null; // 数字比例 - characterMaximumLength: number | null; // 字符串最大长度 + autoIncrement: string | null; // 是否自增 comment: string | null; // 注释 + primaryKey: string | null; // 是否主键 + typeName: string | null; // 类型名 + columnSize: number | null; // 列的长度 + bufferLength: number | null; // 缓冲区长度 + decimalDigits: string | null; // 小数位数 + numPrecRadix: number| null; // 数字精度 + sqlDataType: string| null; // sql数据类型 + sqlDatetimeSub: string| null; // sql日期时间子类型 + charOctetLength:string| null; // 字符串最大长度 + ordinalPosition: number| null; // 位置 + nullable: 0 | 1 | null; //是否为空 + generatedColumn: string | null; // 是否生成列 } -export interface IIndexIncludeColumnItem { +export interface IIndexIncludeColumnItem { key?: string; // 列的key 前端自己给的 ascOrDesc: string | null; // 升序还是降序 cardinality: number | null; // 基数 @@ -40,7 +53,7 @@ export interface IIndexIncludeColumnItem { tableName: string | null; // 表名 type: string | null; // 类型 pages: number | null; // 页数 - prefixLength: number | null; // + prefixLength: number | null; // 前缀长度 } // 编辑表时索引的数据结构 @@ -54,7 +67,7 @@ export interface IIndexItem { } // 编辑表时整体的数据结构 -export interface IEditTableInfo extends IBaseInfo { - columnList: IColumnItem[]; +export interface IEditTableInfo extends IBaseInfo { + columnList: IColumnItemNew[]; indexList: IIndexItem[]; } diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 6e40eeb72..221876e00 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.npmmirror.com/7zip-bin/-/7zip-bin-5.1.1.tgz#9274ec7460652f9c632c59addf24efb1684ef876" integrity sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ== +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.npmmirror.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@ahooksjs/use-request@^2.0.0": version "2.8.15" resolved "https://registry.npmmirror.com/@ahooksjs/use-request/-/use-request-2.8.15.tgz#daa32a8395ba75e8deb9f4fde4e221a4a8f525db" @@ -1734,13 +1739,38 @@ resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz#8cfaf2ff603e9aabb910e9c0558c26cf32744061" integrity sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA== -"@eslint-community/eslint-utils@^4.2.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": + version "4.8.1" + resolved "https://registry.npmmirror.com/@eslint-community/regexpp/-/regexpp-4.8.1.tgz#8c4bb756cc2aa7eaf13cfa5e69c83afb3260c20c" + integrity sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ== + +"@eslint/eslintrc@^2.1.2": + version "2.1.2" + resolved "https://registry.npmmirror.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396" + integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@eslint/js@8.49.0": + version "8.49.0" + resolved "https://registry.npmmirror.com/@eslint/js/-/js-8.49.0.tgz#86f79756004a97fa4df866835093f1df3d03c333" + integrity sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w== + "@floating-ui/core@^0.6.2": version "0.6.2" resolved "https://registry.npmmirror.com/@floating-ui/core/-/core-0.6.2.tgz#f2813f0e5f3d5ed7af5029e1a082203dadf02b7d" @@ -1804,6 +1834,25 @@ resolved "https://registry.npmmirror.com/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz#2dc8c57044de0340eb53a7ba602e59abf80dc799" integrity sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ== +"@humanwhocodes/config-array@^0.11.11": + version "0.11.11" + resolved "https://registry.npmmirror.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" + integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.npmmirror.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + "@iconify/types@^2.0.0": version "2.0.0" resolved "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57" @@ -1989,7 +2038,7 @@ resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -2360,11 +2409,21 @@ resolved "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-2.2.7.tgz#226a9e31680835a6188e887f3988e60c04d3f6a3" integrity sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA== +"@types/json-schema@^7.0.12": + version "7.0.13" + resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85" + integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ== + "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.12" resolved "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb" integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + "@types/lodash@^4.14.195": version "4.14.195" resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" @@ -2429,6 +2488,11 @@ resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a" integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw== +"@types/semver@^7.5.0": + version "7.5.2" + resolved "https://registry.npmmirror.com/@types/semver/-/semver-7.5.2.tgz#31f6eec1ed7ec23f4f05608d3a2d381df041f564" + integrity sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw== + "@types/stylis@^4.0.2": version "4.2.0" resolved "https://registry.npmmirror.com/@types/stylis/-/stylis-4.2.0.tgz#199a3f473f0c3a6f6e4e1b17cdbc967f274bdc6b" @@ -2483,6 +2547,23 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/eslint-plugin@^6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.2.tgz#f18cc75c9cceac8080a9dc2e7d166008c5207b9f" + integrity sha512-ooaHxlmSgZTM6CHYAFRlifqh1OAr3PAQEwi7lhYhaegbnXrnh7CDcHmc3+ihhbQC7H0i4JF0psI5ehzkF6Yl6Q== + dependencies: + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/type-utils" "6.7.2" + "@typescript-eslint/utils" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/parser@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-5.48.1.tgz#d0125792dab7e232035434ab8ef0658154db2f10" @@ -2493,6 +2574,17 @@ "@typescript-eslint/typescript-estree" "5.48.1" debug "^4.3.4" +"@typescript-eslint/parser@^6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/parser/-/parser-6.7.2.tgz#e0ae93771441b9518e67d0660c79e3a105497af4" + integrity sha512-KA3E4ox0ws+SPyxQf9iSI25R6b4Ne78ORhNHeVKrPQnoYsb9UhieoiRoJgrzgEeKGOXhcY1i8YtOeCHHTDa6Fw== + dependencies: + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + "@typescript-eslint/scope-manager@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-5.48.1.tgz#39c71e4de639f5fe08b988005beaaf6d79f9d64d" @@ -2509,6 +2601,14 @@ "@typescript-eslint/types" "5.62.0" "@typescript-eslint/visitor-keys" "5.62.0" +"@typescript-eslint/scope-manager@6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.2.tgz#cf59a2095d2f894770c94be489648ad1c78dc689" + integrity sha512-bgi6plgyZjEqapr7u2mhxGR6E8WCzKNUFWNh6fkpVe9+yzRZeYtDTbsIBzKbcxI+r1qVWt6VIoMSNZ4r2A+6Yw== + dependencies: + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + "@typescript-eslint/type-utils@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-5.48.1.tgz#5d94ac0c269a81a91ad77c03407cea2caf481412" @@ -2519,6 +2619,16 @@ debug "^4.3.4" tsutils "^3.21.0" +"@typescript-eslint/type-utils@6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/type-utils/-/type-utils-6.7.2.tgz#ed921c9db87d72fa2939fee242d700561454f367" + integrity sha512-36F4fOYIROYRl0qj95dYKx6kybddLtsbmPIYNK0OBeXv2j9L5nZ17j9jmfy+bIDHKQgn2EZX+cofsqi8NPATBQ== + dependencies: + "@typescript-eslint/typescript-estree" "6.7.2" + "@typescript-eslint/utils" "6.7.2" + debug "^4.3.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/types@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.48.1.tgz#efd1913a9aaf67caf8a6e6779fd53e14e8587e14" @@ -2529,6 +2639,11 @@ resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +"@typescript-eslint/types@6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/types/-/types-6.7.2.tgz#75a615a6dbeca09cafd102fe7f465da1d8a3c066" + integrity sha512-flJYwMYgnUNDAN9/GAI3l8+wTmvTYdv64fcH8aoJK76Y+1FCZ08RtI5zDerM/FYT5DMkAc+19E4aLmd5KqdFyg== + "@typescript-eslint/typescript-estree@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.48.1.tgz#9efa8ee2aa471c6ab62e649f6e64d8d121bc2056" @@ -2555,6 +2670,19 @@ semver "^7.3.7" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.2.tgz#ce5883c23b581a5caf878af641e49dd0349238c7" + integrity sha512-kiJKVMLkoSciGyFU0TOY0fRxnp9qq1AzVOHNeN1+B9erKFCJ4Z8WdjAkKQPP+b1pWStGFqezMLltxO+308dJTQ== + dependencies: + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/visitor-keys" "6.7.2" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + "@typescript-eslint/utils@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.48.1.tgz#20f2f4e88e9e2a0961cbebcb47a1f0f7da7ba7f9" @@ -2569,6 +2697,19 @@ eslint-utils "^3.0.0" semver "^7.3.7" +"@typescript-eslint/utils@6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-6.7.2.tgz#b9ef0da6f04932167a9222cb4ac59cb187165ebf" + integrity sha512-ZCcBJug/TS6fXRTsoTkgnsvyWSiXwMNiPzBUani7hDidBdj1779qwM1FIAmpH4lvlOZNF3EScsxxuGifjpLSWQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.7.2" + "@typescript-eslint/types" "6.7.2" + "@typescript-eslint/typescript-estree" "6.7.2" + semver "^7.5.4" + "@typescript-eslint/utils@^5.10.0": version "5.62.0" resolved "https://registry.npmmirror.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" @@ -2599,6 +2740,14 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" +"@typescript-eslint/visitor-keys@6.7.2": + version "6.7.2" + resolved "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.2.tgz#4cb2bd786f1f459731b0ad1584c9f73e1c7a4d5c" + integrity sha512-uVw9VIMFBUTz8rIeaUT3fFe8xIUx8r4ywAdlQv1ifH+6acn/XF8Y6rwJ7XNmkNMDrTW+7+vxFFPIF40nJCVsMQ== + dependencies: + "@typescript-eslint/types" "6.7.2" + eslint-visitor-keys "^3.4.1" + "@umijs/ast@4.0.72": version "4.0.72" resolved "https://registry.npmmirror.com/@umijs/ast/-/ast-4.0.72.tgz#54cf0d5edc5a09b06a2dff51d978cb3dc6bc6e11" @@ -2964,7 +3113,12 @@ resolved "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.9.tgz#b6ef7457e826be8049667ae673eda7876eb049be" integrity sha512-4VSbbcMoxc4KLjb1gs96SRmi7w4h1SF+fCoiK0XaQX62buCc1G5d0DC5bJ9xJBNPDSVCmIrcl8BiYxzjrqaaJA== -acorn@^8.8.2: +acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn@^8.8.2, acorn@^8.9.0: version "8.10.0" resolved "https://registry.npmmirror.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -3009,7 +3163,7 @@ ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.5: +ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.npmmirror.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -3217,6 +3371,27 @@ array-union@^2.1.0: resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +array.prototype.find@^2.2.1: + version "2.2.2" + resolved "https://registry.npmmirror.com/array.prototype.find/-/array.prototype.find-2.2.2.tgz#e862cf891e725d8f2a10e5e42d750629faaabd32" + integrity sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + +array.prototype.findlastindex@^1.2.2: + version "1.2.3" + resolved "https://registry.npmmirror.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" + integrity sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + es-shim-unscopables "^1.0.0" + get-intrinsic "^1.2.1" + array.prototype.flat@^1.3.1: version "1.3.1" resolved "https://registry.npmmirror.com/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz#ffc6576a7ca3efc2f46a143b9d1dda9b4b3cf5e2" @@ -3260,6 +3435,19 @@ arraybuffer.prototype.slice@^1.0.1: is-array-buffer "^3.0.2" is-shared-array-buffer "^1.0.2" +arraybuffer.prototype.slice@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" + integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== + dependencies: + array-buffer-byte-length "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + is-array-buffer "^3.0.2" + is-shared-array-buffer "^1.0.2" + asar@^3.1.0: version "3.2.0" resolved "https://registry.npmmirror.com/asar/-/asar-3.2.0.tgz#e6edb5edd6f627ebef04db62f771c61bea9c1221" @@ -3315,6 +3503,13 @@ async@^3.2.3: resolved "https://registry.npmmirror.com/async/-/async-3.2.4.tgz#2d22e00f8cddeb5fde5dd33522b56d1cf569a81c" integrity sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ== +asynciterator.prototype@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz#8c5df0514936cdd133604dfcc9d3fb93f09b2b62" + integrity sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg== + dependencies: + has-symbols "^1.0.3" + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -3984,6 +4179,11 @@ concurrently@^8.1.0: tree-kill "^1.2.2" yargs "^17.7.2" +confusing-browser-globals@^1.0.10: + version "1.0.11" + resolved "https://registry.npmmirror.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" + integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== + console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.npmmirror.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -4116,7 +4316,7 @@ cross-env@^7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^7.0.1, cross-spawn@^7.0.3: +cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -4265,7 +4465,7 @@ dayjs@^1.11.1, dayjs@^1.11.7, dayjs@^1.11.9, dayjs@^1.9.1: resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== -debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.4: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmmirror.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -4279,7 +4479,7 @@ debug@^2.6.8: dependencies: ms "2.0.0" -debug@^3.2.6: +debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4291,6 +4491,11 @@ decode-uri-component@^0.2.0: resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +deep-is@^0.1.3: + version "0.1.4" + resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + deepmerge@^4.2.2: version "4.3.1" resolved "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" @@ -4314,6 +4519,15 @@ default-browser@^4.0.0: execa "^7.1.1" titleize "^3.0.0" +define-data-property@^1.0.1: + version "1.1.0" + resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" + integrity sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g== + dependencies: + get-intrinsic "^1.2.1" + gopd "^1.0.1" + has-property-descriptors "^1.0.0" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -4332,6 +4546,15 @@ define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +define-properties@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" + integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== + dependencies: + define-data-property "^1.0.1" + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -4441,6 +4664,13 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dom-align@^1.7.0: version "1.12.4" resolved "https://registry.npmmirror.com/dom-align/-/dom-align-1.12.4.tgz#3503992eb2a7cfcb2ed3b2a6d21e0b9c00d54511" @@ -4705,6 +4935,15 @@ enhanced-resolve@5.9.3: graceful-fs "^4.2.4" tapable "^2.2.0" +enhanced-resolve@^0.9.1: + version "0.9.1" + resolved "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-0.9.1.tgz#4d6e689b3725f86090927ccc86cd9f1635b89e2e" + integrity sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.2.0" + tapable "^0.1.8" + entities@^2.0.0: version "2.2.0" resolved "https://registry.npmmirror.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -4781,6 +5020,51 @@ es-abstract@^1.19.0, es-abstract@^1.20.4: unbox-primitive "^1.0.2" which-typed-array "^1.1.10" +es-abstract@^1.22.1: + version "1.22.2" + resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a" + integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA== + dependencies: + array-buffer-byte-length "^1.0.0" + arraybuffer.prototype.slice "^1.0.2" + available-typed-arrays "^1.0.5" + call-bind "^1.0.2" + es-set-tostringtag "^2.0.1" + es-to-primitive "^1.2.1" + function.prototype.name "^1.1.6" + get-intrinsic "^1.2.1" + get-symbol-description "^1.0.0" + globalthis "^1.0.3" + gopd "^1.0.1" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + is-array-buffer "^3.0.2" + is-callable "^1.2.7" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-typed-array "^1.1.12" + is-weakref "^1.0.2" + object-inspect "^1.12.3" + object-keys "^1.1.1" + object.assign "^4.1.4" + regexp.prototype.flags "^1.5.1" + safe-array-concat "^1.0.1" + safe-regex-test "^1.0.0" + string.prototype.trim "^1.2.8" + string.prototype.trimend "^1.0.7" + string.prototype.trimstart "^1.0.7" + typed-array-buffer "^1.0.0" + typed-array-byte-length "^1.0.0" + typed-array-byte-offset "^1.0.0" + typed-array-length "^1.0.4" + unbox-primitive "^1.0.2" + which-typed-array "^1.1.11" + es-get-iterator@^1.1.2: version "1.1.3" resolved "https://registry.npmmirror.com/es-get-iterator/-/es-get-iterator-1.1.3.tgz#3ef87523c5d464d41084b2c3c9c214f1199763d6" @@ -4796,6 +5080,26 @@ es-get-iterator@^1.1.2: isarray "^2.0.5" stop-iteration-iterator "^1.0.0" +es-iterator-helpers@^1.0.12: + version "1.0.15" + resolved "https://registry.npmmirror.com/es-iterator-helpers/-/es-iterator-helpers-1.0.15.tgz#bd81d275ac766431d19305923707c3efd9f1ae40" + integrity sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g== + dependencies: + asynciterator.prototype "^1.0.0" + call-bind "^1.0.2" + define-properties "^1.2.1" + es-abstract "^1.22.1" + es-set-tostringtag "^2.0.1" + function-bind "^1.1.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + has-property-descriptors "^1.0.0" + has-proto "^1.0.1" + has-symbols "^1.0.3" + internal-slot "^1.0.5" + iterator.prototype "^1.1.2" + safe-array-concat "^1.0.1" + es-set-tostringtag@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8" @@ -4890,6 +5194,89 @@ escape-string-regexp@^1.0.5: resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +eslint-config-airbnb-base@^15.0.0: + version "15.0.0" + resolved "https://registry.npmmirror.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== + dependencies: + confusing-browser-globals "^1.0.10" + object.assign "^4.1.2" + object.entries "^1.1.5" + semver "^6.3.0" + +eslint-config-prettier@^9.0.0: + version "9.0.0" + resolved "https://registry.npmmirror.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f" + integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw== + +eslint-import-resolver-node@^0.3.7: + version "0.3.9" + resolved "https://registry.npmmirror.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-import-resolver-webpack@^0.13.7: + version "0.13.7" + resolved "https://registry.npmmirror.com/eslint-import-resolver-webpack/-/eslint-import-resolver-webpack-0.13.7.tgz#49cd0108767b1f8ff81123c7e1ae362305aad47b" + integrity sha512-2a+meyMeABBRO4K53Oj1ygkmt5lhQS79Lmx2f684Qnv6gjvD4RLOM5jfPGTXwQ0A2K03WSoKt3HRQu/uBgxF7w== + dependencies: + array.prototype.find "^2.2.1" + debug "^3.2.7" + enhanced-resolve "^0.9.1" + find-root "^1.1.0" + has "^1.0.3" + interpret "^1.4.0" + is-core-module "^2.13.0" + is-regex "^1.1.4" + lodash "^4.17.21" + resolve "^2.0.0-next.4" + semver "^5.7.2" + +eslint-module-utils@^2.8.0: + version "2.8.0" + resolved "https://registry.npmmirror.com/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz#e439fee65fc33f6bba630ff621efc38ec0375c49" + integrity sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw== + dependencies: + debug "^3.2.7" + +eslint-plugin-babel@^5.3.1: + version "5.3.1" + resolved "https://registry.npmmirror.com/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz#75a2413ffbf17e7be57458301c60291f2cfbf560" + integrity sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g== + dependencies: + eslint-rule-composer "^0.3.0" + +eslint-plugin-import@^2.28.1: + version "2.28.1" + resolved "https://registry.npmmirror.com/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz#63b8b5b3c409bfc75ebaf8fb206b07ab435482c4" + integrity sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A== + dependencies: + array-includes "^3.1.6" + array.prototype.findlastindex "^1.2.2" + array.prototype.flat "^1.3.1" + array.prototype.flatmap "^1.3.1" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.7" + eslint-module-utils "^2.8.0" + has "^1.0.3" + is-core-module "^2.13.0" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.6" + object.groupby "^1.0.0" + object.values "^1.1.6" + semver "^6.3.1" + tsconfig-paths "^3.14.2" + eslint-plugin-jest@27.2.1: version "27.2.1" resolved "https://registry.npmmirror.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.1.tgz#b85b4adf41c682ea29f1f01c8b11ccc39b5c672c" @@ -4897,7 +5284,15 @@ eslint-plugin-jest@27.2.1: dependencies: "@typescript-eslint/utils" "^5.10.0" -eslint-plugin-react-hooks@4.6.0: +eslint-plugin-prettier@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.0.0.tgz#6887780ed95f7708340ec79acfdf60c35b9be57a" + integrity sha512-AgaZCVuYDXHUGxj/ZGu1u8H8CYgDY3iG6w5kUFw4AzMVXzB7VvbKgYR4nATIN+OvUrghMbiDLeimVjVY5ilq3w== + dependencies: + prettier-linter-helpers "^1.0.0" + synckit "^0.8.5" + +eslint-plugin-react-hooks@4.6.0, eslint-plugin-react-hooks@^4.6.0: version "4.6.0" resolved "https://registry.npmmirror.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== @@ -4923,6 +5318,33 @@ eslint-plugin-react@7.32.2: semver "^6.3.0" string.prototype.matchall "^4.0.8" +eslint-plugin-react@^7.33.2: + version "7.33.2" + resolved "https://registry.npmmirror.com/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz#69ee09443ffc583927eafe86ffebb470ee737608" + integrity sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw== + dependencies: + array-includes "^3.1.6" + array.prototype.flatmap "^1.3.1" + array.prototype.tosorted "^1.1.1" + doctrine "^2.1.0" + es-iterator-helpers "^1.0.12" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.6" + object.fromentries "^2.0.6" + object.hasown "^1.1.2" + object.values "^1.1.6" + prop-types "^15.8.1" + resolve "^2.0.0-next.4" + semver "^6.3.1" + string.prototype.matchall "^4.0.8" + +eslint-rule-composer@^0.3.0: + version "0.3.0" + resolved "https://registry.npmmirror.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" + integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== + eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -4931,6 +5353,14 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.npmmirror.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + eslint-utils@^3.0.0: version "3.0.0" resolved "https://registry.npmmirror.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" @@ -4948,11 +5378,75 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994" integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA== +eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: + version "3.4.3" + resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" + integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== + +eslint@^8.49.0: + version "8.49.0" + resolved "https://registry.npmmirror.com/eslint/-/eslint-8.49.0.tgz#09d80a89bdb4edee2efcf6964623af1054bf6d42" + integrity sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.2" + "@eslint/js" "8.49.0" + "@humanwhocodes/config-array" "^0.11.11" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + ajv "^6.12.4" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" + ignore "^5.2.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + is-path-inside "^3.0.3" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.3" + strip-ansi "^6.0.1" + text-table "^0.2.0" + +espree@^9.6.0, espree@^9.6.1: + version "9.6.1" + resolved "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" + integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== + dependencies: + acorn "^8.9.0" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.4.1" + esprima@^4.0.0: version "4.0.1" resolved "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.npmmirror.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.npmmirror.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -4965,7 +5459,7 @@ estraverse@^4.1.1: resolved "https://registry.npmmirror.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== -estraverse@^5.2.0, estraverse@^5.3.0: +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: version "5.3.0" resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== @@ -5048,6 +5542,11 @@ fast-deep-equal@3.1.3, fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.1.2: + version "1.3.0" + resolved "https://registry.npmmirror.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + fast-glob@3.2.12: version "3.2.12" resolved "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" @@ -5086,6 +5585,11 @@ fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: resolved "https://registry.npmmirror.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.npmmirror.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + fast-redact@^3.0.0: version "3.2.0" resolved "https://registry.npmmirror.com/fast-redact/-/fast-redact-3.2.0.tgz#b1e2d39bc731376d28bde844454fa23e26919987" @@ -5113,6 +5617,13 @@ fetch-blob@^3.1.2, fetch-blob@^3.1.4: node-domexception "^1.0.0" web-streams-polyfill "^3.0.3" +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + filelist@^1.0.4: version "1.0.4" resolved "https://registry.npmmirror.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" @@ -5132,6 +5643,11 @@ filter-obj@^1.1.0: resolved "https://registry.npmmirror.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^4.1.0: version "4.1.0" resolved "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -5148,6 +5664,20 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" +flat-cache@^3.0.4: + version "3.1.0" + resolved "https://registry.npmmirror.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f" + integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew== + dependencies: + flatted "^3.2.7" + keyv "^4.5.3" + rimraf "^3.0.2" + +flatted@^3.2.7: + version "3.2.9" + resolved "https://registry.npmmirror.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" + integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== + flatten@^1.0.2: version "1.0.3" resolved "https://registry.npmmirror.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" @@ -5265,6 +5795,16 @@ function.prototype.name@^1.1.5: es-abstract "^1.19.0" functions-have-names "^1.2.2" +function.prototype.name@^1.1.6: + version "1.1.6" + resolved "https://registry.npmmirror.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" + integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + functions-have-names "^1.2.3" + functions-have-names@^1.2.2, functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.npmmirror.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -5376,6 +5916,13 @@ globals@^11.1.0: resolved "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^13.19.0: + version "13.21.0" + resolved "https://registry.npmmirror.com/globals/-/globals-13.21.0.tgz#163aae12f34ef502f5153cfbdd3600f36c63c571" + integrity sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg== + dependencies: + type-fest "^0.20.2" + globalthis@^1.0.3: version "1.0.3" resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" @@ -5423,6 +5970,11 @@ graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, resolved "https://registry.npmmirror.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" integrity sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + handle-thing@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" @@ -5721,6 +6273,11 @@ internal-slot@^1.0.3, internal-slot@^1.0.4, internal-slot@^1.0.5: has "^1.0.3" side-channel "^1.0.4" +interpret@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + intersection-observer@^0.12.0: version "0.12.2" resolved "https://registry.npmmirror.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" @@ -5915,7 +6472,7 @@ is-generator-function@^1.0.10: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -5951,6 +6508,11 @@ is-number@^7.0.0: resolved "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^4.1.0: version "4.1.0" resolved "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" @@ -6012,7 +6574,7 @@ is-symbol@^1.0.2, is-symbol@^1.0.3, is-symbol@^1.0.4: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.npmmirror.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== @@ -6125,6 +6687,17 @@ istanbul-lib-instrument@^5.0.4: istanbul-lib-coverage "^3.2.0" semver "^6.3.0" +iterator.prototype@^1.1.2: + version "1.1.2" + resolved "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.2.tgz#5e29c8924f01916cb9335f1ff80619dcff22b0c0" + integrity sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w== + dependencies: + define-properties "^1.2.1" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + reflect.getprototypeof "^1.0.4" + set-function-name "^2.0.1" + jake@^10.8.5: version "10.8.7" resolved "https://registry.npmmirror.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" @@ -6231,6 +6804,11 @@ jsesc@~0.5.0: resolved "https://registry.npmmirror.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-even-better-errors@^2.3.0: version "2.3.1" resolved "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" @@ -6241,6 +6819,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + json2mq@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" @@ -6248,6 +6831,13 @@ json2mq@^0.2.0: dependencies: string-convert "^0.2.0" +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + json5@^2.1.2, json5@^2.2.0, json5@^2.2.2: version "2.2.3" resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -6282,6 +6872,13 @@ keyboardevents-areequal@^0.2.1: resolved "https://registry.npmmirror.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== +keyv@^4.5.3: + version "4.5.3" + resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" + integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug== + dependencies: + json-buffer "3.0.1" + kolorist@^1.6.0: version "1.8.0" resolved "https://registry.npmmirror.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c" @@ -6309,6 +6906,14 @@ less@4.1.3: needle "^3.1.0" source-map "~0.6.0" +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.npmmirror.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + lightningcss-darwin-arm64@1.19.0: version "1.19.0" resolved "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz#56ab071e932f845dbb7667f44f5b78441175a343" @@ -6497,6 +7102,11 @@ memfs@^3.4.1: dependencies: fs-monkey "^1.0.4" +memory-fs@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" + integrity sha512-+y4mDxU4rvXXu5UDSGCGNiesFmwCHuefGMoPCO1WYucNYj7DsLqrFaa2fXVI0H+NNiPTwwzKwspn9yTZqUGqng== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -6579,7 +7189,7 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -6593,7 +7203,7 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.0: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -6684,6 +7294,11 @@ natural-compare-lite@^1.4.0: resolved "https://registry.npmmirror.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.npmmirror.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + nearley@^2.20.1: version "2.20.1" resolved "https://registry.npmmirror.com/nearley/-/nearley-2.20.1.tgz#246cd33eff0d012faf197ff6774d7ac78acdd474" @@ -6843,7 +7458,7 @@ object-keys@^1.1.1: resolved "https://registry.npmmirror.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.0, object.assign@^4.1.4: +object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4: version "4.1.4" resolved "https://registry.npmmirror.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -6881,6 +7496,16 @@ object.getprototypeof@^1.0.3: es-abstract "^1.20.4" reflect.getprototypeof "^1.0.2" +object.groupby@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/object.groupby/-/object.groupby-1.0.1.tgz#d41d9f3c8d6c778d9cbac86b4ee9f5af103152ee" + integrity sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + object.hasown@^1.1.2: version "1.1.2" resolved "https://registry.npmmirror.com/object.hasown/-/object.hasown-1.1.2.tgz#f919e21fad4eb38a57bc6345b3afd496515c3f92" @@ -6953,6 +7578,18 @@ open@^9.1.0: is-inside-container "^1.0.0" is-wsl "^2.2.0" +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.npmmirror.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== + dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" @@ -7502,6 +8139,18 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.7: picocolors "^1.0.0" source-map-js "^1.0.2" +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + prettier-plugin-organize-imports@^2: version "2.3.4" resolved "https://registry.npmmirror.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-2.3.4.tgz#65473861ae5ab7960439fff270a2258558fbe9ba" @@ -8249,6 +8898,18 @@ reflect.getprototypeof@^1.0.2: globalthis "^1.0.3" which-builtin-type "^1.1.3" +reflect.getprototypeof@^1.0.4: + version "1.0.4" + resolved "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz#aaccbf41aca3821b87bb71d9dcbc7ad0ba50a3f3" + integrity sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + get-intrinsic "^1.2.1" + globalthis "^1.0.3" + which-builtin-type "^1.1.3" + regenerate-unicode-properties@10.1.0, regenerate-unicode-properties@^10.1.0: version "10.1.0" resolved "https://registry.npmmirror.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c" @@ -8282,6 +8943,15 @@ regexp.prototype.flags@^1.4.3, regexp.prototype.flags@^1.5.0: define-properties "^1.2.0" functions-have-names "^1.2.3" +regexp.prototype.flags@^1.5.1: + version "1.5.1" + resolved "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" + integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + set-function-name "^2.0.0" + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.npmmirror.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -8370,6 +9040,15 @@ resolve@^1.14.2: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.22.4: + version "1.22.6" + resolved "https://registry.npmmirror.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362" + integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.4: version "2.0.0-next.4" resolved "https://registry.npmmirror.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" @@ -8389,7 +9068,7 @@ reusify@^1.0.4: resolved "https://registry.npmmirror.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.0: +rimraf@^3.0.0, rimraf@^3.0.2: version "3.0.2" resolved "https://registry.npmmirror.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -8459,6 +9138,16 @@ safe-array-concat@^1.0.0: has-symbols "^1.0.3" isarray "^2.0.5" +safe-array-concat@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" + integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.2.1" + has-symbols "^1.0.3" + isarray "^2.0.5" + safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmmirror.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -8533,7 +9222,7 @@ select-hose@^2.0.0: resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -semver@^5.6.0: +semver@^5.6.0, semver@^5.7.2: version "5.7.2" resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -8543,7 +9232,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7: +semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -8555,6 +9244,15 @@ semver@~7.0.0: resolved "https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +set-function-name@^2.0.0, set-function-name@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" + integrity sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA== + dependencies: + define-data-property "^1.0.1" + functions-have-names "^1.2.3" + has-property-descriptors "^1.0.0" + setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.npmmirror.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" @@ -8854,6 +9552,15 @@ string.prototype.trim@^1.2.7: define-properties "^1.1.4" es-abstract "^1.20.4" +string.prototype.trim@^1.2.8: + version "1.2.8" + resolved "https://registry.npmmirror.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" + integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + string.prototype.trimend@^1.0.6: version "1.0.6" resolved "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533" @@ -8863,6 +9570,15 @@ string.prototype.trimend@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" +string.prototype.trimend@^1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" + integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + string.prototype.trimstart@^1.0.6: version "1.0.6" resolved "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4" @@ -8872,6 +9588,15 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" +string.prototype.trimstart@^1.0.7: + version "1.0.7" + resolved "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" + integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.2.0" + es-abstract "^1.22.1" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -8893,6 +9618,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" @@ -8903,6 +9633,11 @@ strip-final-newline@^3.0.0: resolved "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== +strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + styled-components@6.0.0-rc.0: version "6.0.0-rc.0" resolved "https://registry.npmmirror.com/styled-components/-/styled-components-6.0.0-rc.0.tgz#c92f8f3c1d16edf780d84f51eeac67bd2a42754b" @@ -9053,7 +9788,7 @@ swr@^2.0.0: dependencies: use-sync-external-store "^1.2.0" -synckit@0.8.5: +synckit@0.8.5, synckit@^0.8.5: version "0.8.5" resolved "https://registry.npmmirror.com/synckit/-/synckit-0.8.5.tgz#b7f4358f9bb559437f9f167eb6bc46b3c9818fa3" integrity sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q== @@ -9089,6 +9824,11 @@ tailwindcss@^3: resolve "^1.22.2" sucrase "^3.32.0" +tapable@^0.1.8: + version "0.1.10" + resolved "https://registry.npmmirror.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4" + integrity sha512-jX8Et4hHg57mug1/079yitEKWGB3LCwoxByLsNim89LABq8NqgiX+6iYVOsq0vX8uJHkU+DZ5fnq95f800bEsQ== + tapable@^2.0.0, tapable@^2.2.0, tapable@^2.2.1: version "2.2.1" resolved "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" @@ -9133,6 +9873,11 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + thenify-all@^1.0.0: version "1.6.0" resolved "https://registry.npmmirror.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" @@ -9234,11 +9979,26 @@ truncate-utf8-bytes@^1.0.0: dependencies: utf8-byte-length "^1.0.1" +ts-api-utils@^1.0.1: + version "1.0.3" + resolved "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-1.0.3.tgz#f12c1c781d04427313dbac808f453f050e54a331" + integrity sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg== + ts-interface-checker@^0.1.9: version "0.1.13" resolved "https://registry.npmmirror.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699" integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA== +tsconfig-paths@^3.14.2: + version "3.14.2" + resolved "https://registry.npmmirror.com/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz#6e32f1f79412decd261f92d633a9dc1cfa99f088" + integrity sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@2.3.0: version "2.3.0" resolved "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e" @@ -9277,6 +10037,18 @@ tty-browserify@0.0.0: resolved "https://registry.npmmirror.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.npmmirror.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + type@^1.0.1: version "1.2.0" resolved "https://registry.npmmirror.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" From 0e9d8912ff8fd23b4b528b8702e145e2e692f26c Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Fri, 22 Sep 2023 14:15:16 +0800 Subject: [PATCH 0783/1069] update --- .../chat2db/plugin/mysql/MysqlMetaData.java | 47 ++++++++----- .../plugin/mysql/builder/MysqlSqlBuilder.java | 2 +- .../plugin/mysql/type/MysqlCharsetEnum.java | 67 ++++++++++++++++++ .../plugin/mysql/type/MysqlCollationEnum.java | 69 +++++++++++++++++++ .../mysql/type/MysqlColumnTypeEnum.java | 11 ++- .../plugin/mysql/type/MysqlIndexTypeEnum.java | 6 +- .../domain/api/service/TableService.java | 7 ++ .../domain/core/impl/TableServiceImpl.java | 6 ++ .../api/controller/rdb/RdbDocController.java | 4 +- .../api/controller/rdb/TableController.java | 27 ++++---- .../rdb/doc/DatabaseExportService.java | 14 ++-- .../main/java/ai/chat2db/spi/MetaData.java | 10 +++ .../chat2db/spi/jdbc/DefaultMetaService.java | 5 ++ .../java/ai/chat2db/spi/model/Charset.java | 13 ++++ .../java/ai/chat2db/spi/model/Collation.java | 11 +++ .../java/ai/chat2db/spi/model/ColumnType.java | 2 +- .../java/ai/chat2db/spi/model/TableMeta.java | 19 +++++ 17 files changed, 275 insertions(+), 45 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCharsetEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Charset.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Collation.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java 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 15f8acf01..2d6a878fe 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 @@ -2,25 +2,26 @@ import java.sql.Connection; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; 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.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; public class MysqlMetaData extends DefaultMetaService implements MetaData { @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, - @NotEmpty String tableName) { + @NotEmpty String tableName) { String sql = "SHOW CREATE TABLE " + format(databaseName) + "." - + format(tableName); + + format(tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { if (resultSet.next()) { return resultSet.getString("Create Table"); @@ -34,14 +35,14 @@ public static String format(String tableName) { } private static String ROUTINES_SQL - = - "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " - + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " - + "routine_name = '%s';"; + = + "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " + + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " + + "routine_name = '%s';"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, - String functionName) { + String functionName) { String sql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -60,11 +61,11 @@ public Function function(Connection connection, @NotEmpty String databaseName, S } private static String TRIGGER_SQL - = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " - + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; + = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " + + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; private static String TRIGGER_SQL_LIST - = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; + = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; @Override public List triggers(Connection connection, String databaseName, String schemaName) { @@ -84,7 +85,7 @@ public List triggers(Connection connection, String databaseName, String @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, - String triggerName) { + String triggerName) { String sql = String.format(TRIGGER_SQL, databaseName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -101,7 +102,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, - String procedureName) { + String procedureName) { String sql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); @@ -118,8 +119,8 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, } private static String VIEW_SQL - = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " - + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; + = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " + + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { @@ -135,8 +136,18 @@ public Table view(Connection connection, String databaseName, String schemaName, return table; }); } + @Override public SqlBuilder getSqlBuilder() { return new MysqlSqlBuilder(); } + + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return TableMeta.builder() + .columnTypes(MysqlColumnTypeEnum.getTypes()) + .charsets(MysqlCharsetEnum.getCharsets()) + .collations(MysqlCollationEnum.getCollations()) + .build(); + } } 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 88d616f8d..a3c83e692 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 @@ -25,7 +25,7 @@ public String buildCreateTableSql(Table table) { // append primary key and index for (TableIndex tableIndex : table.getIndexList()) { MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); - script.append("\t").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + script.append("\t").append("ADD ").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCharsetEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCharsetEnum.java new file mode 100644 index 000000000..8a30ef054 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCharsetEnum.java @@ -0,0 +1,67 @@ +package ai.chat2db.plugin.mysql.type; + +import ai.chat2db.spi.model.Charset; +import org.checkerframework.checker.units.qual.C; + +import java.util.Arrays; +import java.util.List; + +public enum MysqlCharsetEnum { + + UTF8("utf8", "utf8_general_ci"), + BIG5("big5", "big5_chinese_ci"), + DEC8("dec8", "dec8_swedish_ci"), + CP850("cp850", "cp850_general_ci"), + HP8("hp8", "hp8_english_ci"), + KOI8R("koi8r", "koi8r_general_ci"), + LATIN1("latin1", "latin1_swedish_ci"), + LATIN2("latin2", "latin2_general_ci"), + SWE7("swe7", "swe7_swedish_ci"), + ASCII("ascii", "ascii_general_ci"), + UJIS("ujis", "ujis_japanese_ci"), + SJIS("sjis", "sjis_japanese_ci"), + HEBREW("hebrew", "hebrew_general_ci"), + TIS620("tis620", "tis620_thai_ci"), + EUCKR("euckr", "euckr_korean_ci"), + KOI8U("koi8u", "koi8u_general_ci"), + GB2312("gb2312", "gb2312_chinese_ci"), + GREEK("greek", "greek_general_ci"), + CP1250("cp1250", "cp1250_general_ci"), + GBK("gbk", "gbk_chinese_ci"), + LATIN5("latin5", "latin5_turkish_ci"), + ARMSCII8("armscii8", "armscii8_general_ci"), + UCS2("ucs2", "ucs2_general_ci"), + CP866("cp866", "cp866_general_ci"), + KEYBCS2("keybcs2", "keybcs2_general_ci"), + MACCE("macce", "macce_general_ci"), + MACROMAN("macroman", "macroman_general_ci"), + CP852("cp852", "cp852_general_ci"), + LATIN7("latin7", "latin7_general_ci"), + UTF8MB4("utf8mb4", "utf8mb4_general_ci"), + CP1251("cp1251", "cp1251_general_ci"), + UTF16("utf16", "utf16_general_ci"), + UTF16LE("utf16le", "utf16le_general_ci"), + CP1256("cp1256", "cp1256_general_ci"), + CP1257("cp1257", "cp1257_general_ci"), + UTF32("utf32", "utf32_general_ci"), + BINARY("binary", "binary"), + GEOSTD8("geostd8", "geostd8_general_ci"), + CP932("cp932", "cp932_japanese_ci"), + EUCJPMS("eucjpms", "eucjpms_japanese_ci"), + GB18030("gb18030", "gb18030_chinese_ci"); + private Charset charset; + + MysqlCharsetEnum(String charsetName, String defaultCollationName) { + this.charset = new Charset(charsetName, defaultCollationName); + } + + + public Charset getCharset() { + return charset; + } + + public static List getCharsets() { + return Arrays.stream(MysqlCharsetEnum.values()).map(MysqlCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java new file mode 100644 index 000000000..1a03b1422 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java @@ -0,0 +1,69 @@ +package ai.chat2db.plugin.mysql.type; + +import ai.chat2db.spi.model.Collation; + +import java.util.Arrays; +import java.util.List; + +public enum MysqlCollationEnum { + + UTF8_GENERAL_CI("utf8_general_ci"), + + UTF8MB4_GENERAL_CI("utf8mb4_general_ci"), + + BIG5_CHINESE_CI("big5_chinese_ci"), + DEC8_SWEDISH_CI("dec8_swedish_ci"), + + HP8_ENGLISH_CI("hp8_english_ci"), + KOI8R_GENERAL_CI("koi8r_general_ci"), + LATIN1_SWEDISH_CI("latin1_swedish_ci"), + LATIN2_GENERAL_CI("latin2_general_ci"), + SWE7_SWEDISH_CI("swe7_swedish_ci"), + ASCII_GENERAL_CI("ascii_general_ci"), + UJIS_JAPANESE_CI("ujis_japanese_ci"), + SJIS_JAPANESE_CI("sjis_japanese_ci"), + HEBREW_GENERAL_CI("hebrew_general_ci"), + TIS620_THAI_CI("tis620_thai_ci"), + EUCKR_KOREAN_CI("euckr_korean_ci"), + KOI8U_GENERAL_CI("koi8u_general_ci"), + GB2312_CHINESE_CI("gb2312_chinese_ci"), + GREEK_GENERAL_CI("greek_general_ci"), + CP1250_GENERAL_CI("cp1250_general_ci"), + GBK_CHINESE_CI("gbk_chinese_ci"), + LATIN5_TURKISH_CI("latin5_turkish_ci"), + ARMSCII8_GENERAL_CI("armscii8_general_ci"), + CP1250_CZECH_CS("cp1250_czech_cs"), + UCS2_GENERAL_CI("ucs2_general_ci"), + CP866_GENERAL_CI("cp866_general_ci"), + KEYBCS2_GENERAL_CI("keybcs2_general_ci"), + MACCE_GENERAL_CI("macce_general_ci"), + MACROMAN_GENERAL_CI("macroman_general_ci"), + CP852_GENERAL_CI("cp852_general_ci"), + LATIN7_GENERAL_CI("latin7_general_ci"), + CP1251_GENERAL_CI("cp1251_general_ci"), + UTF16_GENERAL_CI("utf16_general_ci"), + UTF16LE_GENERAL_CI("utf16le_general_ci"), + CP1256_GENERAL_CI("cp1256_general_ci"), + CP1257_GENERAL_CI("cp1257_general_ci"), + UTF32_GENERAL_CI("utf32_general_ci"), + BINARY("binary"), + GEOSTD8_GENERAL_CI("geostd8_general_ci"), + CP932_JAPANESE_CI("cp932_japanese_ci"), + EUCJPMS_JAPANESE_CI("eucjpms_japanese_ci"), + GB18030_CHINESE_CI("gb18030_chinese_ci"), + ; + private Collation collation; + + MysqlCollationEnum(String collationName) { + this.collation = new Collation(collationName); + } + + public Collation getCollation() { + return collation; + } + + public static List getCollations() { + return Arrays.asList(MysqlCollationEnum.values()).stream().map(MysqlCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index cd74bb357..b153fd2ef 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils; import java.util.Arrays; +import java.util.List; import java.util.Map; public enum MysqlColumnTypeEnum implements ColumnBuilder { @@ -131,7 +132,7 @@ public ColumnType getColumnType() { static { for (MysqlColumnTypeEnum value : MysqlColumnTypeEnum.values()) { - COLUMN_TYPE_MAP.put(value.getColumnType().getDataTypeName(), value); + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); } } @@ -238,7 +239,7 @@ private String buildNullable(TableColumn column,MysqlColumnTypeEnum type) { } private String buildDataType(TableColumn column, MysqlColumnTypeEnum type) { - String columnType = type.columnType.getDataTypeName(); + String columnType = type.columnType.getTypeName(); if (Arrays.asList(BINARY, VARBINARY, VARCHAR, CHAR).contains(type)) { return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); } @@ -298,5 +299,11 @@ private String unsignedDataType(String dataTypeName, String middle) { return StringUtils.join(dataTypeName, middle); } + public static List getTypes(){ + return Arrays.stream(MysqlColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } + } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index 48be0ef45..530786e46 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -91,11 +91,11 @@ public String buildModifyIndex(TableIndex tableIndex) { if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { return buildDropIndex(tableIndex); } - if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); } - if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { - return StringUtils.join("MODIFY ", buildIndexScript(tableIndex)); + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join("ADD ", buildIndexScript(tableIndex)); } return ""; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java index 32218325d..62ecb5e0b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java @@ -98,4 +98,11 @@ public interface TableService { * @return */ List queryTypes(TypeQueryParam param); + + /** + * + * @param param + * @return + */ + TableMeta queryTableMeta(TypeQueryParam param); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 022ed13d1..5f9532956 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -159,4 +159,10 @@ public List queryTypes(TypeQueryParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); return metaSchema.types(Chat2DBContext.getConnection()); } + + @Override + public TableMeta queryTableMeta(TypeQueryParam param) { + MetaData metaSchema = Chat2DBContext.getMetaData(); + return metaSchema.getTableMeta(null, null, null); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java index 64f7acf94..552a6cf24 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java @@ -75,8 +75,8 @@ public void export(@Valid @RequestBody DataExportRequest request, HttpServletRes TableQueryParam param = rdbWebConverter.tableRequest2param(request); for (TableVO tableVO: tableVOS) { param.setTableName(tableVO.getName()); - tableVO.setColumnList(rdbWebConverter.columnDto2vo(tableService.queryColumns(param))); - tableVO.setIndexList(rdbWebConverter.indexDto2vo(tableService.queryIndexes(param))); + tableVO.setColumnList(tableService.queryColumns(param)); + tableVO.setIndexList(tableService.queryIndexes(param)); } Class targetClass = ExportServiceFactory.get(exportType.getCode()); Constructor constructor = targetClass.getDeclaredConstructor(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 3fcbc0985..ac985e8e0 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -1,7 +1,5 @@ package ai.chat2db.server.web.api.controller.rdb; -import java.util.List; - import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; @@ -18,18 +16,13 @@ import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.spi.model.Type; +import ai.chat2db.spi.model.*; import com.google.common.collect.Lists; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @ConnectionInfoAspect @RequestMapping("/api/rdb/table") @@ -66,7 +59,7 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } - + /** * 查询当前DB下的表columns @@ -167,7 +160,7 @@ public DataResult
    query(@Valid TableDetailQueryRequest request) { * @return */ @PostMapping("/modify/sql") - public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest request) { + public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest request) { return tableService.buildSql( rdbWebConverter.tableRequest2param(request.getOldTable()), rdbWebConverter.tableRequest2param(request.getNewTable())) @@ -188,6 +181,14 @@ public ListResult types(@Valid TypeQueryRequest request) { return ListResult.of(types); } + + @GetMapping("/table_meta") + public DataResult tableMeta(@Valid TypeQueryRequest request) { + TypeQueryParam typeQueryParam = TypeQueryParam.builder().dataSourceId(request.getDataSourceId()).build(); + TableMeta tableMeta = tableService.queryTableMeta(typeQueryParam); + return DataResult.of(tableMeta); + } + /** * 删除表 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java index 9967369c7..1f9345298 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java @@ -8,6 +8,9 @@ import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.server.web.api.util.StringUtils; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; @@ -82,13 +85,13 @@ public void dataAssemble(String databaseName, ExportOptions exportOptions, Table val t = new TableParameter(); t.setFieldName(item.getName() + "[" + StringUtils.isNull(item.getComment()) + "]"); List colForTable = new LinkedList<>(); - for (ColumnVO info : item.getColumnList()) { + for (TableColumn info : item.getColumnList()) { val p = new TableParameter(); p.setFieldName(info.getName()).setColumnDefault(info.getDefaultValue()) .setColumnComment(info.getComment()) .setColumnType(info.getColumnType()) - .setLength(String.valueOf(info.getCharacterMaximumLength())).setIsNullAble(String.valueOf(info.getNullable())) - .setDecimalPlaces(String.valueOf(info.getNumericPrecision())); + .setLength(String.valueOf(info.getColumnSize())).setIsNullAble(String.valueOf(info.getNullable())) + .setDecimalPlaces(String.valueOf(info.getDecimalDigits())); colForTable.add(p); } String key = databaseName + JOINER + t.getFieldName(); @@ -108,11 +111,12 @@ public void dataAssemble(String databaseName, ExportOptions exportOptions, Table } } - private List vo2Info(List indexList) { + private List vo2Info(List indexList) { return indexList.stream().map(v -> { IndexInfo info = new IndexInfo(); info.setName(v.getName()); - info.setColumnName(v.getColumns()); + List columnList = v.getColumnList(); + info.setColumnName(columnList.stream().map(TableIndexColumn::getColumnName).collect(Collectors.joining(","))); info.setIndexType(v.getType()); info.setComment(v.getComment()); return info; diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 6ac13f537..abba4689a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -187,4 +187,14 @@ List indexes(Connection connection, @NotEmpty String databaseName, S * @return */ SqlBuilder getSqlBuilder(); + + + /** + * + * @param databaseName + * @param schemaName + * @param tableName + * @return + */ + TableMeta getTableMeta( String databaseName, String schemaName, String tableName); } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 7d58cd26c..ca2ca8eba 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -99,4 +99,9 @@ public List types(Connection connection) { public SqlBuilder getSqlBuilder() { return new DefaultSqlBuilder(); } + + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return null; + } } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Charset.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Charset.java new file mode 100644 index 000000000..79cf5d29e --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Charset.java @@ -0,0 +1,13 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Charset { + + private String charsetName; + + private String defaultCollationName; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Collation.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Collation.java new file mode 100644 index 000000000..12e7de3af --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Collation.java @@ -0,0 +1,11 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class Collation { + + private String collationName; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java index 56b08f79f..eb4cb31a6 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java @@ -7,7 +7,7 @@ @AllArgsConstructor public class ColumnType { - private String dataTypeName; + private String typeName; private boolean supportLength; private boolean supportScale; private boolean supportNullable; 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 new file mode 100644 index 000000000..7531c7e78 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableMeta.java @@ -0,0 +1,19 @@ +package ai.chat2db.spi.model; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class TableMeta { + + private List columnTypes; + + + private List charsets; + + + private List collations; +} From fa319d8be58b764b18143474e5b3f95445ee158f Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Fri, 22 Sep 2023 16:44:13 +0800 Subject: [PATCH 0784/1069] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=8D=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E6=95=B0=E6=8D=AE=E5=BA=93=E7=B1=BB=E5=9E=8B=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=E7=9A=84=E5=AF=BC=E5=85=A5=E5=A4=B1=E8=B4=A5=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ncx/service/impl/ConverterServiceImpl.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index c3b882762..d82375518 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -111,19 +111,26 @@ public void insertDBConfig(List>> list) { for (Map> map : list) { for (Map.Entry> valueMap : map.entrySet()) { Map resultMap = valueMap.getValue(); + // mysql的版本还无法区分 + DataBaseType dataBaseType = DataBaseType.matchType(resultMap.get("ConnType")); + DataSourceDO dataSourceDO; + if (null == dataBaseType) { + //未匹配到数据库类型,如:navicat支持MongoDB等,但chat2DB暂不支持 + continue; + } else { + dataSourceDO = new DataSourceDO(); + dataSourceDO.setHost(resultMap.get("Host")); + dataSourceDO.setPort(resultMap.get("Port")); + dataSourceDO.setUrl(String.format(dataBaseType.getUrlString(), dataSourceDO.getHost(), dataSourceDO.getPort())); + } // 解密密码 String password = cipher.decryptString(resultMap.getOrDefault("Password", "")); - DataSourceDO dataSourceDO = new DataSourceDO(); LocalDateTime dateTime = LocalDateTime.now(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(resultMap.get("ConnectionName")); - dataSourceDO.setHost(resultMap.get("Host")); - dataSourceDO.setPort(resultMap.get("Port")); dataSourceDO.setUserName(resultMap.get("UserName")); dataSourceDO.setType(resultMap.get("ConnType")); - // mysql的版本还无法区分 - dataSourceDO.setUrl(String.format(Objects.requireNonNull(DataBaseType.matchType(dataSourceDO.getType())).getUrlString(), dataSourceDO.getHost(), dataSourceDO.getPort())); //password 为解密出来的密文,再使用chat2db的加密 DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); String encryptStr = desUtil.encrypt(password, "CBC"); From 9af6c20ebf4129b41886f8f437a5cbcc8de5ce81 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 22 Sep 2023 18:25:50 +0800 Subject: [PATCH 0785/1069] edit table --- chat2db-client/.eslintrc.js | 2 +- chat2db-client/.vscode/settings.json | 1 + .../DatabaseTableEditor/BaseInfo/index.tsx | 15 +- .../DatabaseTableEditor/ColumnList/index.less | 25 ++- .../DatabaseTableEditor/ColumnList/index.tsx | 199 ++++++++++++++---- .../DatabaseTableEditor/IncludeCol/index.tsx | 77 +++---- .../DatabaseTableEditor/IndexList/index.less | 19 +- .../DatabaseTableEditor/IndexList/index.tsx | 81 ++++--- .../src/blocks/DatabaseTableEditor/index.less | 1 - .../src/blocks/DatabaseTableEditor/index.tsx | 2 + .../src/components/CustomSelect/index.less | 4 + .../src/components/CustomSelect/index.tsx | 63 ++++++ chat2db-client/src/constants/editTable.ts | 6 +- chat2db-client/src/i18n/en-us/editTable.ts | 11 +- chat2db-client/src/i18n/zh-cn/editTable.ts | 4 + chat2db-client/src/pages/demo/index.tsx | 148 +++++++------ chat2db-client/src/service/sql.ts | 8 +- chat2db-client/src/typings/database.ts | 31 ++- chat2db-client/src/typings/editTable.ts | 24 +-- 19 files changed, 516 insertions(+), 205 deletions(-) create mode 100644 chat2db-client/src/components/CustomSelect/index.less create mode 100644 chat2db-client/src/components/CustomSelect/index.tsx diff --git a/chat2db-client/.eslintrc.js b/chat2db-client/.eslintrc.js index f6cbe181a..4e44d483e 100644 --- a/chat2db-client/.eslintrc.js +++ b/chat2db-client/.eslintrc.js @@ -56,7 +56,7 @@ module.exports = { // allow: ['info', 'error', 'warn'], // }, // ], - 'no-duplicate-imports': 2, // 禁止重复 import + 'no-duplicate-imports': [2], // 禁止重复 import 'newline-per-chained-call': 2, // 链式调用必须换行 'no-underscore-dangle': 2, // 禁止标识符中有悬空下划线 'eol-last': 2, // 文件以单一的换行符结束 diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index f4f7285bb..c6ee5882f 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -26,6 +26,7 @@ "AZUREAI", "bgcolor", "Cascader", + "charsets", "chatgpt", "CLICKHOUSE", "Consolas", diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index e5d634500..e85fcd48e 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -37,11 +37,20 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => return (
    -
    - + + - + diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less index c392610f6..8a45a9dc4 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less @@ -1,6 +1,6 @@ @import '../../../styles/var.less'; -.box { +.columnList { height: 100%; padding: 10px; box-sizing: border-box; @@ -10,16 +10,39 @@ .columnListHeader { margin: 0px -5px 10px; + flex-shrink: 0; button { margin: 0px 5px; } } +.formBox { + height: 0px; + flex: 1; + display: flex; + flex-direction: column; +} + .tableBox { flex: 1; overflow: auto; } +.otherInfo { + margin: 10px -10px 0px; + padding: 10px; + height: 200px; + display: flex; + align-items: center; + justify-content: center; + border-top: 1px solid var(--color-border); +} + +.otherInfoFormBox { + min-height: 140px; + width: 400px; +} + .editableCell { height: 28px; line-height: 26px; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 88cec6fef..b972c35c4 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,8 +1,7 @@ import React, { useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import { MenuOutlined } from '@ant-design/icons'; -import type { DragEndEvent } from '@dnd-kit/core'; -import { DndContext } from '@dnd-kit/core'; +import { DndContext, type DragEndEvent } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; @@ -10,9 +9,10 @@ import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } import { CSS } from '@dnd-kit/utilities'; import sqlService from '@/service/sql'; import { Context } from '../index'; -import { IColumnItemNew } from '@/typings'; +import { IColumnItemNew, IColumnTypes } from '@/typings'; import i18n from '@/i18n'; import { EditColumnOperationType } from '@/constants'; +import CustomSelect from '@/components/CustomSelect'; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; @@ -20,6 +20,23 @@ interface RowProps extends React.HTMLAttributes { interface IProps {} +interface IOption { + label: string; + value: string | number | null; +} + +// 编辑配置 +interface IEditingConfig extends IColumnTypes { + editKey: string; +} + +// 列字段类型,select组件的options需要的数据结构 +interface IColumnTypesOption extends IColumnTypes { + label: string; + value: string | number | null; +} + +// 本组件暴露给父组件的方法 export interface IColumnListRef { getColumnListInfo: () => IColumnItemNew[]; } @@ -52,6 +69,7 @@ const Row = ({ children, ...props }: RowProps) => { ); }; +// 创建一个空的数据结构 const createInitialData = () => { return { key: uuidv4(), @@ -78,6 +96,7 @@ const createInitialData = () => { ordinalPosition: null, nullable: null, generatedColumn: null, + editStatus: EditColumnOperationType.Add, }; }; @@ -85,22 +104,45 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const { dataSourceId, databaseName, schemaName, tableDetails } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(undefined); - const [databaseFieldTypeList, setDatabaseFieldTypeList] = useState([]); + const [editingKey, setEditingKey] = useState(null); + const [editingConfig, setEditingConfig] = useState(null); + const [databaseSupportField, setDatabaseSupportField] = useState<{ + columnTypes: IColumnTypesOption[]; + charsets: IOption[]; + collations: IOption[]; + }>({ + columnTypes: [], + charsets: [], + collations: [], + }); const isEditing = (record: IColumnItemNew) => record.key === editingKey; const edit = (record: IColumnItemNew) => { - form.setFieldsValue({ ...record }); - setEditingKey(record.key); + if (record.key) { + form.setFieldsValue({ ...record }); + setEditingKey(record.key); + + // 根据当前字段类型,设置编辑配置 + databaseSupportField.columnTypes.forEach((i) => { + if (i.typeName === record.columnType) { + setEditingConfig({ + ...i, + editKey: record.key!, + }); + } + }); + } }; + // 整理服务端返回的数据,构造为前端需要的数据结构 useEffect(() => { if (tableDetails) { const list = tableDetails?.columnList?.map((t) => { return { ...t, + oldName: t.name, key: uuidv4(), }; }) || []; @@ -116,7 +158,36 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) databaseName, }) .then((res) => { - setDatabaseFieldTypeList(res.map((i) => i.typeName)); + const columnTypes = + res?.columnTypes?.map((i) => { + return { + ...i, + value: i.typeName, + label: i.typeName, + }; + }) || []; + + const charsets = + res?.charsets?.map((i) => { + return { + value: i.charsetName, + label: i.charsetName, + }; + }) || []; + + const collations = + res?.collations?.map((i) => { + return { + value: i.collationName, + label: i.collationName, + }; + }) || []; + + setDatabaseSupportField({ + columnTypes, + charsets, + collations, + }); }); }, []); @@ -128,8 +199,8 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }, { title: 'O T', - dataIndex: 'operationType', - width: '120px', + dataIndex: 'editStatus', + width: '60px', align: 'center', render: (text: EditColumnOperationType) => { return text === EditColumnOperationType.Add ? ( @@ -156,23 +227,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ); }, }, - { - title: i18n('editTable.label.columnSize'), - dataIndex: 'columnSize', - width: '120px', - render: (text: string, record: IColumnItemNew) => { - const editable = isEditing(record); - return editable ? ( - - - - ) : ( -
    edit(record)}> - {text} -
    - ); - }, - }, { title: i18n('editTable.label.columnType'), dataIndex: 'columnType', @@ -183,7 +237,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef)
    {editable ? ( - ) : (
    edit(record)}> @@ -194,6 +248,23 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ); }, }, + { + title: i18n('editTable.label.columnSize'), + dataIndex: 'columnSize', + width: '120px', + render: (text: string, record: IColumnItemNew) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
    edit(record)}> + {text} +
    + ); + }, + }, { title: i18n('editTable.label.nullable'), dataIndex: 'nullable', @@ -246,22 +317,29 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) if (name === 'nullable') { value = value ? 1 : 0; } + const newData = dataSource.map((item) => { if (item.key === editingKey) { + // 判断当前数据是新增的数据还是编辑后的数据 + let editStatus = item.editStatus; + if (editStatus !== EditColumnOperationType.Add) { + editStatus = EditColumnOperationType.Modify; + } return { ...item, [name]: value, + editStatus, }; } return item; }); setDataSource(newData); + console.log(field); }; const addData = () => { const newData = { ...createInitialData(), - operationType: EditColumnOperationType.Add, }; setDataSource([...dataSource, newData]); edit(newData); @@ -271,9 +349,11 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) setDataSource( dataSource.map((i) => { if (i.key === editingKey) { + setEditingKey(null); + setEditingConfig(null); return { ...i, - operationType: EditColumnOperationType.Delete, + editStatus: EditColumnOperationType.Delete, }; } return i; @@ -320,18 +400,60 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) getColumnListInfo, })); + const renderOtherInfoForm = () => { + const labelCol = { + style: { width: 90 }, + }; + + return ( + <> + {editingConfig?.supportDefaultValue && ( + + + + )} + {editingConfig?.supportCharset && ( + + + + )} + {editingConfig?.supportCollation && ( + + + + )} + {editingConfig?.supportScale && ( + + + + )} + + ); + }; + return ( -
    +
    -
    -
    + +
    - i.key)} strategy={verticalListSortingStrategy}> + i.key!)} strategy={verticalListSortingStrategy}>
    ) pagination={false} rowKey="key" columns={columns as any} - dataSource={dataSource.filter((i) => i.operationType !== EditColumnOperationType.Delete)} + dataSource={dataSource.filter((i) => i.editStatus !== EditColumnOperationType.Delete)} /> - - + +
    +
    {renderOtherInfoForm()}
    +
    + ); }); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index f49f5ed67..304308991 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -1,7 +1,7 @@ import React, { useMemo, useState, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import classnames from 'classnames'; -import { Table, InputNumber, Form, Select, Button } from 'antd'; +import { Table, Form, Select, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; @@ -14,22 +14,29 @@ interface IProps { const createInitialData = () => { return { key: uuidv4(), - indexName: null, + oldName: null, + name: null, tableName: null, - type: null, - columnName: null, + columnType: null, + dataType: null, + defaultValue: null, + autoIncrement: null, comment: null, - ordinalPosition: null, - collation: null, + primaryKey: null, schemaName: null, - databaseName: '', - nonUnique: true, - indexQualifier: null, - ascOrDesc: null, - cardinality: null, - pages: null, - filterCondition: null, - prefixLength: null, + databaseName: null, + typeName: null, + columnSize: null, + bufferLength: null, + decimalDigits: null, + numPrecRadix: null, + nullableInt: null, + sqlDataType: null, + sqlDatetimeSub: null, + charOctetLength: null, + ordinalPosition: null, + nullable: null, + generatedColumn: null, }; }; @@ -65,7 +72,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) return columnListInfo || []; }, []); - const edit = (record: Partial & { key: React.Key }) => { + const edit = (record: IIndexIncludeColumnItem) => { form.setFieldsValue({ ...record }); setEditingKey(record.key); }; @@ -97,7 +104,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) { title: i18n('editTable.label.columnName'), dataIndex: 'columnName', - width: '45%', + // width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? ( @@ -111,23 +118,23 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) ); }, }, - { - title: i18n('editTable.label.prefixLength'), - dataIndex: 'prefixLength', - width: '45%', - render: (text: string, record: IIndexIncludeColumnItem) => { - const editable = isEditing(record); - return editable ? ( - - - - ) : ( -
    edit(record)}> - {text} -
    - ); - }, - }, + // { + // title: i18n('editTable.label.prefixLength'), + // dataIndex: 'prefixLength', + // width: '45%', + // render: (text: string, record: IIndexIncludeColumnItem) => { + // const editable = isEditing(record); + // return editable ? ( + // + // + // + // ) : ( + //
    edit(record)}> + // {text} + //
    + // ); + // }, + // }, ]; const onValuesChange = (changedValues: any, allValues: any) => { @@ -147,11 +154,9 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) const includeColInfo: IIndexIncludeColumnItem[] = []; dataSource.forEach((t) => { columnList.forEach((columnItem) => { - if (t.columnName === columnItem.name) { + if (t.name === columnItem.name) { includeColInfo.push({ - ...createInitialData(), ...columnItem, - columnName: t.columnName, }); } }); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less index fe75757b6..065e4b7c6 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less @@ -1,7 +1,11 @@ @import '../../../styles/var.less'; -.box { +.indexList { + height: 100%; padding: 10px; + box-sizing: border-box; + display: flex; + flex-direction: column; } .indexListHeader { @@ -10,6 +14,19 @@ margin: 0px 5px; } } + +.formBox { + flex: 1; + height: 0px; + display: flex; + flex-direction: column; +} + +.tableBox { + flex: 1; + overflow: auto; +} + .editableCell { height: 28px; line-height: 26px; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index e7fb003b7..797b581df 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -11,8 +11,7 @@ import React, { import styles from './index.less'; import classnames from 'classnames'; import { MenuOutlined } from '@ant-design/icons'; -import type { DragEndEvent } from '@dnd-kit/core'; -import { DndContext } from '@dnd-kit/core'; +import { type DragEndEvent, DndContext } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; @@ -20,7 +19,7 @@ import { Table, Input, Form, Select, Button, Modal } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import IncludeCol, { IIncludeColRef } from '../IncludeCol'; import { IIndexItem, IIndexIncludeColumnItem } from '@/typings'; -import { IndexesType } from '@/constants'; +import { IndexesType, EditColumnOperationType } from '@/constants'; import { Context } from '../index'; import i18n from '@/i18n'; @@ -42,6 +41,7 @@ const createInitialData = (): IIndexItem => { type: null, columns: null, comment: null, + editStatus: EditColumnOperationType.Add, }; }; @@ -53,21 +53,22 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = const { tableDetails } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(dataSource[0]?.key); + const [editingKey, setEditingKey] = useState(null); const [includeColModalOpen, setIncludeColModalOpen] = useState(false); const includeColRef = useRef(null); const isEditing = (record: IIndexItem) => record.key === editingKey; - const edit = (record: Partial & { key?: React.Key }) => { + const edit = (record: IIndexItem) => { form.setFieldsValue({ ...record }); - setEditingKey(record.key); + setEditingKey(record.key || null); }; useEffect(() => { const data = tableDetails.indexList?.map((i) => { return { ...i, + oldName: i.name, key: uuidv4(), }; }); @@ -75,19 +76,26 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }, [tableDetails]); const addData = () => { - const newData = { - key: uuidv4(), - columnList: [], - name: '', - type: null, - columns: null, - }; + const newData = createInitialData(); setDataSource([...dataSource, newData]); edit(newData); }; const deleteData = () => { setDataSource(dataSource.filter((i) => i.key !== editingKey)); + setDataSource( + dataSource.map((i) => { + if (i.key === editingKey) { + setEditingKey(null); + // setEditingConfig(null); + return { + ...i, + editStatus: EditColumnOperationType.Delete, + }; + } + return i; + }), + ); }; const handelFieldsChange = (field: any) => { @@ -99,9 +107,14 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = } const newData = dataSource.map((item) => { if (item.key === editingKey) { + let editStatus = item.editStatus; + if (editStatus !== EditColumnOperationType.Add) { + editStatus = EditColumnOperationType.Modify; + } return { ...item, [name]: value, + editStatus, }; } return item; @@ -162,11 +175,13 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = const columns = [ { key: 'sort', - width: '60px', + width: '40px', + align: 'center', }, { title: i18n('editTable.label.index'), width: '70px', + align: 'center', render: (text: string, record: IIndexItem) => { return dataSource.findIndex((i) => i.key === record.key) + 1; }, @@ -218,7 +233,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = const editable = isEditing(record); const text = columnList ?.map((t) => { - return `${t.columnName}`; + return `${t.name}`; }) .join(','); return editable ? ( @@ -265,29 +280,31 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }, [editingKey]); return ( -
    +
    {/* */}
    -
    - - i.key)} strategy={verticalListSortingStrategy}> -
    - - + +
    + + i.key!)} strategy={verticalListSortingStrategy}> +
    i.editStatus !== EditColumnOperationType.Delete)} + /> + + + { function submit() { if (baseInfoRef.current && columnListRef.current && indexListRef.current) { const newTable = { + ...oldTableDetails, ...baseInfoRef.current.getBaseInfo(), columnList: columnListRef.current.getColumnListInfo()!, indexList: indexListRef.current.getIndexListInfo()!, @@ -100,6 +101,7 @@ export default memo((props: IProps) => { params.oldTable = oldTableDetails; } console.log(newTable); + console.log(params.oldTable); sqlService.getModifyTableSql(params).then((res) => { console.log(res); }); diff --git a/chat2db-client/src/components/CustomSelect/index.less b/chat2db-client/src/components/CustomSelect/index.less new file mode 100644 index 000000000..2e57988d1 --- /dev/null +++ b/chat2db-client/src/components/CustomSelect/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/components/CustomSelect/index.tsx b/chat2db-client/src/components/CustomSelect/index.tsx new file mode 100644 index 000000000..5e8e2dba1 --- /dev/null +++ b/chat2db-client/src/components/CustomSelect/index.tsx @@ -0,0 +1,63 @@ +import React, { memo, useEffect, useState } from 'react'; +import { Select } from 'antd'; + +interface IOption { + label: string; + value: string | number | null; +} + +interface IProps { + className?: string; + options: IOption[]; + onChange?: any; + value?: any; +} + +const CustomSelect = memo((props: IProps) => { + const { options, onChange, value } = props; + const [customOptions, setCustomOptions] = useState([]); + const [customValue, setCustomValue] = useState(); + + useEffect(() => { + setCustomOptions([...options, { label: '', value: null }]); + }, [options]); + + useEffect(() => { + setCustomValue(value); + }, [value]); + + // 1. 如果自定义的节点为null就过滤掉自定义节点 + // 2. 如果自定义节点的值和前面的节点的值相同就过滤掉自定义节点 + const filtrationCustomOptions = (list: IOption[]) => { + const newList = [...list]; + const lastItem = newList[newList.length - 1]; + newList.forEach((item, index) => { + if ((lastItem.value === item.value && index !== list.length - 1) || !item.value) { + newList.pop(); + } + }); + return newList; + }; + + const onSearch = (v: string) => { + customOptions[customOptions.length - 1].label = v; + customOptions[customOptions.length - 1].value = v; + setCustomOptions([...customOptions]); + }; + const customChange = (v: string) => { + setCustomValue(v); + onChange?.(v); + }; + return ( + + + + ); +}; + +const App: React.FC = () => { + const onFinish = (values: any) => { + console.log('Received values from form: ', values); + }; + + const checkPrice = (_: any, value: { number: number }) => { + if (value.number > 0) { + return Promise.resolve(); + } + return Promise.reject(new Error('Price must be greater than zero!')); + }; + + return ( +
    { + console.log(a); + }} + initialValues={{ + price: { + number: 0, + currency: 'rmb', + }, + }} + > + + + + + + + + ); +}; + +export default App; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 0b3fe6127..304670d4e 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,5 +1,5 @@ import createRequest from './base'; -import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseFieldType, IEditTableInfo } from '@/typings'; +import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseSupportField, IEditTableInfo } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; @@ -183,9 +183,9 @@ const sqlFormat = createRequest<{ const getDatabaseFieldTypeList = createRequest<{ dataSourceId: number; databaseName: string; -}, IDatabaseFieldType[]>('/api/rdb/table/type_list', { method: 'get' }); +}, IDatabaseSupportField>('/api/rdb/table/table_meta', { method: 'get' }); -/** 数据库支持的数据类型 */ +/** 获取表的详情 */ const getTableDetails = createRequest<{ dataSourceId: number; databaseName: string; @@ -204,7 +204,7 @@ export interface IModifyTableSqlParams { refresh: boolean; } -/** 数据库支持的数据类型 */ +/** 获取修改表的sql */ const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); export default { diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index e8f0b15d0..7b9a0e222 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -35,7 +35,34 @@ export interface IResultConfig { hasNextPage: boolean; } -/** 不同数据库支持的列字段类型*/ -export interface IDatabaseFieldType { +/** 不同数据库支持的列字段类型 以及字符集 排列规则列表*/ +export interface IDatabaseSupportField { + columnTypes: IColumnTypes[]; + charsets: ICharset[]; + collations: ICollation[]; +} + +/** 字段所对应的 字符集*/ +export interface ICharset { + charsetName: string; // 字符集名称 + defaultCollationName: string; // 字符集默认的排序规则 +} + +/** 排列规则*/ +export interface ICollation { + collationName: string; +} + +/** 不同数据库支持的列字段类型 以及支持调整的选项*/ +export interface IColumnTypes { typeName: string; + supportAutoIncrement: boolean; // 是否支持自增 + supportCharset: boolean; // 是否支持字符集 + supportCollation: boolean; // 是否支持排序规则 + supportComments: boolean; // 是否支持注释 + supportDefaultValue: boolean; // 是否支持默认值 + supportExtent: boolean; // 是否支持扩展 + supportLength: boolean; // 是否支持长度 + supportNullable: boolean; // 是否支持为空 + supportScale: boolean; // 是否支持小数位 } diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index f78f663c9..7e3ddfc12 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -7,7 +7,7 @@ export interface IBaseInfo { } export interface IColumnItemNew { - operationType?: EditColumnOperationType; // 操作类型 + editStatus: EditColumnOperationType | null; // 操作类型 key?: string; oldName: string | null; // 老的列名 @@ -36,24 +36,8 @@ export interface IColumnItemNew { generatedColumn: string | null; // 是否生成列 } -export interface IIndexIncludeColumnItem { - key?: string; // 列的key 前端自己给的 - ascOrDesc: string | null; // 升序还是降序 - cardinality: number | null; // 基数 - collation: string | null; // 排序规则 - columnName: string | null; // 列名 - comment: string | null; // 注释 - databaseName: string | null; // 数据库名 - filterCondition: string | null; // 过滤条件 - indexName: string | null; // 索引名 - indexQualifier: string | null; // 索引限定符 - nonUnique: boolean | null; // 是否唯一 - ordinalPosition: number | null; // 位置 - schemaName: string | null; // 模式名 - tableName: string | null; // 表名 - type: string | null; // 类型 - pages: number | null; // 页数 - prefixLength: number | null; // 前缀长度 +export interface IIndexIncludeColumnItem extends IColumnItemNew { + } // 编辑表时索引的数据结构 @@ -64,6 +48,8 @@ export interface IIndexItem { comment?: string | null; type: IndexesType | null; columnList: IIndexIncludeColumnItem[]; + editStatus: EditColumnOperationType | null; // 操作类型 + } // 编辑表时整体的数据结构 From b736cdd2b64a286387b813d5178543416d65f507 Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Fri, 22 Sep 2023 18:54:30 +0800 Subject: [PATCH 0786/1069] =?UTF-8?q?datagrip=E5=AF=BC=E5=85=A5=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ncx/ConverterController.java | 57 ++++++++++------ .../ncx/service/ConverterService.java | 4 ++ .../service/impl/ConverterServiceImpl.java | 67 +++++++++++++++++++ .../server/web/api/util/FileUtils.java | 26 +++++++ 4 files changed, 134 insertions(+), 20 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java index 6bc522c48..ad9fdfe56 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java @@ -2,7 +2,6 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.util.ConfigUtils; -import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; import lombok.SneakyThrows; @@ -13,9 +12,11 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import ai.chat2db.server.web.api.util.FileUtils; import java.io.File; import java.util.Objects; +import java.util.UUID; /** * ConverterController @@ -30,36 +31,52 @@ public class ConverterController { @Autowired private ConverterService converterService; + /** + * 导出教程 + * @see + * + * @param file file + * @return DataResult + **/ @SneakyThrows @PostMapping("/ncx/upload") - public DataResult uploadFile(@RequestParam("file") MultipartFile file) { + public DataResult ncxUploadFile(@RequestParam("file") MultipartFile file) { // 验证文件后缀 - String fileExtension = getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); - if (!isAllowedExtension(fileExtension)) { + String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); + if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.NCX.name())) { return DataResult.error("1", "上传的文件必须是ncx文件!"); } - File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + "temp.tmp"); + File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); file.transferTo(temp); return DataResult.of(converterService.uploadFile(temp)); } - private String getFileExtension(String fileName) { - int dotIndex = fileName.lastIndexOf("."); - if (dotIndex > 0) { - return fileName.substring(dotIndex + 1).toLowerCase(); - } else { - return ""; + @SneakyThrows + @PostMapping("/dbp/upload") + public DataResult dbpUploadFile(@RequestParam("file") MultipartFile file) { + // 验证文件后缀 + String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); + if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.DBP.name())) { + return DataResult.error("1", "上传的文件必须是ncx文件!"); } + File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); + file.transferTo(temp); + return DataResult.of(converterService.dbpUploadFile(temp)); } - private boolean isAllowedExtension(String extension) { - // 只允许上传的文件后缀 - String[] allowedExtensions = {"ncx"}; - for (String ext : allowedExtensions) { - if (ext.equalsIgnoreCase(extension)) { - return true; - } - } - return false; + + /** + * 导入datagrip的连接信息,通过 ctrl/cmd + c(shift多选)复制连接,再导入进来 + * 目前复制的连接信息里面是没有密码的 + * + * @param text text + * @return DataResult + **/ + @SneakyThrows + @PostMapping("/datagrip/upload") + public DataResult datagripUploadFile(@RequestParam("text") String text) { + return DataResult.of(converterService.datagripUploadFile(text)); } + + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java index 659581638..fc55f7a0e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/ConverterService.java @@ -13,4 +13,8 @@ public interface ConverterService { UploadVO uploadFile(File file); + + UploadVO dbpUploadFile(File file); + + UploadVO datagripUploadFile(String text); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index d82375518..2fab62892 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -23,9 +23,14 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParser; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -44,6 +49,20 @@ public class ConverterServiceImpl implements ConverterService { private static CommonCipher cipher; + /** + * 连接信息头部 + **/ + private static final String DATASOURCE_SETTINGS = "#DataSourceSettings#"; + private static final String XML_HEADER = ""; + /** + * xml连接信息开始标志位 + **/ + private static final String BEGIN = "#BEGIN#"; + /** + * xml连接信息结束标志位 + **/ + private static final String END = "#END#"; + @Autowired private DataSourceMapper dataSourceMapper; @@ -101,6 +120,54 @@ public UploadVO uploadFile(File file) { return vo; } + + @Override + public UploadVO dbpUploadFile(File file) { + return null; + } + + @SneakyThrows + @Override + public UploadVO datagripUploadFile(String text) { + UploadVO vo = new UploadVO(); + if (!text.startsWith(DATASOURCE_SETTINGS)) { + throw new RuntimeException("连接信息的头部不正确!"); + } + String[] items = text.split("\n"); + List configs = new ArrayList<>(); + for (int i = 0; i < items.length; i++) { + if (items[i].equals(BEGIN)) { + configs.add(items[i + 1]); + } + } + for (String config : configs) { + //1、创建一个DocumentBuilderFactory的对象 + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + //2、创建一个DocumentBuilder的对象 + //创建DocumentBuilder对象 + DocumentBuilder db = dbf.newDocumentBuilder(); + //3、通过DocumentBuilder对象的parser方法加载xml文件到当前项目下 + try (InputStream inputStream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8))) { + Document document = db.parse(inputStream); + //获取Connection节点的集合 + NodeList connectList = document.getElementsByTagName("data-source"); + //选中第一个节点 + NamedNodeMap itemMap = connectList.item(0).getAttributes(); + //创建datasource + DataSourceDO dataSourceDO = new DataSourceDO(); + dataSourceDO.setAlias(itemMap.getNamedItem("name").getNodeValue()); + for (int i = 0; i < connectList.getLength(); i++) { + //通过 item(i)方法 获取一个Connection节点,nodeList的索引值从0开始 + Node connect = connectList.item(i); + //获取Connection节点的所有属性集合 + NamedNodeMap attrs = connect.getAttributes(); + } + dataSourceMapper.insert(dataSourceDO); + } + } + return vo; + } + /** * 写入到数据库 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java new file mode 100644 index 000000000..b1fdd2f54 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/FileUtils.java @@ -0,0 +1,26 @@ +package ai.chat2db.server.web.api.util; + +/** + * FileUtil + * + * @author lzy + **/ +public class FileUtils { + + public enum ConfigFile { + // navicat连接信息文件 + NCX, + // dbeaver连接信息文件 + DBP + } + + public static String getFileExtension(String fileName) { + int dotIndex = fileName.lastIndexOf("."); + if (dotIndex > 0) { + return fileName.substring(dotIndex + 1).toLowerCase(); + } else { + return ""; + } + } + +} From 992713c125eed6f80a5489bf3ca8e63708f13593 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 23 Sep 2023 14:46:48 +0800 Subject: [PATCH 0787/1069] edit table --- .../DatabaseTableEditor/ColumnList/index.tsx | 4 +- .../DatabaseTableEditor/IncludeCol/index.tsx | 65 +++++++------------ .../DatabaseTableEditor/IndexList/index.tsx | 31 +++++++-- .../src/blocks/DatabaseTableEditor/index.tsx | 16 ++++- chat2db-client/src/constants/editTable.ts | 13 ++-- chat2db-client/src/typings/editTable.ts | 20 ++++++ 6 files changed, 91 insertions(+), 58 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index b972c35c4..e3d24b58e 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -387,12 +387,14 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) function getColumnListInfo(): IColumnItemNew[] { return dataSource.map((i) => { - return { + const data = { ...i, tableName: tableDetails?.name, databaseName, schemaName: schemaName || null, }; + delete data.key; + return data; }); } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 304308991..24869109a 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import i18n from '@/i18n'; +import { string } from 'sql-formatter/lib/src/lexer/regexFactory'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; @@ -14,29 +15,7 @@ interface IProps { const createInitialData = () => { return { key: uuidv4(), - oldName: null, name: null, - tableName: null, - columnType: null, - dataType: null, - defaultValue: null, - autoIncrement: null, - comment: null, - primaryKey: null, - schemaName: null, - databaseName: null, - typeName: null, - columnSize: null, - bufferLength: null, - decimalDigits: null, - numPrecRadix: null, - nullableInt: null, - sqlDataType: null, - sqlDatetimeSub: null, - charOctetLength: null, - ordinalPosition: null, - nullable: null, - generatedColumn: null, }; }; @@ -49,9 +28,9 @@ const InitialDataSource = [createInitialData()]; const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) => { const { includedColumnList } = props; const { columnListRef } = useContext(Context); - const [dataSource, setDataSource] = useState(InitialDataSource); + const [dataSource, setDataSource] = useState(InitialDataSource); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(dataSource[0]?.key); + const [editingKey, setEditingKey] = useState(null); const isEditing = (record: IIndexIncludeColumnItem) => record.key === editingKey; useEffect(() => { @@ -59,8 +38,8 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) setDataSource( includedColumnList.map((t) => { return { - ...t, key: uuidv4(), + name: t.name, }; }), ); @@ -68,27 +47,23 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }, [includedColumnList]); const columnList: IColumnItemNew[] = useMemo(() => { - const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter((i) => i.name); + const columnListInfo = columnListRef.current?.getColumnListInfo()?.filter((i) => i.name !== null); return columnListInfo || []; }, []); - const edit = (record: IIndexIncludeColumnItem) => { + const edit = (record: any) => { form.setFieldsValue({ ...record }); - setEditingKey(record.key); + setEditingKey(record.key || null); }; const addData = () => { const newData = createInitialData(); setDataSource([...dataSource, newData]); + console.log([...dataSource, newData]); edit(newData); }; const deleteData = () => { - // if (dataSource.length === 1) { - // message.warning('至少保留一条数据') - // return - // } - setDataSource(dataSource.filter((i) => i.key !== editingKey)); }; @@ -103,12 +78,12 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }, { title: i18n('editTable.label.columnName'), - dataIndex: 'columnName', + dataIndex: 'name', // width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? ( - +
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 797b581df..8377024f3 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -23,7 +23,7 @@ import { IndexesType, EditColumnOperationType } from '@/constants'; import { Context } from '../index'; import i18n from '@/i18n'; -const indexesTypeList = [IndexesType['Normal'], IndexesType['Unique'], IndexesType['Fulltext'], IndexesType['Spatial']]; +const indexesTypeList = Object.values(IndexesType); interface IProps {} @@ -165,7 +165,10 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }; function getIndexListInfo(): IIndexListInfo { - return dataSource; + return dataSource.map((i) => { + delete i.key; + return i; + }); } useImperativeHandle(ref, () => ({ @@ -254,29 +257,47 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = ); }, }, + // { + // title: i18n('editTable.label.comment'), + // dataIndex: 'comment', + // render: (text: string, record: IIndexItem) => { + // const editable = isEditing(record); + // return editable ? ( + // + // + // + // ) : ( + //
    edit(record)}> + // {text} + //
    + // ); + // }, + // }, ]; const getIncludeColInfo = () => { setDataSource( dataSource.map((i) => { const columnList = includeColRef.current?.getIncludeColInfo(); + console.log(columnList); if (i.key === editingKey && columnList) { i.columnList = columnList; } return i; }), ); + setIncludeColModalOpen(false); }; const indexIncludedColumnList: IIndexIncludeColumnItem[] = useMemo(() => { - let data: IIndexIncludeColumnItem[] | null = []; + let list: IIndexIncludeColumnItem[] = []; dataSource.forEach((i) => { if (i.key === editingKey) { - data = i.columnList; + list = i.columnList || []; } }); - return data; + return list; }, [editingKey]); return ( diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 6c1a16b06..51dc582bc 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -8,6 +8,7 @@ import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import { IEditTableInfo } from '@/typings'; import i18n from '@/i18n'; +import lodash from 'lodash'; interface IProps { dataSourceId: number; @@ -73,7 +74,20 @@ export default memo((props: IProps) => { refresh: true, }; sqlService.getTableDetails(params).then((res) => { - setTableDetails(res || {}); + const newTableDetails = lodash.cloneDeep(res); + newTableDetails.indexList.forEach((i) => { + i.columnList = i.columnList.map((j: any) => { + let newColumn: any = {}; + newTableDetails.columnList.forEach((k: any) => { + if (j.columnName === k.name) { + newColumn = k; + } + }); + return newColumn; + }); + }); + console.log(newTableDetails); + setTableDetails(newTableDetails || {}); setOldTableDetails(res); }); } diff --git a/chat2db-client/src/constants/editTable.ts b/chat2db-client/src/constants/editTable.ts index 7ebb51e49..78fd3b39a 100644 --- a/chat2db-client/src/constants/editTable.ts +++ b/chat2db-client/src/constants/editTable.ts @@ -1,13 +1,10 @@ // 索引类型 export enum IndexesType { - // 普通索引 - Normal = 'normal', - // 唯一索引 - Unique = 'unique', - // 全文索引 - Fulltext = 'fulltext', - // 空间索引 - Spatial = 'spatial', + NormPRIMARY_KEYal = 'PRIMARY_KEY', + NORMAL = 'NORMAL', + UNIQUE = 'UNIQUE', + FULLTEXT = 'FULLTEXT', + SPATIAL = 'SPATIAL', } export enum EditColumnOperationType { diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index 7e3ddfc12..359ecdbf9 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -36,10 +36,30 @@ export interface IColumnItemNew { generatedColumn: string | null; // 是否生成列 } +// export interface IIndexIncludeColumnItem extends IColumnItemNew { +} +// 后端给的索引内列的数据结构 +export interface IAfterEndIndexIncludeColumnItem { + ascOrDesc: string | null; // 升序还是降序 + cardinality: number | null; // 基数 + collation: string | null; // 排序规则 + columnName: string | null; // 列名 + comment: string | null; // 注释 + databaseName: string | null; // 数据库名 + filterCondition: string | null; // 过滤条件 + indexName: string | null; // 索引名 + indexQualifier: string | null; // 索引限定符 + nonUnique: boolean | null; // 是否唯一 + ordinalPosition: number | null; // 位置 + schemaName: string | null; // 模式名 + tableName: string | null; // 表名 + type: string | null; // 类型 + pages: number | null; // 页数 } + // 编辑表时索引的数据结构 export interface IIndexItem { key?: string; From 6ae9f74d85cb211507737f50d1e11c1c455a8a38 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 23 Sep 2023 21:39:26 +0800 Subject: [PATCH 0788/1069] =?UTF-8?q?=E9=9B=86=E6=88=90=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/Iconfont/index.tsx | 10 +- chat2db-client/src/i18n/en-us/editTable.ts | 1 + chat2db-client/src/i18n/zh-cn/editTable.ts | 1 + .../workspace/components/TableList/index.less | 18 ++ .../workspace/components/TableList/index.tsx | 153 ++++++----- .../Tree/TreeNodeRightClick/index.tsx | 257 +++++++++--------- 6 files changed, 246 insertions(+), 194 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 55e81cc2c..c1c4f5ec0 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -5,16 +5,16 @@ import styles from './index.less'; // 只有本地开发时使用cdn,发布线上时要下载iconfont到 /assets/font if (__ENV__ === 'local') { - let container = ` + const container = ` /* 在线链接服务仅供平台体验和调试使用,平台不承诺服务的稳定性,企业客户需下载字体包自行发布使用并做好备份。 */ @font-face { font-family: 'iconfont'; /* Project id 3633546 */ - src: url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff2?t=1693730048111') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.woff?t=1693730048111') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_73vqnzklicw.ttf?t=1693730048111') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_xr1m2ndzj4j.woff2?t=1695475278282') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_xr1m2ndzj4j.woff?t=1695475278282') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_xr1m2ndzj4j.ttf?t=1695475278282') format('truetype'); } `; - let style = document.createElement('style'); + const style = document.createElement('style'); style.type = 'text/css'; document.head.appendChild(style); style.appendChild(document.createTextNode(container)); diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts index 796cf0808..f20300b06 100644 --- a/chat2db-client/src/i18n/en-us/editTable.ts +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -12,6 +12,7 @@ export default { 'editTable.label.indexType': 'Index type', 'editTable.label.includeColumn': 'Include column', 'editTable.button.createTable': 'Create table', + 'editTable.button.importTable': 'Import table', 'editTable.label.index': 'Index', 'editTable.label.columnName': 'Name', 'editTable.label.columnSize': 'Size', diff --git a/chat2db-client/src/i18n/zh-cn/editTable.ts b/chat2db-client/src/i18n/zh-cn/editTable.ts index 62c27ed3c..7724ec2cd 100644 --- a/chat2db-client/src/i18n/zh-cn/editTable.ts +++ b/chat2db-client/src/i18n/zh-cn/editTable.ts @@ -12,6 +12,7 @@ export default { 'editTable.label.indexType': '索引类型', 'editTable.label.includeColumn': '包含列', 'editTable.button.createTable': '新建表', + 'editTable.button.importTable': '导出表', 'editTable.label.index': '序号', 'editTable.label.columnName': '列名', 'editTable.label.columnSize': '长度', diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index 4586e1fdb..1a320f398 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -69,3 +69,21 @@ } } } + +.operationItem { + display: flex; + align-items: center; + .operationIconRight { + margin-left: 10px; + } + .operationTitle { + margin-left: 6px; + } +} + +:global { + .ant-dropdown-menu-submenu-title { + display: flex; + align-items: center; + } +} 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 6112820a5..5d1aaa162 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Input, Cascader, Button, Space, Dropdown, MenuProps } from 'antd'; +import { Input, Cascader, Dropdown, MenuProps } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; @@ -15,7 +15,6 @@ import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; import { ITreeNode } from '@/typings'; import styles from './index.less'; -import { DownOutlined } from '@ant-design/icons'; import { ExportTypeEnum } from '@/typings/resultTable'; interface IOption { @@ -64,44 +63,95 @@ const TableList = dvaModel((props: any) => { const items: MenuProps['items'] = useMemo( () => [ { - label: i18n('common.button.exportWord'), - key: '1', - // icon: , - onClick: () => { - handleExport(ExportTypeEnum.WORD); - }, - }, - { - label: i18n('common.button.exportExcel'), - key: '2', - // icon: , - onClick: () => { - handleExport(ExportTypeEnum.EXCEL); - }, - }, - { - label: i18n('common.button.exportHtml'), - key: '3', - // icon: , - onClick: () => { - handleExport(ExportTypeEnum.HTML); - }, - }, - { - label: i18n('common.button.exportMarkdown'), - key: '4', - // icon: , + label: ( +
    + +
    {i18n('editTable.button.createTable')}
    +
    + ), + key: 'createTable', onClick: () => { - handleExport(ExportTypeEnum.MARKDOWN); + dispatch({ + type: 'workspace/setCreateConsoleIntro', + payload: { + id: uuidV4(), + type: WorkspaceTabType.EditTable, + title: 'create-table', + uniqueData: {}, + }, + }); }, }, { - label: i18n('common.button.exportPdf'), - key: '5', - // icon: , - onClick: () => { - handleExport(ExportTypeEnum.PDF); - }, + label: ( +
    + +
    {i18n('editTable.button.importTable')}
    +
    + ), + key: 'importTable', + children: [ + { + label: ( +
    + +
    {i18n('common.button.exportWord')}
    +
    + ), + key: '1', + onClick: () => { + handleExport(ExportTypeEnum.WORD); + }, + }, + { + label: ( +
    + +
    {i18n('common.button.exportExcel')}
    +
    + ), + key: '2', + onClick: () => { + handleExport(ExportTypeEnum.EXCEL); + }, + }, + { + label: ( +
    + +
    {i18n('common.button.exportHtml')}
    +
    + ), + key: '3', + onClick: () => { + handleExport(ExportTypeEnum.HTML); + }, + }, + { + label: ( +
    + +
    {i18n('common.button.exportMarkdown')}
    +
    + ), + key: '4', + onClick: () => { + handleExport(ExportTypeEnum.MARKDOWN); + }, + }, + { + label: ( +
    + +
    {i18n('common.button.exportPdf')}
    +
    + ), + key: '5', + onClick: () => { + handleExport(ExportTypeEnum.PDF); + }, + }, + ], }, ], [curWorkspaceParams], @@ -175,25 +225,6 @@ const TableList = dvaModel((props: any) => { setCurType(selectedOptions[0]); } - const cascaderOnChange: any = () => { - dispatch({ - type: 'workspace/setCreateConsoleIntro', - payload: { - id: uuidV4(), - type: WorkspaceTabType.EditTable, - title: 'create-table', - uniqueData: {}, - }, - }); - }; - - const options = [ - { - value: 'createTable', - label: i18n('editTable.button.createTable'), - }, - ]; - return (
    @@ -223,14 +254,6 @@ const TableList = dvaModel((props: any) => {
    - - -
    refreshTableList()}>
    @@ -238,11 +261,11 @@ const TableList = dvaModel((props: any) => {
    {curType.value === TreeNodeType.TABLES && ( - +
    -
    + )}
    diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 6f65162eb..0e0c53a55 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -1,11 +1,10 @@ -import React, { memo, useContext, useMemo, useState, useRef } from 'react'; +import React, { memo, useMemo, useState } from 'react'; import i18n from '@/i18n'; -import classnames from 'classnames'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; -import { MenuProps, message, Modal, Input, Dropdown, notification } from 'antd'; +import { message, Modal, Input, Dropdown, notification } from 'antd'; import { TreeNodeType, CreateTabIntroType, WorkspaceTabType } from '@/constants'; -import { ITreeConfigItem, ITreeConfig, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; +import { ITreeConfigItem, treeConfig } from '@/pages/main/workspace/components/Tree/treeConfig'; import { ITreeNode } from '@/typings'; import connectionServer from '@/service/connection'; import mysqlServer from '@/service/sql'; @@ -21,7 +20,7 @@ export type IProps = { data: ITreeNode; dispatch: any; workspaceModel: IWorkspaceModelType['state']; -} +}; export interface IOperationColumnConfigItem { text: string; @@ -35,14 +34,14 @@ function TreeNodeRightClick(props: IProps) { const [verifyTableName, setVerifyTableName] = useState(''); const [modalApi, modelDom] = Modal.useModal(); const [notificationApi, notificationDom] = notification.useNotification(); - const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType] + const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; const { getChildren, operationColumn } = treeNodeConfig; const { curWorkspaceParams } = workspaceModel; const [monacoVerifyDialog, setMonacoVerifyDialog] = useState(false); const [monacoDefaultValue, setMonacoDefaultValue] = useState(''); const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { - return t.type === data.extraParams?.databaseType - })! + return t.type === data.extraParams?.databaseType; + })!; const OperationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { [OperationColumn.Refresh]: (data) => { return { @@ -50,36 +49,38 @@ function TreeNodeRightClick(props: IProps) { icon: '\uec08', handle: () => { refresh(); - } - } + }, + }; }, [OperationColumn.ExportDDL]: (data) => { return { text: i18n('workspace.menu.exportDDL'), icon: '\ue613', handle: () => { - mysqlServer.exportCreateTableSql({ - ...curWorkspaceParams, - tableName: data.key - } as any).then(res => { - setMonacoDefaultValue(res); - setMonacoVerifyDialog(true); - }) - } - } + mysqlServer + .exportCreateTableSql({ + ...curWorkspaceParams, + tableName: data.key, + } as any) + .then((res) => { + setMonacoDefaultValue(res); + setMonacoVerifyDialog(true); + }); + }, + }; }, [OperationColumn.ShiftOut]: (data) => { return { text: '移出', icon: '\ue62a', handle: () => { - connectionServer.remove({ id: +data.key }).then(res => { - treeConfig[TreeNodeType.DATA_SOURCES]?.getChildren!({} as any).then(res => { + connectionServer.remove({ id: +data.key }).then((res) => { + treeConfig[TreeNodeType.DATA_SOURCES]?.getChildren!({} as any).then((res) => { // setTreeData(res); - }) - }) - } - } + }); + }); + }, + }; }, [OperationColumn.CreateTable]: (data) => { return { @@ -88,19 +89,18 @@ function TreeNodeRightClick(props: IProps) { handle: () => { const operationData = { type: 'new', - nodeData: data - } + nodeData: data, + }; // setOperationDataDialog(operationData) - } - } + }, + }; }, [OperationColumn.CreateConsole]: (data) => { return { text: '新建查询', icon: '\ue619', - handle: () => { - } - } + handle: () => {}, + }; }, [OperationColumn.DeleteTable]: (data) => { return { @@ -108,8 +108,8 @@ function TreeNodeRightClick(props: IProps) { icon: '\ue6a7', handle: () => { setVerifyDialog(true); - } - } + }, + }; }, [OperationColumn.EditTable]: (data) => { return { @@ -123,9 +123,9 @@ function TreeNodeRightClick(props: IProps) { workspaceTabType: WorkspaceTabType.EditTable, treeNodeData: data, }, - }) - } - } + }); + }, + }; }, [OperationColumn.EditSource]: (data) => { return { @@ -136,24 +136,24 @@ function TreeNodeRightClick(props: IProps) { // dataType: data.dataType as any, // id: +data.key // }) - } - } + }, + }; }, [OperationColumn.Top]: (data) => { return { text: data.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: data.pinned ? '\ue61d' : '\ue627', - handle: handelTop - } + handle: handelTop, + }; }, - } + }; function handelTop() { - const api = data.pinned ? 'deleteTablePin' : 'addTablePin' + const api = data.pinned ? 'deleteTablePin' : 'addTablePin'; mysqlServer[api]({ ...curWorkspaceParams, - tableName: data.key - } as any).then(res => { + tableName: data.key, + } as any).then((res) => { dispatch({ type: 'workspace/fetchGetCurTableList', payload: { @@ -162,10 +162,10 @@ function TreeNodeRightClick(props: IProps) { refresh: true, }, callback: () => { - message.success(i18n('common.text.submittedSuccessfully')) - } - }) - }) + message.success(i18n('common.text.submittedSuccessfully')); + }, + }); + }); } function refresh() { @@ -173,11 +173,11 @@ function TreeNodeRightClick(props: IProps) { setIsLoading(true); getChildren?.({ ...data, - ...data.extraParams - }).then(res => { + ...data.extraParams, + }).then((res) => { data.children = res; setIsLoading(false); - }) + }); } function handleOk() { @@ -185,14 +185,14 @@ function TreeNodeRightClick(props: IProps) { let p: any = { ...data.extraParams, tableName: data.key, - } - mysqlServer.deleteTable(p).then(res => { + }; + mysqlServer.deleteTable(p).then((res) => { // notificationApi.success( // { // message: i18n('common.text.successfullyDelete'), // } // ) - message.success(i18n('common.text.successfullyDelete')) + message.success(i18n('common.text.successfullyDelete')); dispatch({ type: 'workspace/fetchGetCurTableList', payload: { @@ -202,30 +202,29 @@ function TreeNodeRightClick(props: IProps) { callback: () => { setVerifyDialog(false); setVerifyTableName(''); - } - }) - - }) + }, + }); + }); } else { - message.error(i18n('workspace.tips.affirmDeleteTable')) + message.error(i18n('workspace.tips.affirmDeleteTable')); } } function excludeSomeOperation() { - const excludes = dataSourceFormConfig.baseInfo.excludes - const newOperationColumn: OperationColumn[] = [] + const excludes = dataSourceFormConfig.baseInfo.excludes; + const newOperationColumn: OperationColumn[] = []; operationColumn?.map((item: OperationColumn) => { - let flag = false - excludes?.map(t => { + let flag = false; + excludes?.map((t) => { if (item === t) { - flag = true + flag = true; } - }) + }); if (!flag) { - newOperationColumn.push(item) + newOperationColumn.push(item); } - }) - return newOperationColumn + }); + return newOperationColumn; } const dropdowns = useMemo(() => { @@ -234,68 +233,78 @@ function TreeNodeRightClick(props: IProps) { const concrete = OperationColumnConfig[t](data); return { key: i, - label:
    - -
    - {concrete.text} + label: ( +
    + +
    {concrete.text}
    -
    , - onClick: concrete.handle - } + ), + onClick: concrete.handle, + }; }); } - return [] - }, [dataSourceFormConfig]) + return []; + }, [dataSourceFormConfig]); - return <> - {modelDom} - {notificationDom} - { - !!dropdowns.length && - -
    - -
    -
    - } - { setVerifyDialog(false) })} - > - { setVerifyTableName(e.target.value) }}> - - {/* 这里后续肯定是要提出去的 */} - { - monacoVerifyDialog && + return ( + <> + {modelDom} + {notificationDom} + {!!dropdowns.length && ( + +
    + +
    +
    + )} { setMonacoVerifyDialog(false) })} - footer={false} + title={`${i18n('workspace.menu.deleteTable')}-${data.key}`} + open={verifyDialog} + onOk={handleOk} + width={400} + onCancel={() => { + setVerifyDialog(false); + }} > -
    - -
    + { + setVerifyTableName(e.target.value); + }} + >
    - } - + {/* 这里后续肯定是要提出去的 */} + {monacoVerifyDialog && ( + { + setMonacoVerifyDialog(false); + }} + footer={false} + > +
    + +
    +
    + )} + + ); } -export default memo(TreeNodeRightClick) \ No newline at end of file +export default memo(TreeNodeRightClick); From 163de743f9e1530dc25a5b7392dbcf017ebb374d Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 24 Sep 2023 09:59:15 +0800 Subject: [PATCH 0789/1069] update --- .../chat2db/plugin/mysql/MysqlMetaData.java | 67 ++++++++++++++++++- .../chat2db/spi/model/TableIndexColumn.java | 3 + 2 files changed, 67 insertions(+), 3 deletions(-) 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 2d6a878fe..8d88888b0 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 @@ -1,14 +1,16 @@ package ai.chat2db.plugin.mysql; import java.sql.Connection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; 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.spi.MetaData; import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; @@ -137,6 +139,65 @@ public Table view(Connection connection, String databaseName, String schemaName, }); } + @Override + public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { + StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM "); + queryBuf.append("`").append(tableName).append("`"); + queryBuf.append(" FROM "); + queryBuf.append("`").append(databaseName).append("`"); + return SQLExecutor.getInstance().execute(connection, queryBuf.toString(), resultSet -> { + LinkedHashMap map = new LinkedHashMap(); + while (resultSet.next()) { + String keyName = resultSet.getString("Key_name"); + TableIndex tableIndex = map.get(keyName); + if (tableIndex != null) { + List columnList = tableIndex.getColumnList(); + columnList.add(getTableIndexColumn(resultSet)); + columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) + .collect(Collectors.toList()); + tableIndex.setColumnList(columnList); + } else { + TableIndex index = new TableIndex(); + index.setDatabaseName(databaseName); + index.setSchemaName(schemaName); + index.setTableName(tableName); + index.setName(keyName); + index.setUnique(!resultSet.getBoolean("Non_unique")); + index.setType(resultSet.getString("Index_type")); + index.setComment(resultSet.getString("Index_comment")); + List tableIndexColumns = new ArrayList<>(); + tableIndexColumns.add(getTableIndexColumn(resultSet)); + index.setColumnList(tableIndexColumns); + if ("PRIMARY".equalsIgnoreCase(keyName)) { + index.setType(MysqlIndexTypeEnum.PRIMARY_KEY.getName()); + } else if (index.getUnique()) { + index.setType(MysqlIndexTypeEnum.UNIQUE.getName()); + } else if ("SPATIAL".equalsIgnoreCase(index.getType())) { + index.setType(MysqlIndexTypeEnum.SPATIAL.getName()); + }else if("FULLTEXT".equalsIgnoreCase(index.getType())){ + index.setType(MysqlIndexTypeEnum.FULLTEXT.getName()); + }else { + index.setType(MysqlIndexTypeEnum.NORMAL.getName()); + } + map.put(keyName, index); + } + } + return map.values().stream().collect(Collectors.toList()); + }); + + } + + private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { + TableIndexColumn tableIndexColumn = new TableIndexColumn(); + tableIndexColumn.setColumnName(resultSet.getString("Column_name")); + tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); + tableIndexColumn.setCollation(resultSet.getString("Collation")); + tableIndexColumn.setCardinality(resultSet.getLong("Cardinality")); + tableIndexColumn.setSubPart(resultSet.getLong("Sub_part")); + tableIndexColumn.setAscOrDesc(resultSet.getString("Collation")); + return tableIndexColumn; + } + @Override public SqlBuilder getSqlBuilder() { return new MysqlSqlBuilder(); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java index 091e9cd2d..b33a25de0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndexColumn.java @@ -108,5 +108,8 @@ public class TableIndexColumn { */ @JsonAlias({"FILTER_CONDITION"}) private String filterCondition; + + + private Long subPart; } From 18ef6bc7a0d34e1d2ef6d9f121d471fa786f2d63 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 24 Sep 2023 10:06:29 +0800 Subject: [PATCH 0790/1069] update --- .../ai/chat2db/server/domain/core/impl/TableServiceImpl.java | 2 +- .../src/main/java/ai/chat2db/spi/sql/Chat2DBContext.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 5f9532956..d5ba425e3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -86,7 +86,7 @@ public DataResult
    query(TableQueryParam param, TableSelector selector) { @Override public ListResult buildSql(Table oldTable, Table newTable) { - SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(oldTable.getDbType()); + SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); List sqls = new ArrayList<>(); if (oldTable == null) { sqls.add(Sql.builder().sql(sqlBuilder.buildCreateTableSql(newTable)).build()); 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 41638d0c5..de609d45c 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 @@ -43,8 +43,8 @@ public static DriverConfig getDefaultDriverConfig(String dbType) { return PLUGIN_MAP.get(dbType).getDBConfig().getDefaultDriverConfig(); } - public static SqlBuilder getSqlBuilder(String dbType) { - return PLUGIN_MAP.get(dbType).getMetaData().getSqlBuilder(); + public static SqlBuilder getSqlBuilder() { + return PLUGIN_MAP.get(getConnectInfo().getDbType()).getMetaData().getSqlBuilder(); } /** From e959dd2ef0ac9d3b0d10675516c5c641858ba875 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 11:07:53 +0800 Subject: [PATCH 0791/1069] fix:edit table execute sql --- .../DatabaseTableEditor/ColumnList/index.tsx | 11 +++++ .../DatabaseTableEditor/IndexList/index.tsx | 1 - .../src/blocks/DatabaseTableEditor/index.less | 4 ++ .../src/blocks/DatabaseTableEditor/index.tsx | 46 +++++++++++++++++-- .../src/blocks/SQLExecute/index.tsx | 6 +-- .../components/Console/MonacoEditor/index.tsx | 9 +++- .../src/components/Iconfont/index.tsx | 6 +-- .../workspace/components/TableList/index.less | 6 +++ .../workspace/components/TableList/index.tsx | 12 ++--- chat2db-client/src/service/sql.ts | 2 +- chat2db-client/src/typings/editTable.ts | 1 - 11 files changed, 82 insertions(+), 22 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index e3d24b58e..c4c7296fd 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -325,6 +325,17 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; } + if (name === 'columnType') { + // 根据当前字段类型,设置编辑配置 + databaseSupportField.columnTypes.forEach((i) => { + if (i.typeName === value) { + setEditingConfig({ + ...editingConfig!, + ...i, + }); + } + }); + } return { ...item, [name]: value, diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 8377024f3..098d97995 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -39,7 +39,6 @@ const createInitialData = (): IIndexItem => { columnList: [], name: '', type: null, - columns: null, comment: null, editStatus: EditColumnOperationType.Add, }; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index f893d6cd8..afd0d96e8 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -63,3 +63,7 @@ z-index: 1; opacity: 0; } + +.monacoEditor { + height: 400px; +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 51dc582bc..c8b262e02 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -1,11 +1,12 @@ import React, { memo, useRef, useState, createContext, useEffect, useMemo } from 'react'; -import { Button } from 'antd'; +import { Button, Modal, message } from 'antd'; import styles from './index.less'; import classnames from 'classnames'; import IndexList, { IIndexListRef } from './IndexList'; import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; -import sqlService, { IModifyTableSqlParams } from '@/service/sql'; +import sqlService, { IModifyTableSqlParams, IExecuteSqlParams } from '@/service/sql'; +import MonacoEditor from '@/components/Console/MonacoEditor'; import { IEditTableInfo } from '@/typings'; import i18n from '@/i18n'; import lodash from 'lodash'; @@ -36,6 +37,7 @@ export default memo((props: IProps) => { const { databaseName, dataSourceId, tableName, schemaName } = props; const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); + const [viewSqlModal, setViewSqlModal] = useState(false); const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); @@ -114,14 +116,27 @@ export default memo((props: IProps) => { params.tableName = tableName; params.oldTable = oldTableDetails; } - console.log(newTable); - console.log(params.oldTable); sqlService.getModifyTableSql(params).then((res) => { - console.log(res); + setViewSqlModal(res?.[0].sql); }); } } + const executeSql = () => { + const executeSQLParams: IExecuteSqlParams = { + sql: viewSqlModal || '', + dataSourceId, + databaseName, + schemaName, + }; + + // 获取当前SQL的查询结果 + sqlService.executeSql(executeSQLParams).then((res) => { + message.success('修改成功'); + setViewSqlModal(false); + }); + }; + return ( { })} + { + setViewSqlModal(false); + }} + okText="执行" + onOk={executeSql} + width="60vw" + maskClosable={false} + > +
    + +
    +
    ); }); diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 266a3c616..ba0992805 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -16,7 +16,7 @@ import sql from '@/service/sql'; import { isNumber } from 'lodash'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import { downloadFile } from '@/utils/common'; -import { useUpdateEffect } from '@/hooks/useUpdateEffect' +import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { className?: string; isActive: boolean; @@ -41,7 +41,7 @@ const defaultResultConfig: IResultConfig = { hasNextPage: true, }; -const SQLExecute = memo(function (props) { +const SQLExecute = memo((props) => { const { data, workspaceModel, aiModel, isActive, dispatch } = props; const draggableRef = useRef(); const [appendValue, setAppendValue] = useState(); @@ -71,7 +71,7 @@ const SQLExecute = memo(function (props) { useUpdateEffect(() => { setAppendValue({ text: data.initDDL }); - }, [data.initDDL]) + }, [data.initDDL]); /** * 执行SQL diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index d296f0bea..97702fd82 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -148,7 +148,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { useEffect(() => { if (options?.theme) { monaco.editor.setTheme(options.theme); - return + return; } monaco.editor.setTheme(appTheme.backgroundColor); }, [appTheme.backgroundColor, options?.theme]); @@ -327,7 +327,12 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e case 'select': const selection = editor.getSelection(); if (selection) { - newRange = new monaco.Range(selection.startLineNumber, selection.startColumn, selection.endLineNumber, selection.endColumn); + newRange = new monaco.Range( + selection.startLineNumber, + selection.startColumn, + selection.endLineNumber, + selection.endColumn, + ); } break; // 在末尾添加内容 diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index c1c4f5ec0..7d3edfbce 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_xr1m2ndzj4j.woff2?t=1695475278282') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_xr1m2ndzj4j.woff?t=1695475278282') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_xr1m2ndzj4j.ttf?t=1695475278282') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_85ed0z3ydn4.woff2?t=1695519893270') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_85ed0z3ydn4.woff?t=1695519893270') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_85ed0z3ydn4.ttf?t=1695519893270') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index 1a320f398..49fae6edb 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -81,6 +81,12 @@ } } +.importTableOperationItem { + .operationIcon { + font-size: 16px; + } +} + :global { .ant-dropdown-menu-submenu-title { display: flex; 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 5d1aaa162..aa8394436 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -65,7 +65,7 @@ const TableList = dvaModel((props: any) => { { label: (
    - +
    {i18n('editTable.button.createTable')}
    ), @@ -93,7 +93,7 @@ const TableList = dvaModel((props: any) => { children: [ { label: ( -
    +
    {i18n('common.button.exportWord')}
    @@ -105,7 +105,7 @@ const TableList = dvaModel((props: any) => { }, { label: ( -
    +
    {i18n('common.button.exportExcel')}
    @@ -117,7 +117,7 @@ const TableList = dvaModel((props: any) => { }, { label: ( -
    +
    {i18n('common.button.exportHtml')}
    @@ -129,7 +129,7 @@ const TableList = dvaModel((props: any) => { }, { label: ( -
    +
    {i18n('common.button.exportMarkdown')}
    @@ -141,7 +141,7 @@ const TableList = dvaModel((props: any) => { }, { label: ( -
    +
    {i18n('common.button.exportPdf')}
    diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 304670d4e..c0e0e4729 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -205,7 +205,7 @@ export interface IModifyTableSqlParams { } /** 获取修改表的sql */ -const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); +const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); export default { getModifyTableSql, diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index 359ecdbf9..96f65fbe8 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -64,7 +64,6 @@ export interface IAfterEndIndexIncludeColumnItem { export interface IIndexItem { key?: string; name: string | null; - columns: string | null; comment?: string | null; type: IndexesType | null; columnList: IIndexIncludeColumnItem[]; From 86fba183a9be59e71da75ff62262b7b3fc33bc1f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 11:27:11 +0800 Subject: [PATCH 0792/1069] fix:The edit table cannot be edited again after execution --- .../blocks/DatabaseTableEditor/IncludeCol/index.tsx | 11 ++++++++--- .../blocks/DatabaseTableEditor/IndexList/index.tsx | 8 ++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 24869109a..78afc91d7 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -1,3 +1,6 @@ +/** + * 这个组件只负责拿到用户选择的表名 + * */ import React, { useMemo, useState, useContext, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import classnames from 'classnames'; @@ -6,7 +9,6 @@ import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import i18n from '@/i18n'; -import { string } from 'sql-formatter/lib/src/lexer/regexFactory'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; @@ -133,9 +135,12 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) dataSource.forEach((t) => { columnList.forEach((columnItem) => { if (t.name === columnItem.name) { - delete columnItem.key; - includeColInfo.push({ + const newColumnItem = { ...columnItem, + }; + delete newColumnItem.key; + includeColInfo.push({ + ...newColumnItem, }); } }); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 098d97995..2c364aad6 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -59,6 +59,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = const isEditing = (record: IIndexItem) => record.key === editingKey; const edit = (record: IIndexItem) => { + console.log(record); form.setFieldsValue({ ...record }); setEditingKey(record.key || null); }; @@ -165,8 +166,11 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = function getIndexListInfo(): IIndexListInfo { return dataSource.map((i) => { - delete i.key; - return i; + const newData = { + ...i, + }; + delete newData.key; + return newData; }); } From 5eeccd42abd7be71fa0eb8d425a0bc647ca40653 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 11:39:13 +0800 Subject: [PATCH 0793/1069] Remove the remaining number of ai --- .../src/blocks/DatabaseTableEditor/IncludeCol/index.tsx | 7 ++----- .../src/blocks/DatabaseTableEditor/IndexList/index.tsx | 7 ++----- chat2db-client/src/components/Console/ChatInput/index.less | 2 +- chat2db-client/src/components/Console/ChatInput/index.tsx | 4 ++-- chat2db-client/src/utils/lodash.ts | 0 5 files changed, 7 insertions(+), 13 deletions(-) create mode 100644 chat2db-client/src/utils/lodash.ts diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 78afc91d7..923722432 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import i18n from '@/i18n'; +import lodash from 'lodash'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; @@ -135,12 +136,8 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) dataSource.forEach((t) => { columnList.forEach((columnItem) => { if (t.name === columnItem.name) { - const newColumnItem = { - ...columnItem, - }; - delete newColumnItem.key; includeColInfo.push({ - ...newColumnItem, + ...lodash.omit(columnItem, 'key'), }); } }); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 2c364aad6..24b87c1cf 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -22,6 +22,7 @@ import { IIndexItem, IIndexIncludeColumnItem } from '@/typings'; import { IndexesType, EditColumnOperationType } from '@/constants'; import { Context } from '../index'; import i18n from '@/i18n'; +import lodash from 'lodash'; const indexesTypeList = Object.values(IndexesType); @@ -166,11 +167,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = function getIndexListInfo(): IIndexListInfo { return dataSource.map((i) => { - const newData = { - ...i, - }; - delete newData.key; - return newData; + return lodash.omit(i, 'key'); }); } diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index e0c0d493a..efdf834e9 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -41,7 +41,7 @@ } .tableSelectBlock { - margin-right: 8px; + // margin-right: 8px; cursor: pointer; padding: 4px; border-radius: 4px 12px; diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 62dc03009..28c6101d1 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -80,7 +80,7 @@ function ChatInput(props: IProps) {
    - {props.aiType === AiSqlSourceType.CHAT2DBAI && ( + {/* {props.aiType === AiSqlSourceType.CHAT2DBAI && (
    - )} + )} */}
    ); }; diff --git a/chat2db-client/src/utils/lodash.ts b/chat2db-client/src/utils/lodash.ts new file mode 100644 index 000000000..e69de29bb From a1a835dba49a3bfdbfd4c2fe14314505545dcb43 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 11:55:27 +0800 Subject: [PATCH 0794/1069] =?UTF-8?q?=E7=A6=81=E7=94=A8=E4=B8=8D=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E7=9A=84=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/blocks/DatabaseTableEditor/ColumnList/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index c4c7296fd..e1db2def0 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -256,7 +256,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const editable = isEditing(record); return editable ? ( - + ) : (
    edit(record)}> @@ -273,7 +273,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const editable = isEditing(record); return editable ? ( - + ) : (
    edit(record)}> @@ -289,7 +289,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const editable = isEditing(record); return editable ? ( - + ) : (
    edit(record)}> From 8037ad78184a22c9d363b61bb12b6f0a0ed607e2 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 24 Sep 2023 12:30:51 +0800 Subject: [PATCH 0795/1069] update --- .../chat2db/plugin/mysql/MysqlMetaData.java | 61 ++++++++++++++++++- .../mysql/type/MysqlColumnTypeEnum.java | 2 +- .../ai/chat2db/spi/model/TableColumn.java | 4 ++ 3 files changed, 64 insertions(+), 3 deletions(-) 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 8d88888b0..b1fb55d01 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 @@ -17,6 +17,7 @@ import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.lang3.StringUtils; public class MysqlMetaData extends DefaultMetaService implements MetaData { @Override @@ -120,6 +121,61 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, }); } + private static String SELECT_TABLE_COLUMNS = "SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s' order by ORDINAL_POSITION"; + + @Override + public List columns(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName); + List tableColumns = new ArrayList<>(); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + TableColumn column = new TableColumn(); + column.setDatabaseName(databaseName); + column.setTableName(tableName); + column.setOldName(resultSet.getString("COLUMN_NAME")); + column.setName(resultSet.getString("COLUMN_NAME")); + //column.setColumnType(resultSet.getString("COLUMN_TYPE")); + column.setColumnType(resultSet.getString("DATA_TYPE").toUpperCase()); + //column.setDataType(resultSet.getInt("DATA_TYPE")); + column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT")); + column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); + column.setComment(resultSet.getString("COLUMN_COMMENT")); + column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY"))); + column.setNullable("YES".equalsIgnoreCase(resultSet.getString("IS_NULLABLE")) ? 1 : 0); + column.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); + column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE")); + column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); + column.setCollationName(resultSet.getString("COLLATION_NAME")); + setColumnSize(column,resultSet.getString("COLUMN_TYPE")); + tableColumns.add(column); + } + return tableColumns; + }); + } + + private void setColumnSize(TableColumn column,String columnType){ + try { + if (columnType.contains("(")) { + String size = columnType.substring(columnType.indexOf("(") + 1, columnType.indexOf(")")); + if (size.contains(",")) { + String[] sizes = size.split(","); + if (StringUtils.isNotBlank(sizes[0])) { + column.setColumnSize(Integer.parseInt(sizes[0])); + } + if (StringUtils.isNotBlank(sizes[1])) { + column.setDecimalDigits(Integer.parseInt(sizes[1])); + } + } else { + column.setColumnSize(Integer.parseInt(size)); + } + } + }catch (Exception e){ + + } + } + + + private static String VIEW_SQL = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; @@ -139,6 +195,7 @@ public Table view(Connection connection, String databaseName, String schemaName, }); } + @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { StringBuilder queryBuf = new StringBuilder("SHOW INDEX FROM "); @@ -174,9 +231,9 @@ public List indexes(Connection connection, String databaseName, Stri index.setType(MysqlIndexTypeEnum.UNIQUE.getName()); } else if ("SPATIAL".equalsIgnoreCase(index.getType())) { index.setType(MysqlIndexTypeEnum.SPATIAL.getName()); - }else if("FULLTEXT".equalsIgnoreCase(index.getType())){ + } else if ("FULLTEXT".equalsIgnoreCase(index.getType())) { index.setType(MysqlIndexTypeEnum.FULLTEXT.getName()); - }else { + } else { index.setType(MysqlIndexTypeEnum.NORMAL.getName()); } map.put(keyName, index); diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index b153fd2ef..e9f15e53c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -231,7 +231,7 @@ private String buildNullable(TableColumn column,MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportNullable()){ return ""; } - if (1==column.getNullable()) { + if (column.getNullable()!=null && 1==column.getNullable()) { return "NULL"; } else { return "NOT NULL"; 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 bf07225ee..740dfa8c9 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 @@ -161,4 +161,8 @@ public class TableColumn { private String editStatus; + + private String charSetName; + + private String collationName; } From 9201442f71719fe565ee07e397ddb5caa7e53372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=83=8F=E9=A3=8E=E4=B8=80=E6=A0=B7?= <963565242@qq.com> Date: Sun, 24 Sep 2023 12:46:13 +0800 Subject: [PATCH 0796/1069] =?UTF-8?q?datagrip=E5=AF=BC=E5=85=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ncx/ConverterController.java | 6 +- .../service/impl/ConverterServiceImpl.java | 73 +++++++++++++------ 2 files changed, 54 insertions(+), 25 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java index ad9fdfe56..b9d6905ac 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java @@ -4,6 +4,7 @@ import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; +import ai.chat2db.server.web.api.util.FileUtils; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -12,7 +13,6 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import ai.chat2db.server.web.api.util.FileUtils; import java.io.File; import java.util.Objects; @@ -33,10 +33,10 @@ public class ConverterController { /** * 导出教程 - * @see * * @param file file * @return DataResult + * @see **/ @SneakyThrows @PostMapping("/ncx/upload") @@ -67,7 +67,7 @@ public DataResult dbpUploadFile(@RequestParam("file") MultipartFile fi /** * 导入datagrip的连接信息,通过 ctrl/cmd + c(shift多选)复制连接,再导入进来 - * 目前复制的连接信息里面是没有密码的 + * 目前复制的连接信息里面是没有密码的、ssh连接信息也没有 * * @param text text * @return DataResult diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index 2fab62892..4546a79a9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -16,25 +16,21 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; +import org.w3c.dom.*; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.SAXParser; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * ConverterServiceImpl @@ -58,13 +54,17 @@ public class ConverterServiceImpl implements ConverterService { * xml连接信息开始标志位 **/ private static final String BEGIN = "#BEGIN#"; - /** - * xml连接信息结束标志位 - **/ - private static final String END = "#END#"; @Autowired private DataSourceMapper dataSourceMapper; + /** + * jdbc通用匹配ip和端口 + */ + public static final Pattern IP_PORT = Pattern.compile("jdbc:(?[a-z]+)://(?[a-zA-Z0-9-//.]+):(?[0-9]+)"); + /** + * oracle匹配ip和端口 + */ + public static final Pattern ORACLE_IP_PORT = Pattern.compile("jdbc:(?[a-z]+):(?[a-z]+):@(?[a-zA-Z0-9-//.]+):(?[0-9]+)"); @Override public UploadVO uploadFile(File file) { @@ -137,7 +137,7 @@ public UploadVO datagripUploadFile(String text) { List configs = new ArrayList<>(); for (int i = 0; i < items.length; i++) { if (items[i].equals(BEGIN)) { - configs.add(items[i + 1]); + configs.add(XML_HEADER + items[i + 1]); } } for (String config : configs) { @@ -149,19 +149,48 @@ public UploadVO datagripUploadFile(String text) { //3、通过DocumentBuilder对象的parser方法加载xml文件到当前项目下 try (InputStream inputStream = new ByteArrayInputStream(config.getBytes(StandardCharsets.UTF_8))) { Document document = db.parse(inputStream); - //获取Connection节点的集合 - NodeList connectList = document.getElementsByTagName("data-source"); - //选中第一个节点 - NamedNodeMap itemMap = connectList.item(0).getAttributes(); + // 获取根元素 + Element rootElement = document.getDocumentElement(); //创建datasource DataSourceDO dataSourceDO = new DataSourceDO(); - dataSourceDO.setAlias(itemMap.getNamedItem("name").getNodeValue()); - for (int i = 0; i < connectList.getLength(); i++) { - //通过 item(i)方法 获取一个Connection节点,nodeList的索引值从0开始 - Node connect = connectList.item(i); - //获取Connection节点的所有属性集合 - NamedNodeMap attrs = connect.getAttributes(); + LocalDateTime dateTime = LocalDateTime.now(); + dataSourceDO.setGmtCreate(dateTime); + dataSourceDO.setGmtModified(dateTime); + dataSourceDO.setAlias(rootElement.getAttribute("name")); + // 获取子元素 database-info + Element databaseInfoElement = (Element) rootElement.getElementsByTagName("database-info").item(0); + + // 获取连接相关信息 + String type = databaseInfoElement.getAttribute("dbms"); + String jdbcUrl = rootElement.getElementsByTagName("jdbc-url").item(0).getTextContent(); + String username = rootElement.getElementsByTagName("user-name").item(0).getTextContent(); + String driverName = rootElement.getElementsByTagName("jdbc-driver").item(0).getTextContent(); + String host = ""; + String port = ""; + if (type.equals(DataBaseType.ORACLE.name())) { + // 创建 Matcher 对象 + Matcher matcher = ORACLE_IP_PORT.matcher(jdbcUrl); + // 查找匹配的 IP 地址和端口号 + if (matcher.find()) { + host = matcher.group("host"); + port = matcher.group("port"); + } + } else { + // 创建 Matcher 对象 + Matcher matcher = IP_PORT.matcher(jdbcUrl); + // 查找匹配的 IP 地址和端口号 + if (matcher.find()) { + host = matcher.group("host"); + port = matcher.group("port"); + + } } + dataSourceDO.setHost(host); + dataSourceDO.setPort(port); + dataSourceDO.setUrl(jdbcUrl); + dataSourceDO.setUserName(username); + dataSourceDO.setDriver(driverName); + dataSourceDO.setType(type); dataSourceMapper.insert(dataSourceDO); } } From 945a9da9a4b8e2f2b8878c4cdd4aca4b93321bf9 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 24 Sep 2023 14:38:04 +0800 Subject: [PATCH 0797/1069] update --- .../ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java | 4 ++-- .../java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index e9f15e53c..eb7d16259 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -56,9 +56,9 @@ public enum MysqlColumnTypeEnum implements ColumnBuilder { TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, true), TIME("TIME", true, false, true, false, false, false, true, true, false), YEAR("YEAR", false, false, true, false, false, false, true, true, false), - CHAR("CHAR", true, false, true, false, false, true, true, true, false), + CHAR("CHAR", true, false, true, false, true, true, true, true, false), - VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false), + VARCHAR("VARCHAR", true, false, true, false, true, true, true, true, false), BINARY("BINARY", true, false, true, false, false, false, true, true, false), diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index 530786e46..c8674010d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -38,7 +38,7 @@ public String getKeyword() { public static MysqlIndexTypeEnum getByType(String type) { for (MysqlIndexTypeEnum value : MysqlIndexTypeEnum.values()) { - if (value.name.equals(type)) { + if (value.name.equalsIgnoreCase(type)) { return value; } } From 6356f15007f28b43fc3950fc6a3d81754c37d5d5 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 15:36:34 +0800 Subject: [PATCH 0798/1069] fix:The drop-down search box supports custom input --- .../DatabaseTableEditor/ColumnList/index.tsx | 100 ++++++++------ .../DatabaseTableEditor/IndexList/index.tsx | 124 +++++++++--------- .../src/components/CustomSelect/index.tsx | 17 ++- chat2db-client/src/constants/editTable.ts | 11 +- 4 files changed, 141 insertions(+), 111 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index e1db2def0..2ac3ec44e 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,4 +1,12 @@ -import React, { useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; +import React, { + useContext, + useEffect, + useState, + useCallback, + forwardRef, + ForwardedRef, + useImperativeHandle, +} from 'react'; import styles from './index.less'; import { MenuOutlined } from '@ant-design/icons'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; @@ -104,7 +112,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const { dataSourceId, databaseName, schemaName, tableDetails } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(null); + const [editingData, setEditingData] = useState(null); const [editingConfig, setEditingConfig] = useState(null); const [databaseSupportField, setDatabaseSupportField] = useState<{ columnTypes: IColumnTypesOption[]; @@ -116,20 +124,24 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) collations: [], }); - const isEditing = (record: IColumnItemNew) => record.key === editingKey; + const isEditing = (record: IColumnItemNew) => record.key === editingData?.key; const edit = (record: IColumnItemNew) => { if (record.key) { form.setFieldsValue({ ...record }); - setEditingKey(record.key); - + setEditingData(record); // 根据当前字段类型,设置编辑配置 + console.log(databaseSupportField.columnTypes, record.columnType); databaseSupportField.columnTypes.forEach((i) => { if (i.typeName === record.columnType) { setEditingConfig({ ...i, editKey: record.key!, }); + console.log({ + ...i, + editKey: record.key!, + }); } }); } @@ -197,19 +209,19 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) width: '40px', align: 'center', }, - { - title: 'O T', - dataIndex: 'editStatus', - width: '60px', - align: 'center', - render: (text: EditColumnOperationType) => { - return text === EditColumnOperationType.Add ? ( - - ) : ( - - ); - }, - }, + // { + // title: 'O T', + // dataIndex: 'editStatus', + // width: '60px', + // align: 'center', + // render: (text: EditColumnOperationType) => { + // return text === EditColumnOperationType.Add ? ( + // + // ) : ( + // + // ); + // }, + // }, { title: i18n('editTable.label.columnName'), dataIndex: 'name', @@ -221,9 +233,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ) : ( -
    edit(record)}> - {text} -
    +
    {text}
    ); }, }, @@ -240,7 +250,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ) : ( -
    edit(record)}> - {text} -
    +
    {text}
    ); }, }, @@ -319,7 +325,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } const newData = dataSource.map((item) => { - if (item.key === editingKey) { + if (item.key === editingData?.key) { // 判断当前数据是新增的数据还是编辑后的数据 let editStatus = item.editStatus; if (editStatus !== EditColumnOperationType.Add) { @@ -345,7 +351,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) return item; }); setDataSource(newData); - console.log(field); }; const addData = () => { @@ -357,10 +362,13 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }; const deleteData = () => { - setDataSource( - dataSource.map((i) => { - if (i.key === editingKey) { - setEditingKey(null); + let list: any = []; + if (editingData?.editStatus === EditColumnOperationType.Add) { + list = dataSource.filter((i) => i.key !== editingData?.key); + } else { + list = dataSource.map((i) => { + if (i.key === editingData?.key) { + setEditingData(null); setEditingConfig(null); return { ...i, @@ -368,12 +376,13 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }; } return i; - }), - ); + }); + } + setDataSource(list); }; const moveData = (action: 'up' | 'down') => { - const index = dataSource.findIndex((i) => i.key === editingKey); + const index = dataSource.findIndex((i) => i.key === editingData?.key); if (index === -1) { return; } @@ -425,8 +434,8 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ) ); }; + const onRow = (record: any) => { + return { + onClick: () => { + if (editingData?.key !== record.key) { + edit(record); + } + }, + }; + }; + return (
    @@ -473,6 +492,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) row: Row, }, }} + onRow={onRow} pagination={false} rowKey="key" columns={columns as any} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 24b87c1cf..bba39e8e7 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -7,6 +7,7 @@ import React, { useRef, useMemo, useEffect, + useCallback, } from 'react'; import styles from './index.less'; import classnames from 'classnames'; @@ -49,20 +50,49 @@ interface RowProps extends React.HTMLAttributes { 'data-row-key': string; } +const Row = ({ children, ...props }: RowProps) => { + const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ + id: props['data-row-key'], + }); + + const style: React.CSSProperties = { + ...props.style, + transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), + transition, + ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), + }; + + return ( +
    + {React.Children.map(children, (child) => { + if ((child as React.ReactElement).key === 'sort') { + return React.cloneElement(child as React.ReactElement, { + children: ( + + ), + }); + } + return child; + })} + + ); +}; + const IndexList = forwardRef((props: IProps, ref: ForwardedRef) => { const { tableDetails } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); - const [editingKey, setEditingKey] = useState(null); + const [editingData, setEditingData] = useState(null); const [includeColModalOpen, setIncludeColModalOpen] = useState(false); const includeColRef = useRef(null); - const isEditing = (record: IIndexItem) => record.key === editingKey; + const isEditing = (record: IIndexItem) => record.key === editingData?.key; const edit = (record: IIndexItem) => { - console.log(record); form.setFieldsValue({ ...record }); - setEditingKey(record.key || null); + if (record.key !== editingData?.key) { + setEditingData(record || null); + } }; useEffect(() => { @@ -83,11 +113,11 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }; const deleteData = () => { - setDataSource(dataSource.filter((i) => i.key !== editingKey)); + setDataSource(dataSource.filter((i) => i.key !== editingData?.key)); setDataSource( dataSource.map((i) => { - if (i.key === editingKey) { - setEditingKey(null); + if (i.key === editingData?.key) { + setEditingData(null); // setEditingConfig(null); return { ...i, @@ -107,7 +137,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = value = value ? 1 : 0; } const newData = dataSource.map((item) => { - if (item.key === editingKey) { + if (item.key === editingData?.key) { let editStatus = item.editStatus; if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; @@ -133,38 +163,6 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = } }; - const Row = ({ children, ...rowProps }: RowProps) => { - const { attributes, listeners, setNodeRef, setActivatorNodeRef, transform, transition, isDragging } = useSortable({ - id: rowProps['data-row-key'], - }); - - const style: React.CSSProperties = { - ...rowProps.style, - transform: CSS.Transform.toString(transform && { ...transform, scaleY: 1 }), - transition, - ...(isDragging ? { position: 'relative', zIndex: 9999 } : {}), - }; - - return ( - - {React.Children.map(children, (child) => { - if ((child as React.ReactElement).key === 'sort') { - return React.cloneElement(child as React.ReactElement, { - children: ( - - ), - }); - } - return child; - })} - - ); - }; - function getIndexListInfo(): IIndexListInfo { return dataSource.map((i) => { return lodash.omit(i, 'key'); @@ -181,14 +179,14 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = width: '40px', align: 'center', }, - { - title: i18n('editTable.label.index'), - width: '70px', - align: 'center', - render: (text: string, record: IIndexItem) => { - return dataSource.findIndex((i) => i.key === record.key) + 1; - }, - }, + // { + // title: i18n('editTable.label.index'), + // width: '70px', + // align: 'center', + // render: (text: string, record: IIndexItem) => { + // return dataSource.findIndex((i) => i.key === record.key) + 1; + // }, + // }, { title: i18n('editTable.label.indexName'), dataIndex: 'name', @@ -200,9 +198,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = ) : ( -
    edit(record)}> - {text} -
    +
    {text}
    ); }, }, @@ -223,9 +219,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = ) : ( -
    edit(record)}> - {text} -
    +
    {text}
    ); }, }, @@ -251,9 +245,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = {text} ) : ( -
    edit(record)}> - {text} -
    +
    {text}
    ); }, }, @@ -279,8 +271,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = setDataSource( dataSource.map((i) => { const columnList = includeColRef.current?.getIncludeColInfo(); - console.log(columnList); - if (i.key === editingKey && columnList) { + if (i.key === editingData?.key && columnList) { i.columnList = columnList; } return i; @@ -290,15 +281,25 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = setIncludeColModalOpen(false); }; + const onRow = (record: any) => { + return { + onClick: () => { + if (editingData?.key !== record.key) { + edit(record); + } + }, + }; + }; + const indexIncludedColumnList: IIndexIncludeColumnItem[] = useMemo(() => { let list: IIndexIncludeColumnItem[] = []; dataSource.forEach((i) => { - if (i.key === editingKey) { + if (i.key === editingData?.key) { list = i.columnList || []; } }); return list; - }, [editingKey]); + }, [editingData?.key]); return (
    @@ -318,6 +319,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = row: Row, }, }} + onRow={onRow} pagination={false} rowKey="key" columns={columns as any} diff --git a/chat2db-client/src/components/CustomSelect/index.tsx b/chat2db-client/src/components/CustomSelect/index.tsx index 5e8e2dba1..4363e1095 100644 --- a/chat2db-client/src/components/CustomSelect/index.tsx +++ b/chat2db-client/src/components/CustomSelect/index.tsx @@ -16,7 +16,8 @@ interface IProps { const CustomSelect = memo((props: IProps) => { const { options, onChange, value } = props; const [customOptions, setCustomOptions] = useState([]); - const [customValue, setCustomValue] = useState(); + const [customValue, setCustomValue] = useState(''); + const [curSearch, setCurSearch] = useState(null); useEffect(() => { setCustomOptions([...options, { label: '', value: null }]); @@ -40,22 +41,30 @@ const CustomSelect = memo((props: IProps) => { }; const onSearch = (v: string) => { - customOptions[customOptions.length - 1].label = v; - customOptions[customOptions.length - 1].value = v; - setCustomOptions([...customOptions]); + setCurSearch(v); }; const customChange = (v: string) => { + setCurSearch(null); setCustomValue(v); onChange?.(v); }; + const onBlur = () => { + if (curSearch) { + onChange?.(curSearch); + } + setCurSearch(null); + }; + return ( { inputOnChange(e.target.value) }} className={styles.input} autoFocus onBlur={onBlur} type="text" /> - : + )} + > + {t.key === editingTab ? ( + { + inputOnChange(e.target.value); + }} + className={styles.input} + autoFocus + onBlur={onBlur} + type="text" + /> + ) : (
    {t.prefixIcon && } -
    - {t.label} -
    +
    {t.label}
    - } -
    - + )} +
    + +
    -
    + ); } - return
    -
    - { - !!internalTabs?.length && -
    - { - internalTabs.map((t, index) => { - return renderTabItem(t, index) - }) - } -
    - } - { - !hideAdd &&
    -
    - + return ( +
    +
    + {!!internalTabs?.length && ( +
    + {internalTabs.map((t, index) => { + return renderTabItem(t, index); + })}
    -
    - } -
    -
    - { - internalTabs?.map(t => { - return
    - {t.children} + )} + {!hideAdd && ( +
    +
    + +
    - }) - } + )} +
    +
    + {internalTabs?.map((t) => { + return ( +
    + {t.children} +
    + ); + })} +
    -
    -}) + ); +}); diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 58f060476..176c045d3 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -95,7 +95,7 @@ function Connections(props: IProps) { }); }; - const handelEnvRefresh = (kind: ConnectionKind) => { + const handleEnvRefresh = (kind: ConnectionKind) => { let p = { pageNo: 1, pageSize: 999, @@ -143,7 +143,7 @@ function Connections(props: IProps) { <>
    {data.name}
    - handelEnvRefresh(t as ConnectionKind)} /> + handleEnvRefresh(t as ConnectionKind)} />
    {(data.list || []).map(t => { const { key, label, icon } = t; diff --git a/chat2db-client/tsconfig.json b/chat2db-client/tsconfig.json index 0b7fd3f2f..bfe6ef82f 100644 --- a/chat2db-client/tsconfig.json +++ b/chat2db-client/tsconfig.json @@ -2,6 +2,7 @@ "extends": "./src/.umi/tsconfig.json", "compilerOptions": { "moduleResolution": "node", - "jsx": "react", + "jsx": "react-jsx", + "noImplicitAny": false, }, } \ No newline at end of file From 9c8c70ba2dac94852c3968f5e496f3f457a13faf Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 24 Sep 2023 16:45:10 +0800 Subject: [PATCH 0801/1069] update --- .../plugin/mysql/type/MysqlColumnTypeEnum.java | 12 ++++++------ .../ncx/service/impl/ConverterServiceImpl.java | 9 +++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index 08c443418..a34ea0bb6 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -72,16 +72,16 @@ public enum MysqlColumnTypeEnum implements ColumnBuilder { LONGBLOB("LONGBLOB", false, false, true, false, false, false, true, false, false, false), - TINYTEXT("TINYTEXT", false, false, true, false, false, true, true, false, false, false), + TINYTEXT("TINYTEXT", false, false, true, false, true, true, true, false, false, false), - TEXT("TEXT", false, false, true, false, false, true, true, false, false, false), + TEXT("TEXT", false, false, true, false, true, true, true, false, false, false), - MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, false, true, true, false, false, false), + MEDIUMTEXT("MEDIUMTEXT", false, false, true, false, true, true, true, false, false, false), - LONGTEXT("LONGTEXT", false, false, true, false, false, true, true, false, false, false), + LONGTEXT("LONGTEXT", false, false, true, false, true, true, true, false, false, false), - ENUM("ENUM", false, false, true, false, false, true, true, true, true, true), + ENUM("ENUM", false, false, true, false, true, true, true, true, true, true), BOOL("BOOL", false, false, true, true, false, false, true, true, false, false), @@ -92,7 +92,7 @@ public enum MysqlColumnTypeEnum implements ColumnBuilder { REAL("REAL", true, true, true, false, false, false, true, true, false, false), - SET("SET", false, false, true, false, false, true, true, true, true, true), + SET("SET", false, false, true, false, true, true, true, true, true, true), GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, false, false, false), diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index 4546a79a9..92c091b69 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -25,10 +25,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -153,7 +150,7 @@ public UploadVO datagripUploadFile(String text) { Element rootElement = document.getDocumentElement(); //创建datasource DataSourceDO dataSourceDO = new DataSourceDO(); - LocalDateTime dateTime = LocalDateTime.now(); + Date dateTime = new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(rootElement.getAttribute("name")); @@ -221,7 +218,7 @@ public void insertDBConfig(List>> list) { } // 解密密码 String password = cipher.decryptString(resultMap.getOrDefault("Password", "")); - LocalDateTime dateTime = LocalDateTime.now(); + Date dateTime =new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(resultMap.get("ConnectionName")); From 788bea6cd8ace85baeca39ac92ec4f6715af882d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 17:19:46 +0800 Subject: [PATCH 0802/1069] feat:edit table base info --- .../DatabaseTableEditor/BaseInfo/index.tsx | 19 ++++++--- .../DatabaseTableEditor/ColumnList/index.tsx | 28 ++++++++++--- .../DatabaseTableEditor/IncludeCol/index.tsx | 41 +++++++++++-------- .../DatabaseTableEditor/IndexList/index.tsx | 5 +-- .../src/blocks/DatabaseTableEditor/index.tsx | 19 ++------- .../src/blocks/Setting/AiSetting/index.tsx | 11 ++++- chat2db-client/src/i18n/en-us/editTable.ts | 5 +++ chat2db-client/src/i18n/zh-cn/editTable.ts | 5 +++ chat2db-client/src/typings/database.ts | 1 + chat2db-client/src/typings/editTable.ts | 16 +++++--- 10 files changed, 96 insertions(+), 54 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index e85fcd48e..2350d9fe4 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -23,6 +23,9 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => form.setFieldsValue({ name: tableDetails.name, comment: tableDetails.comment, + charset: tableDetails.charset, + engine: tableDetails.engine, + incrementValue: tableDetails.incrementValue, }); }, [tableDetails]); @@ -38,9 +41,6 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) =>
    ) => className={styles.form} > - + - + + + + + + + + + +
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 2ac3ec44e..ecf8dad33 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -104,6 +104,9 @@ const createInitialData = () => { ordinalPosition: null, nullable: null, generatedColumn: null, + charSetName: null, + collationName: null, + value: null, editStatus: EditColumnOperationType.Add, }; }; @@ -230,7 +233,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const editable = isEditing(record); return editable ? ( - + ) : (
    {text}
    @@ -297,7 +300,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const editable = isEditing(record); return editable ? ( - + ) : (
    {text}
    @@ -429,6 +432,16 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) return ( <> + {editingConfig?.supportAutoIncrement && ( + + + + )} {editingConfig?.supportDefaultValue && ( ) )} {editingConfig?.supportCharset && ( - + )} {editingConfig?.supportCollation && ( - + )} {editingConfig?.supportScale && ( - + + + )} + {editingConfig?.supportValue && ( + + )} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 923722432..2130ba675 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -18,7 +18,22 @@ interface IProps { const createInitialData = () => { return { key: uuidv4(), - name: null, + ascOrDesc: null, // 升序还是降序 + cardinality: null, // 基数 + collation: null, // 排序规则 + columnName: null, // 列名 + comment: null, // 注释 + filterCondition: null, // 过滤条件 + indexName: null, // 索引名 + indexQualifier: null, // 索引限定符 + nonUnique: null, // 是否唯一 + ordinalPosition: null, // 位置 + schemaName: null, // 模式名 + type: null, // 类型 + pages: null, // 页数 + + databaseName: null, // 数据库名 + tableName: null, // 表名 }; }; @@ -26,12 +41,10 @@ export interface IIncludeColRef { getIncludeColInfo: () => IIndexIncludeColumnItem[]; } -const InitialDataSource = [createInitialData()]; - const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) => { const { includedColumnList } = props; const { columnListRef } = useContext(Context); - const [dataSource, setDataSource] = useState(InitialDataSource); + const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(null); const isEditing = (record: IIndexIncludeColumnItem) => record.key === editingKey; @@ -41,8 +54,8 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) setDataSource( includedColumnList.map((t) => { return { + ...t, key: uuidv4(), - name: t.name, }; }), ); @@ -81,12 +94,12 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }, { title: i18n('editTable.label.columnName'), - dataIndex: 'name', + dataIndex: 'columnName', // width: '45%', render: (text: string, record: IIndexIncludeColumnItem) => { const editable = isEditing(record); return editable ? ( - + + ) : (
    {text}
    @@ -230,7 +229,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = const editable = isEditing(record); const text = columnList ?.map((t) => { - return `${t.name}`; + return `${t.columnName}`; }) .join(','); return editable ? ( diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index c8b262e02..527a31509 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -77,18 +77,6 @@ export default memo((props: IProps) => { }; sqlService.getTableDetails(params).then((res) => { const newTableDetails = lodash.cloneDeep(res); - newTableDetails.indexList.forEach((i) => { - i.columnList = i.columnList.map((j: any) => { - let newColumn: any = {}; - newTableDetails.columnList.forEach((k: any) => { - if (j.columnName === k.name) { - newColumn = k; - } - }); - return newColumn; - }); - }); - console.log(newTableDetails); setTableDetails(newTableDetails || {}); setOldTableDetails(res); }); @@ -130,9 +118,8 @@ export default memo((props: IProps) => { schemaName, }; - // 获取当前SQL的查询结果 - sqlService.executeSql(executeSQLParams).then((res) => { - message.success('修改成功'); + sqlService.executeSql(executeSQLParams).then(() => { + message.success(i18n('common.text.successfulExecution')); setViewSqlModal(false); }); }; @@ -179,7 +166,7 @@ export default memo((props: IProps) => {
    { setViewSqlModal(false); diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index 8aafb01fe..d2f30a5b9 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -19,7 +19,7 @@ export default function SettingAI(props: IProps) { const [aiConfig, setAiConfig] = useState(); const [userInfo, setUserInfo] = useState(); const [loading, setLoading] = useState(false); - + const queryUserInfo = async () => { setLoading(true); try { @@ -89,6 +89,7 @@ export default function SettingAI(props: IProps) {
    Api Key
    { @@ -103,6 +104,7 @@ export default function SettingAI(props: IProps) {
    Api Key
    { @@ -113,6 +115,7 @@ export default function SettingAI(props: IProps) {
    Api Host
    { @@ -123,6 +126,7 @@ export default function SettingAI(props: IProps) {
    HTTP Proxy Host
    { @@ -136,6 +140,7 @@ export default function SettingAI(props: IProps) {
    HTTP Proxy Port
    { @@ -153,6 +158,7 @@ export default function SettingAI(props: IProps) {
    Api Key
    { @@ -163,6 +169,7 @@ export default function SettingAI(props: IProps) {
    Endpoint
    { @@ -173,6 +180,7 @@ export default function SettingAI(props: IProps) {
    DeploymentId
    { @@ -190,6 +198,7 @@ export default function SettingAI(props: IProps) {
    {i18n('setting.label.customAiUrl')}
    { diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts index f20300b06..529240fe0 100644 --- a/chat2db-client/src/i18n/en-us/editTable.ts +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -22,4 +22,9 @@ export default { 'editTable.label.defaultValue': 'Default value', 'editTable.label.characterSet': 'Character set', 'editTable.label.collation': 'Collation', + 'editTable.label.value': 'Value', + 'editTable.label.autoIncrement': 'Auto increment', + 'editTable.label.engine': 'Engine', + 'editTable.label.incrementValue': 'Increment value', + 'editTable.title.sqlPreview': 'SQL preview', }; diff --git a/chat2db-client/src/i18n/zh-cn/editTable.ts b/chat2db-client/src/i18n/zh-cn/editTable.ts index 7724ec2cd..5a4604311 100644 --- a/chat2db-client/src/i18n/zh-cn/editTable.ts +++ b/chat2db-client/src/i18n/zh-cn/editTable.ts @@ -23,4 +23,9 @@ export default { 'editTable.label.characterSet': '字符集', 'editTable.label.collation': '排序规则', 'editTable.label.decimalPoint': '小数点', + 'editTable.label.value': '值', + 'editTable.label.autoIncrement': '是否自增', + 'editTable.label.engine': '引擎', + 'editTable.label.incrementValue': '自增值', + 'editTable.title.sqlPreview': 'sql预览', }; diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index 7b9a0e222..bf56efd48 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -65,4 +65,5 @@ export interface IColumnTypes { supportLength: boolean; // 是否支持长度 supportNullable: boolean; // 是否支持为空 supportScale: boolean; // 是否支持小数位 + supportValue: boolean; // 是否支持值 } diff --git a/chat2db-client/src/typings/editTable.ts b/chat2db-client/src/typings/editTable.ts index 96f65fbe8..711bce954 100644 --- a/chat2db-client/src/typings/editTable.ts +++ b/chat2db-client/src/typings/editTable.ts @@ -3,7 +3,10 @@ import { IndexesType, EditColumnOperationType } from '@/constants'; // 编辑表时表的基础数据 export interface IBaseInfo { name: string; - comment?: string; + comment?: string | null; + charset: string | null; // 字符集 + engine: string | null; // 引擎 + incrementValue: string | null; // 自增值 } export interface IColumnItemNew { @@ -34,14 +37,15 @@ export interface IColumnItemNew { ordinalPosition: number| null; // 位置 nullable: 0 | 1 | null; //是否为空 generatedColumn: string | null; // 是否生成列 -} -// -export interface IIndexIncludeColumnItem extends IColumnItemNew { + charSetName: string | null; // 字符集名 + collationName: string | null; // 排序规则名 + value: string | null; // 值 } -// 后端给的索引内列的数据结构 -export interface IAfterEndIndexIncludeColumnItem { +// +export interface IIndexIncludeColumnItem { + key?: string; // 前端添加的唯一标识 ascOrDesc: string | null; // 升序还是降序 cardinality: number | null; // 基数 collation: string | null; // 排序规则 From cab6a27acddb188953da7d9ec6a8ff8d6b823f9e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 17:59:33 +0800 Subject: [PATCH 0803/1069] style --- .../DatabaseTableEditor/BaseInfo/index.less | 7 +++++-- .../DatabaseTableEditor/BaseInfo/index.tsx | 2 +- .../src/blocks/DatabaseTableEditor/index.less | 20 ++++++------------- .../src/blocks/DatabaseTableEditor/index.tsx | 4 ++-- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less index b33787deb..b2a5e7ebc 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less @@ -1,7 +1,10 @@ @import '../../../styles/var.less'; -.box { - padding: 10px; +.baseInfo { + padding: 40px 10px 0px; + display: flex; + justify-content: center; + height: 100%; } .formBox { diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index 2350d9fe4..2dcbf3470 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -38,7 +38,7 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => })); return ( -
    +
    { ); })}
    -
    + {/*
    -
    +
    */}
    {tabList.map((t) => { From 45d64b892b9f5e8486bbd5a03b8fdd306cc8d569 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 24 Sep 2023 21:59:41 +0800 Subject: [PATCH 0804/1069] style --- .../DatabaseTableEditor/ColumnList/index.less | 3 ++- .../DatabaseTableEditor/ColumnList/index.tsx | 15 ++++++--------- .../DatabaseTableEditor/IndexList/index.less | 2 +- .../DatabaseTableEditor/IndexList/index.tsx | 5 +++++ .../src/blocks/DatabaseTableEditor/index.less | 12 +++++++++++- .../src/blocks/DatabaseTableEditor/index.tsx | 4 ++-- 6 files changed, 27 insertions(+), 14 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less index 8a45a9dc4..d9760125b 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less @@ -25,10 +25,11 @@ .tableBox { flex: 1; - overflow: auto; + overflow: hidden; } .otherInfo { + flex-shrink: 0; margin: 10px -10px 0px; padding: 10px; height: 200px; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index ecf8dad33..610625afb 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,12 +1,4 @@ -import React, { - useContext, - useEffect, - useState, - useCallback, - forwardRef, - ForwardedRef, - useImperativeHandle, -} from 'react'; +import React, { useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import { MenuOutlined } from '@ant-design/icons'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; @@ -510,6 +502,11 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) row: Row, }, }} + style={{ + height: '100%', + overflow: 'auto', + }} + sticky onRow={onRow} pagination={false} rowKey="key" diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less index 065e4b7c6..07c62be7f 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less @@ -24,7 +24,7 @@ .tableBox { flex: 1; - overflow: auto; + overflow: hidden; } .editableCell { diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 45967bcc7..77dcfcbbb 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -318,6 +318,11 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = row: Row, }, }} + style={{ + height: '100%', + overflow: 'auto', + }} + sticky onRow={onRow} pagination={false} rowKey="key" diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index 679a03722..825e95269 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -7,6 +7,7 @@ } .header { + position: relative; display: flex; align-items: center; justify-content: center; @@ -14,6 +15,12 @@ box-sizing: border-box; } +.saveButton { + position: absolute; + // left: 0px; + right: 20px; +} + .tabList { display: flex; border-bottom: 0; @@ -29,11 +36,14 @@ padding: 0px 30px; border-radius: 6px; cursor: pointer; + &:hover { + color: var(--color-primary); + } } .currentTab { color: var(--color-primary); - background-color: var(--color-hover-bg); + background-color: var(--color-primary-bg); } .main { diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 99c802f83..527a31509 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -149,11 +149,11 @@ export default memo((props: IProps) => { ); })}
    - {/*
    +
    -
    */} +
    {tabList.map((t) => { From 49e719f6377aded7c9afdd008a41fcfe799d7bcc Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 25 Sep 2023 17:47:16 +0800 Subject: [PATCH 0805/1069] support table edit --- .../plugin/mysql/builder/MysqlSqlBuilder.java | 10 ++++++++-- .../plugin/mysql/type/MysqlIndexTypeEnum.java | 14 ++++++++------ .../controller/rdb/request/IndexRequest.java | 8 ++++++-- .../controller/rdb/request/TableRequest.java | 17 ++++++++++++++++- 4 files changed, 38 insertions(+), 11 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 a3c83e692..3420b8265 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 @@ -18,14 +18,20 @@ public String buildCreateTableSql(Table table) { // append column for (TableColumn column : table.getColumnList()) { + if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType())){ + continue; + } MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(column.getColumnType()); script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); } // append primary key and index for (TableIndex tableIndex : table.getIndexList()) { + if(StringUtils.isBlank(tableIndex.getName())|| StringUtils.isBlank(tableIndex.getType())){ + continue; + } MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); - script.append("\t").append("ADD ").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + script.append("\t").append("").append(mysqlIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); } script = new StringBuilder(script.substring(0, script.length() - 2)); @@ -84,7 +90,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { - if (StringUtils.isNotBlank(tableIndex.getEditStatus())) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { MysqlIndexTypeEnum mysqlIndexTypeEnum = MysqlIndexTypeEnum.getByType(tableIndex.getType()); script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index c8674010d..2c0623dea 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -9,13 +9,13 @@ public enum MysqlIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), - NORMAL("Normal", "KEY"), + NORMAL("Normal", "INDEX"), - UNIQUE("Unique", "UNIQUE KEY"), + UNIQUE("Unique", "UNIQUE INDEX"), - FULLTEXT("Fulltext", "FULLTEXT KEY"), + FULLTEXT("Fulltext", "FULLTEXT INDEX"), - SPATIAL("Spatial", "SPATIAL KEY"); + SPATIAL("Spatial", "SPATIAL INDEX"); public String getName() { return name; @@ -72,7 +72,9 @@ private String buildIndexColumn(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { - script.append("`").append(column.getColumnName()).append("`").append(","); + if(StringUtils.isNotBlank(column.getColumnName())) { + script.append("`").append(column.getColumnName()).append("`").append(","); + } } script.deleteCharAt(script.length() - 1); script.append(")"); @@ -104,6 +106,6 @@ private String buildDropIndex(TableIndex tableIndex) { if (MysqlIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } - return StringUtils.join("DROP KEY `", tableIndex.getOldName()); + return StringUtils.join("DROP INDEX `", tableIndex.getOldName()); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/IndexRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/IndexRequest.java index 051376a3e..ae798888d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/IndexRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/IndexRequest.java @@ -3,8 +3,8 @@ import java.util.List; import ai.chat2db.spi.enums.IndexTypeEnum; -import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; +import ai.chat2db.spi.model.TableIndexColumn; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -41,5 +41,9 @@ public class IndexRequest { /** * 索引包含的列 */ - private List columnList; + private List columnList; + + + private String editStatus; + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java index 169ff4e2d..0e49878d6 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableRequest.java @@ -3,6 +3,7 @@ import java.util.List; import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -36,5 +37,19 @@ public class TableRequest { /** * 索引 */ - private List indexList; + private List indexList; + + + private String engine; + + + private String charset; + + + private String collate; + + private Long incrementValue; + + private String partition; + } From 48f63ce31a49c572b9a02145ace6d9851232b849 Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Mon, 25 Sep 2023 19:35:17 +0800 Subject: [PATCH 0806/1069] =?UTF-8?q?dbeaver=E5=AF=BC=E5=85=A5=E5=88=9D?= =?UTF-8?q?=E6=AD=A5=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ncx/ConverterController.java | 2 +- .../controller/ncx/enums/ExportConstants.java | 58 ++++ .../service/impl/ConverterServiceImpl.java | 101 +++++- .../chat2db/server/web/api/util/XMLUtils.java | 287 ++++++++++++++++++ 4 files changed, 445 insertions(+), 3 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/XMLUtils.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java index b9d6905ac..25d77f37b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/ConverterController.java @@ -57,7 +57,7 @@ public DataResult dbpUploadFile(@RequestParam("file") MultipartFile fi // 验证文件后缀 String fileExtension = FileUtils.getFileExtension(Objects.requireNonNull(file.getOriginalFilename())); if (!fileExtension.equalsIgnoreCase(FileUtils.ConfigFile.DBP.name())) { - return DataResult.error("1", "上传的文件必须是ncx文件!"); + return DataResult.error("1", "上传的文件必须是dbp文件!"); } File temp = new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + UUID.randomUUID() + ".tmp"); file.transferTo(temp); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java new file mode 100644 index 000000000..4382be94e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java @@ -0,0 +1,58 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.chat2db.server.web.api.controller.ncx.enums; + +import ai.chat2db.server.tools.common.util.ConfigUtils; + +import java.io.File; + +/** + * Import/Export constants + */ +public class ExportConstants { + + public static final String ARCHIVE_FILE_EXT = ".dbp"; //NON-NLS-1 + public static final String CONFIG_FILE = ".dbeaver"; //NON-NLS-1 + public static final String CONFIG_DATASOURCE_FILE = "data-sources.json"; //NON-NLS-1 + public static final String DIR_PROJECTS = "projects"; //NON-NLS-1 + public static final String DIR_DRIVERS = "drivers"; //NON-NLS-1 + + public static final String META_FILENAME = "meta.xml"; //NON-NLS-1 + + public static final String TAG_ARCHIVE = "archive"; //NON-NLS-1 + public static final String TAG_SOURCE = "source"; + public static final String TAG_PROJECTS = "projects"; //NON-NLS-1 + public static final String TAG_PROJECT = "project"; //NON-NLS-1 + public static final String TAG_RESOURCE = "resource"; //NON-NLS-1 + public static final String TAG_ATTRIBUTE = "attribute"; //NON-NLS-1 + public static final String TAG_LIBRARIES = "libraries"; //NON-NLS-1 + + public static final String ATTR_VERSION = "version"; //NON-NLS-1 + public static final String ATTR_HOST = "host"; + public static final String ATTR_ADDRESS = "address"; + public static final String ATTR_TIME = "time"; + public static final String ATTR_QUALIFIER = "qualifier"; //NON-NLS-1 + public static final String ATTR_NAME = "name"; //NON-NLS-1 + public static final String ATTR_VALUE = "value"; //NON-NLS-1 + public static final String ATTR_DIRECTORY = "directory"; //NON-NLS-1 + public static final String ATTR_DESCRIPTION = "description"; //NON-NLS-1 + public static final String ATTR_CHARSET = "charset"; //NON-NLS-1 + public static final String ATTR_PATH = "path"; //NON-NLS-1 + public static final String ATTR_FILE = "file"; //NON-NLS-1 + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index 4546a79a9..fe91401c1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -3,15 +3,22 @@ import ai.chat2db.server.domain.core.util.DesUtil; import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; +import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType; +import ai.chat2db.server.web.api.controller.ncx.enums.ExportConstants; import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; import ai.chat2db.server.web.api.controller.ncx.factory.CipherFactory; import ai.chat2db.server.web.api.controller.ncx.service.ConverterService; import ai.chat2db.server.web.api.controller.ncx.vo.UploadVO; +import ai.chat2db.server.web.api.util.XMLUtils; import ai.chat2db.spi.model.SSHInfo; +import cn.hutool.core.io.FileUtil; import com.alibaba.excel.util.FileUtils; import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONArray; +import com.alibaba.fastjson2.JSONObject; +import com.google.common.io.Files; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -22,15 +29,22 @@ import javax.xml.parsers.DocumentBuilderFactory; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.InputStream; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; /** * ConverterServiceImpl @@ -120,10 +134,93 @@ public UploadVO uploadFile(File file) { return vo; } - + @SneakyThrows @Override public UploadVO dbpUploadFile(File file) { - return null; + UploadVO vo = new UploadVO(); + Document metaTree; + //等待删除的projects + List projects = new ArrayList<>(); + try (ZipFile zipFile = new ZipFile(file, ZipFile.OPEN_READ)) { + ZipEntry metaEntry = zipFile.getEntry(ExportConstants.META_FILENAME); + if (metaEntry == null) { + throw new RuntimeException("Cannot find meta file"); + } + try (InputStream metaStream = zipFile.getInputStream(metaEntry)) { + metaTree = XMLUtils.parseDocument(metaStream); + } catch (Exception e) { + throw new RuntimeException("Cannot parse meta file: " + e.getMessage()); + } + Element projectsElement = XMLUtils.getChildElement(metaTree.getDocumentElement(), ExportConstants.TAG_PROJECTS); + if (projectsElement != null) { + final Collection projectList = XMLUtils.getChildElementList(projectsElement, ExportConstants.TAG_PROJECT); + for (Element projectElement : projectList) { + //获取项目名称 + String projectName = projectElement.getAttribute(ExportConstants.ATTR_NAME); + //导入匹配文件目录 + String config = ConfigUtils.CONFIG_BASE_PATH + File.separator + projectName + File.separator + ExportConstants.CONFIG_FILE; + importDbeaverConfig(new File(config), + projectElement, + //不可替换成File.separator + ExportConstants.DIR_PROJECTS + "/" + projectName + "/", + zipFile); + //加入删除名单 + projects.add(projectName); + //配置的json文件 + File json = new File(config + File.separator + ExportConstants.CONFIG_DATASOURCE_FILE); + JSONObject jsonObject = JSON.parseObject(new FileInputStream(json)); + JSONObject connections = jsonObject.getJSONObject("connections"); + Set keys = connections.keySet(); + for (String key : keys) { + JSONObject configurations = connections.getJSONObject(key); + JSONObject configuration = configurations.getJSONObject("configuration"); + DataSourceDO dataSourceDO = new DataSourceDO(); + LocalDateTime dateTime = LocalDateTime.now(); + dataSourceDO.setGmtCreate(dateTime); + dataSourceDO.setGmtModified(dateTime); + dataSourceDO.setAlias(configurations.getString("name")); + dataSourceDO.setHost(configuration.getString("host")); + dataSourceDO.setPort(configuration.getString("port")); + dataSourceDO.setUrl(configuration.getString("url")); + //dataSourceDO.setUserName(configuration.getString("host")); + //dataSourceDO.setDriver(configuration.getString("host")); + dataSourceDO.setType(configurations.getString("provider").toUpperCase()); + dataSourceMapper.insert(dataSourceDO); + } + } + } + } + //删除临时文件 + FileUtils.delete(file); + //删除dbeaver存留的配置临时文件 + //projects.forEach(v -> FileUtils.delete(new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + v))); + return vo; + } + + @SneakyThrows + private static void importDbeaverConfig(File resource, Element resourceElement, String containerPath, ZipFile zipFile) { + for (Element childElement : XMLUtils.getChildElementList(resourceElement, ExportConstants.TAG_RESOURCE)) { + String childName = childElement.getAttribute(ExportConstants.ATTR_NAME); + String entryPath = containerPath + childName; + ZipEntry resourceEntry = zipFile.getEntry(entryPath); + if (resourceEntry == null) { + continue; + } + boolean isDirectory = resourceEntry.isDirectory(); + if (isDirectory) { + File folder = new File(resource.getPath()); + if (!folder.exists()) { + FileUtil.mkdir(folder); + } + resource = folder; + importDbeaverConfig(folder, childElement, entryPath + "/", zipFile); + } else { + File file = new File(resource.getPath() + File.separator + childName); + if (!file.exists()) { + FileUtil.writeFromStream(zipFile.getInputStream(resourceEntry), file); + } + } + } } @SneakyThrows diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/XMLUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/XMLUtils.java new file mode 100644 index 000000000..1755af40a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/XMLUtils.java @@ -0,0 +1,287 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.chat2db.server.web.api.util; + +import org.apache.batik.xml.XMLException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.InputSource; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Common XML utils + */ +public class XMLUtils { + + public static Document parseDocument(String fileName) + throws XMLException { + return parseDocument(new java.io.File(fileName)); + } + + public static Document parseDocument(java.io.File file) throws XMLException { + try (InputStream is = new FileInputStream(file)) { + return parseDocument(new InputSource(is)); + } catch (IOException e) { + throw new XMLException("Error opening file '" + file + "'", e); + } + } + + public static Document parseDocument(InputStream is) throws XMLException { + return parseDocument(new InputSource(is)); + } + + public static Document parseDocument(java.io.Reader is) throws XMLException { + return parseDocument(new InputSource(is)); + } + + public static Document parseDocument(InputSource source) throws XMLException { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + DocumentBuilder xmlBuilder = dbf.newDocumentBuilder(); + return xmlBuilder.parse(source); + } catch (Exception er) { + throw new XMLException("Error parsing XML document", er); + } + } + + public static Document createDocument() + throws XMLException { + try { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder xmlBuilder = dbf.newDocumentBuilder(); + return xmlBuilder.newDocument(); + } catch (Exception er) { + throw new XMLException("Error creating XML document", er); + } + } + + public static Element getChildElement(Element element, String childName) { + if (element == null) { + return null; + } + for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE && + ((Element) node).getTagName().equals(childName)) { + return (Element) node; + } + } + return null; + } + + public static String getChildElementBody(Element element, String childName) { + if (element == null) { + return null; + } + for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE && + ((Element) node).getTagName().equals(childName)) { + return getElementBody((Element) node); + } + } + return null; + } + + public static String getElementBody( Element element) { + return element.getTextContent(); + } + + // Get list of all child elements of specified node + + public static List getChildElementList( + Element parent, + String nodeName) { + List list = new ArrayList<>(); + if (parent != null) { + for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE && + nodeName.equals(node.getNodeName())) { + list.add((Element) node); + } + } + } + return list; + } + + // Get list of all child elements of specified node + + public static Collection getChildElementListNS( + Element parent, + String nsURI) { + List list = new ArrayList<>(); + if (parent != null) { + for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE && + node.getNamespaceURI().equals(nsURI)) { + list.add((Element) node); + } + } + } + return list; + } + + // Get list of all child elements of specified node + public static Collection getChildElementListNS( + Element parent, + String nodeName, + String nsURI) { + List list = new ArrayList<>(); + for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE && + node.getLocalName().equals(nodeName) && + node.getNamespaceURI().equals(nsURI)) { + list.add((Element) node); + } + } + return list; + } + + // Get list of all child elements of specified node + + public static Collection getChildElementList( + Element parent, + String[] nodeNameList) { + List list = new ArrayList<>(); + for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + for (int i = 0; i < nodeNameList.length; i++) { + if (node.getNodeName().equals(nodeNameList[i])) { + list.add((Element) node); + } + } + } + } + return list; + } + + // Find one child element with specified name + public static Element findChildElement( + Element parent) { + for (Node node = parent.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + return (Element) node; + } + } + return null; + } + + public static Object escapeXml(Object obj) { + if (obj == null) { + return null; + } else if (obj instanceof CharSequence) { + return escapeXml((CharSequence) obj); + } else { + return obj; + } + } + + public static String escapeXml(CharSequence str) { + if (str == null) { + return null; + } + StringBuilder res = null; + int strLength = str.length(); + for (int i = 0; i < strLength; i++) { + char c = str.charAt(i); + String repl = encodeXMLChar(c); + if (repl == null) { + if (res != null) { + res.append(c); + } + } else { + if (res == null) { + res = new StringBuilder(str.length() + 5); + for (int k = 0; k < i; k++) { + res.append(str.charAt(k)); + } + } + res.append(repl); + } + } + return res == null ? str.toString() : res.toString(); + } + + public static boolean isValidXMLChar(char c) { + return (c >= 32 || c == '\n' || c == '\r' || c == '\t'); + } + + /** + * Encodes a char to XML-valid form replacing &,',",<,> with special XML encoding. + * + * @param ch char to convert + * @return XML-encoded text + */ + public static String encodeXMLChar(char ch) { + switch (ch) { + case '&': + return "&"; + case '\"': + return """; + case '\'': + return "'"; + case '<': + return "<"; + case '>': + return ">"; + default: + return null; + } + } + + public static XMLException adaptSAXException(Exception toCatch) { + if (toCatch instanceof XMLException) { + return (XMLException) toCatch; + } else if (toCatch instanceof org.xml.sax.SAXException) { + String message = toCatch.getMessage(); + Exception embedded = ((org.xml.sax.SAXException) toCatch).getException(); + if (embedded != null && embedded.getMessage() != null && embedded.getMessage().equals(message)) { + // Just SAX wrapper - skip it + return adaptSAXException(embedded); + } else { + return new XMLException( + message, + embedded != null ? adaptSAXException(embedded) : null); + } + } else { + return new XMLException(toCatch.getMessage(), toCatch); + } + } + + public static Collection getChildElementList(Element element) { + List children = new ArrayList<>(); + if (element != null) { + for (Node node = element.getFirstChild(); node != null; node = node.getNextSibling()) { + if (node.getNodeType() == Node.ELEMENT_NODE) { + children.add((Element) node); + } + } + } + return children; + } +} From d2a377b2e1c04ab7ccf280b87d7e5e777d303a7f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 25 Sep 2023 23:15:19 +0800 Subject: [PATCH 0807/1069] fix:edit table bug style --- .../DatabaseTableEditor/BaseInfo/index.less | 4 +- .../DatabaseTableEditor/ColumnList/index.less | 80 ++++++++++++++++++- .../DatabaseTableEditor/ColumnList/index.tsx | 79 +++++++++--------- .../DatabaseTableEditor/IncludeCol/index.less | 66 ++++++++++++++- .../DatabaseTableEditor/IncludeCol/index.tsx | 51 +++++++++--- .../DatabaseTableEditor/IndexList/index.less | 74 ++++++++++++++++- .../DatabaseTableEditor/IndexList/index.tsx | 78 +++++++++++------- .../src/blocks/DatabaseTableEditor/index.less | 33 +++++--- .../src/blocks/DatabaseTableEditor/index.tsx | 42 ++++++---- .../src/components/Iconfont/index.tsx | 6 +- chat2db-client/src/i18n/en-us/editTable.ts | 5 +- chat2db-client/src/i18n/zh-cn/editTable.ts | 1 + 12 files changed, 403 insertions(+), 116 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less index b2a5e7ebc..8f022caec 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.less @@ -1,9 +1,9 @@ @import '../../../styles/var.less'; .baseInfo { - padding: 40px 10px 0px; + padding: 20px 10px 0px; display: flex; - justify-content: center; + // justify-content: center; height: 100%; } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less index d9760125b..f945bd709 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.less @@ -25,9 +25,31 @@ .tableBox { flex: 1; + display: flex; + flex-direction: column; + border-radius: 8px 8px 0px 0px; overflow: hidden; } +.addColumnButton { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + border: 1px dashed var(--color-border); + line-height: 30px; + margin-top: 10px; + color: var(--color-text-secondary); + cursor: pointer; + i { + margin-right: 5px; + } + &:hover { + color: var(--color-primary); + border-color: var(--color-primary); + } +} + .otherInfo { flex-shrink: 0; margin: 10px -10px 0px; @@ -50,11 +72,63 @@ padding: 0px 7px; box-sizing: border-box; border: 1px solid transparent; - border-radius: 4px; .f-single-line(); width: 100%; cursor: pointer; - &:hover { - border: 1px solid var(--color-border); +} + +// .cellContent { +// border: 1px solid transparent; +// margin: -1px; +// &:hover { +// border: 1px solid var(--color-primary); +// } +// } + +.operationBar { + display: flex; + justify-content: end; + .deleteIconBox { + height: 26px; + width: 26px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + &:hover { + color: var(--color-primary); + } + } +} +.columnList { + :global { + .ant-table-body { + border: 1px solid var(--color-border); + border-top: 0px; + border-bottom: 0px; + } + .ant-table-header { + border: 1px solid var(--color-border); + border-bottom: 0px; + } + .ant-table { + border-radius: 10px; + border-bottom: 0px; + } + .ant-table-wrapper .ant-table-tbody > tr > td { + // border: 0px; + padding: 4px 2px; + } + .ant-table-wrapper .ant-table-thead > tr > th { + padding: 8px 4px; + &::before { + display: none; + } + } + .ant-table-wrapper .ant-table-thead > tr > td { + &::before { + display: none; + } + } } } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 610625afb..074c0ad09 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -3,7 +3,7 @@ import styles from './index.less'; import { MenuOutlined } from '@ant-design/icons'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; -import { Table, InputNumber, Input, Form, Select, Checkbox, Button } from 'antd'; +import { Table, InputNumber, Input, Form, Select, Checkbox } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; @@ -13,6 +13,7 @@ import { IColumnItemNew, IColumnTypes } from '@/typings'; import i18n from '@/i18n'; import { EditColumnOperationType } from '@/constants'; import CustomSelect from '@/components/CustomSelect'; +import Iconfont from '@/components/Iconfont'; interface RowProps extends React.HTMLAttributes { 'data-row-key': string; @@ -223,12 +224,16 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) width: '160px', render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); - return editable ? ( - - - - ) : ( -
    {text}
    + return ( +
    + {editable ? ( + + + + ) : ( +
    {text}
    + )} +
    ); }, }, @@ -299,6 +304,23 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) ); }, }, + { + width: '40px', + render: (text: string, record: IColumnItemNew) => { + return ( +
    { + deleteData(record); + }} + > +
    + +
    +
    + ); + }, + }, ]; const onDragEnd = ({ active, over }: DragEndEvent) => { @@ -356,13 +378,13 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) edit(newData); }; - const deleteData = () => { + const deleteData = (record) => { let list: any = []; - if (editingData?.editStatus === EditColumnOperationType.Add) { - list = dataSource.filter((i) => i.key !== editingData?.key); + if (record?.editStatus === EditColumnOperationType.Add) { + list = dataSource.filter((i) => i.key !== record?.key); } else { list = dataSource.map((i) => { - if (i.key === editingData?.key) { + if (i.key === record?.key) { setEditingData(null); setEditingConfig(null); return { @@ -376,30 +398,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) setDataSource(list); }; - const moveData = (action: 'up' | 'down') => { - const index = dataSource.findIndex((i) => i.key === editingData?.key); - if (index === -1) { - return; - } - if (action === 'up') { - if (index === 0) { - return; - } - const newData = [...dataSource]; - newData[index] = dataSource[index - 1]; - newData[index - 1] = dataSource[index]; - setDataSource(newData); - } else { - if (index === dataSource.length - 1) { - return; - } - const newData = [...dataSource]; - newData[index] = dataSource[index + 1]; - newData[index + 1] = dataSource[index]; - setDataSource(newData); - } - }; - function getColumnListInfo(): IColumnItemNew[] { return dataSource.map((i) => { const data = { @@ -486,12 +484,12 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) return (
    -
    + {/*
    -
    +
    */}
    @@ -503,7 +501,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }, }} style={{ - height: '100%', + maxHeight: '100%', overflow: 'auto', }} sticky @@ -515,7 +513,12 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) /> +
    + + {i18n('editTable.button.addColumn')} +
    +
    {renderOtherInfoForm()}
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less index a5d5c3b79..b4da4ea3e 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.less @@ -1,16 +1,25 @@ @import '../../../styles/var.less'; -.box { - max-height: 60vh; - overflow-y: auto; - overflow-x: hidden; +.includeCol { + max-height: 40vh; + display: flex; + flex-direction: column; + overflow: hidden; } .ant-input-number { width: 100%; } +.formBox { + flex: 1; + height: 0; + overflow: hidden; + display: flex; +} + .indexListHeader { + flex-shrink: 0; margin: 0px -10px 10px; button { margin: 0px 10px; @@ -31,3 +40,52 @@ border: 1px solid var(--color-border); } } + +.operationBar { + display: flex; + justify-content: end; + .deleteIconBox { + height: 26px; + width: 26px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + &:hover { + color: var(--color-primary); + } + } +} + +.includeCol { + :global { + .ant-table-body { + border: 1px solid var(--color-border); + border-top: 0px; + border-bottom: 0px; + } + .ant-table-header { + border: 1px solid var(--color-border); + border-bottom: 0px; + } + .ant-table { + border-radius: 10px; + border-bottom: 0px; + } + .ant-table-wrapper .ant-table-tbody > tr > td { + // border: 0px; + padding: 4px 2px; + } + .ant-table-wrapper .ant-table-thead > tr > th { + padding: 8px 4px; + &::before { + display: none; + } + } + .ant-table-wrapper .ant-table-thead > tr > td { + &::before { + display: none; + } + } + } +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 2130ba675..907dd88ca 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -10,6 +10,7 @@ import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; import i18n from '@/i18n'; import lodash from 'lodash'; +import Iconfont from '@/components/Iconfont'; interface IProps { includedColumnList: IIndexIncludeColumnItem[]; @@ -79,15 +80,16 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) edit(newData); }; - const deleteData = () => { - setDataSource(dataSource.filter((i) => i.key !== editingKey)); + const deleteData = (record) => { + setDataSource(dataSource.filter((i) => i.key !== record.key)); }; const columns = [ { title: i18n('editTable.label.index'), dataIndex: 'index', - width: '10%', + width: '50px', + align: 'center', render: (text: string, record: IIndexIncludeColumnItem) => { return dataSource.findIndex((i) => i.key === record.key) + 1; }, @@ -109,6 +111,23 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) ); }, }, + { + width: '40px', + render: (text: string, record: IIndexIncludeColumnItem) => { + return ( +
    { + deleteData(record); + }} + > +
    + +
    +
    + ); + }, + }, // { // title: i18n('editTable.label.prefixLength'), // dataIndex: 'prefixLength', @@ -145,9 +164,11 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }; const getIncludeColInfo = (): IIndexIncludeColumnItem[] => { - return dataSource.map((t) => { - return lodash.omit(t, 'key'); - }); + return dataSource + .map((t) => { + return lodash.omit(t, 'key'); + }) + .filter((t) => t.columnName); }; useImperativeHandle(ref, () => ({ @@ -155,13 +176,23 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) })); return ( -
    +
    - + {/* */}
    - -
    + +
    ); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less index 07c62be7f..195cea1cb 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.less @@ -16,17 +16,40 @@ } .formBox { - flex: 1; height: 0px; + flex: 1; display: flex; flex-direction: column; } .tableBox { flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; + border-radius: 8px 8px 0px 0px; overflow: hidden; } +.addColumnButton { + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; + border: 1px dashed var(--color-border); + line-height: 30px; + margin-top: 10px; + color: var(--color-text-secondary); + cursor: pointer; + i { + margin-right: 5px; + } + &:hover { + color: var(--color-primary); + border-color: var(--color-primary); + } +} + .editableCell { height: 28px; line-height: 26px; @@ -52,3 +75,52 @@ } } } + +.operationBar { + display: flex; + justify-content: end; + .deleteIconBox { + height: 26px; + width: 26px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + &:hover { + color: var(--color-primary); + } + } +} + +.indexList { + :global { + .ant-table-body { + border: 1px solid var(--color-border); + border-top: 0px; + border-bottom: 0px; + } + .ant-table-header { + border: 1px solid var(--color-border); + border-bottom: 0px; + } + .ant-table { + border-radius: 10px; + border-bottom: 0px; + } + .ant-table-wrapper .ant-table-tbody > tr > td { + // border: 0px; + padding: 4px 2px; + } + .ant-table-wrapper .ant-table-thead > tr > th { + padding: 8px 4px; + &::before { + display: none; + } + } + .ant-table-wrapper .ant-table-thead > tr > td { + &::before { + display: none; + } + } + } +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 77dcfcbbb..5c3fd35bc 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -15,11 +15,12 @@ import { type DragEndEvent, DndContext } from '@dnd-kit/core'; import { restrictToVerticalAxis } from '@dnd-kit/modifiers'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import { Table, Input, Form, Select, Button, Modal } from 'antd'; +import { Table, Input, Form, Select, Modal } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import IncludeCol, { IIncludeColRef } from '../IncludeCol'; import { IIndexItem, IIndexIncludeColumnItem } from '@/typings'; import { IndexesType, EditColumnOperationType } from '@/constants'; +import Iconfont from '@/components/Iconfont'; import { Context } from '../index'; import i18n from '@/i18n'; import lodash from 'lodash'; @@ -111,13 +112,12 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = edit(newData); }; - const deleteData = () => { - setDataSource(dataSource.filter((i) => i.key !== editingData?.key)); + const deleteData = (record) => { + setDataSource(dataSource.filter((i) => i.key !== record?.key)); setDataSource( dataSource.map((i) => { - if (i.key === editingData?.key) { + if (i.key === record?.key) { setEditingData(null); - // setEditingConfig(null); return { ...i, editStatus: EditColumnOperationType.Delete, @@ -248,22 +248,39 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = ); }, }, - // { - // title: i18n('editTable.label.comment'), - // dataIndex: 'comment', - // render: (text: string, record: IIndexItem) => { - // const editable = isEditing(record); - // return editable ? ( - // - // - // - // ) : ( - //
    edit(record)}> - // {text} - //
    - // ); - // }, - // }, + { + title: i18n('editTable.label.comment'), + dataIndex: 'comment', + render: (text: string, record: IIndexItem) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
    edit(record)}> + {text} +
    + ); + }, + }, + { + width: '40px', + render: (text: string, record: IIndexItem) => { + return ( +
    { + deleteData(record); + }} + > +
    + +
    +
    + ); + }, + }, ]; const getIncludeColInfo = () => { @@ -298,16 +315,16 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = } }); return list; - }, [editingData?.key]); + }, [includeColModalOpen]); return (
    -
    + {/*
    - {/* - */} -
    + + +
    */}
    @@ -319,7 +336,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = }, }} style={{ - height: '100%', + maxHeight: '100%', overflow: 'auto', }} sticky @@ -331,11 +348,16 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = /> +
    + + {i18n('editTable.button.addColumn')} +
    { diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index 825e95269..1a5466661 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -7,18 +7,17 @@ } .header { - position: relative; + // position: relative; display: flex; align-items: center; - justify-content: center; + justify-content: space-between; margin: 10px 10px 0px 10px; box-sizing: border-box; } .saveButton { - position: absolute; - // left: 0px; - right: 20px; + // position: absolute; + // right: 20px; } .tabList { @@ -27,14 +26,29 @@ font-size: 14px; border-radius: 6px; background-color: var(--color-bg-subtle); + position: relative; + &::after { + position: absolute; + left: calc(82px * var(--i)); + content: ''; + height: 26px; + width: 82px; + border-radius: 4px; + color: var(--color-primary); + background-color: var(--color-primary-bg); + z-index: 0; + transition: left 0.2s ease-out; + } } .tabItem { position: relative; - height: 28px; - line-height: 28px; - padding: 0px 30px; - border-radius: 6px; + z-index: 1; + height: 26px; + width: 82px; + text-align: center; + line-height: 26px; + border-radius: 4px; cursor: pointer; &:hover { color: var(--color-primary); @@ -43,7 +57,6 @@ .currentTab { color: var(--color-primary); - background-color: var(--color-primary-bg); } .main { diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 527a31509..24696f70d 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -19,6 +19,7 @@ interface IProps { } interface ITabItem { + index: number; title: string; key: string; component: any; // TODO: 组件的Ts是什么 @@ -38,22 +39,26 @@ export default memo((props: IProps) => { const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); const [viewSqlModal, setViewSqlModal] = useState(false); + const [newTableName, setNewTableName] = useState(tableName); const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); const tabList = useMemo(() => { return [ { + index: 0, title: i18n('editTable.tab.basicInfo'), key: 'basic', component: , }, { + index: 1, title: i18n('editTable.tab.columnInfo'), key: 'column', component: , }, { + index: 2, title: i18n('editTable.tab.indexInfo'), key: 'index', component: , @@ -68,21 +73,26 @@ export default memo((props: IProps) => { useEffect(() => { if (tableName) { - const params = { - databaseName, - dataSourceId, - tableName, - schemaName, - refresh: true, - }; - sqlService.getTableDetails(params).then((res) => { - const newTableDetails = lodash.cloneDeep(res); - setTableDetails(newTableDetails || {}); - setOldTableDetails(res); - }); + getTableDetails(); } }, []); + const getTableDetails = () => { + if (!newTableName) return; + const params = { + databaseName, + dataSourceId, + tableName: newTableName, + schemaName, + refresh: true, + }; + sqlService.getTableDetails(params).then((res) => { + const newTableDetails = lodash.cloneDeep(res); + setTableDetails(newTableDetails || {}); + setOldTableDetails(res); + }); + }; + function submit() { if (baseInfoRef.current && columnListRef.current && indexListRef.current) { const newTable = { @@ -100,11 +110,12 @@ export default memo((props: IProps) => { newTable, }; - if (tableName) { - params.tableName = tableName; + if (newTableName) { + // params.tableName = tableName; params.oldTable = oldTableDetails; } sqlService.getModifyTableSql(params).then((res) => { + setNewTableName(newTable.name); setViewSqlModal(res?.[0].sql); }); } @@ -120,6 +131,7 @@ export default memo((props: IProps) => { sqlService.executeSql(executeSQLParams).then(() => { message.success(i18n('common.text.successfulExecution')); + getTableDetails(); setViewSqlModal(false); }); }; @@ -136,7 +148,7 @@ export default memo((props: IProps) => { >
    -
    +
    {tabList.map((item) => { return (
    Date: Tue, 26 Sep 2023 11:38:58 +0800 Subject: [PATCH 0808/1069] =?UTF-8?q?dbeaver=E5=AF=BC=E5=85=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ncx/dbeaver/DBSValueEncryptor.java | 29 +++++ .../ncx/dbeaver/DefaultValueEncryptor.java | 105 ++++++++++++++++++ .../controller/ncx/enums/DataBaseType.java | 17 +-- .../controller/ncx/enums/ExportConstants.java | 8 +- .../service/impl/ConverterServiceImpl.java | 87 ++++++++++----- 5 files changed, 209 insertions(+), 37 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DBSValueEncryptor.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DefaultValueEncryptor.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DBSValueEncryptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DBSValueEncryptor.java new file mode 100644 index 000000000..60fde7853 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DBSValueEncryptor.java @@ -0,0 +1,29 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ai.chat2db.server.web.api.controller.ncx.dbeaver; + +/** + * Value encryptor + */ +public interface DBSValueEncryptor { + + byte[] encryptValue(byte[] value); + + byte[] decryptValue(byte[] value); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DefaultValueEncryptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DefaultValueEncryptor.java new file mode 100644 index 000000000..28ed1ef07 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/dbeaver/DefaultValueEncryptor.java @@ -0,0 +1,105 @@ +/* + * DBeaver - Universal Database Manager + * Copyright (C) 2010-2023 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ai.chat2db.server.web.api.controller.ncx.dbeaver; + +import org.apache.poi.util.IOUtils; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/** + * Default value encryptor. + * + * Uses Eclipse secure preferences to read/write secrets. + */ +public class DefaultValueEncryptor implements DBSValueEncryptor { + + public static final String CIPHER_NAME = "AES/CBC/PKCS5Padding"; + public static final String KEY_ALGORITHM = "AES"; + + private static final byte[] LOCAL_KEY_CACHE = new byte[] { -70, -69, 74, -97, 119, 74, -72, 83, -55, 108, 45, 101, 61, -2, 84, 74 }; + + private final SecretKey secretKey; + private final Cipher cipher; + + public DefaultValueEncryptor(SecretKey secretKey) { + this.secretKey = secretKey; + try { + this.cipher = Cipher.getInstance(CIPHER_NAME); + } catch (Exception e) { + throw new IllegalStateException("Internal error during encrypted init", e); + } + } + + /** + * 通过 DBeaver 源码查看到默认的 SecretKey + **/ + public static SecretKey getLocalSecretKey() { + return new SecretKeySpec(LOCAL_KEY_CACHE, DefaultValueEncryptor.KEY_ALGORITHM); + } + + public static SecretKey makeSecretKeyFromPassword(String password) { + byte[] bytes = password.getBytes(StandardCharsets.UTF_8); + byte[] passBytes = Arrays.copyOf(bytes, 16); + return new SecretKeySpec(passBytes, KEY_ALGORITHM); + } + + @Override + public byte[] encryptValue(byte[] value) { + try { + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + byte[] iv = cipher.getIV(); + + ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream(); + try (CipherOutputStream cipherOut = new CipherOutputStream(resultBuffer, cipher)) { + resultBuffer.write(iv); + cipherOut.write(value); + } + return resultBuffer.toByteArray(); + } catch (Exception e) { + throw new RuntimeException("Error encrypting value", e); + } + } + + @Override + public byte[] decryptValue(byte[] value) { + try (InputStream byteStream = new ByteArrayInputStream(value)) { + byte[] fileIv = new byte[16]; + byteStream.read(fileIv); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(fileIv)); + + try (CipherInputStream cipherIn = new CipherInputStream(byteStream, cipher)) { + ByteArrayOutputStream resultBuffer = new ByteArrayOutputStream(); + IOUtils.copy(cipherIn, resultBuffer); + return resultBuffer.toByteArray(); + } + + } catch (Exception e) { + throw new RuntimeException("Error decrypting value", e); + } + } + +} \ No newline at end of file diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java index 3dacfeade..114a88578 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/DataBaseType.java @@ -37,31 +37,31 @@ public enum DataBaseType { /** * Mariadb **/ - Mariadb("jdbc:mariadb://%s:%s"), + MARIADB("jdbc:mariadb://%s:%s"), /** * DM **/ DM("jdbc:dm://%s:%s"), /** - * KINGBASE8 + * KINGBASE **/ - KINGBASE8("jdbc:kingbase8://%s:%s"), + KINGBASE("jdbc:kingbase8://%s:%s"), /** * Presto **/ - Presto("jdbc:presto://%s:%s"), + PRESTO("jdbc:presto://%s:%s"), /** * OceanBase **/ - OceanBase("jdbc:oceanbase://%s:%s"), + OCEANBASE("jdbc:oceanbase://%s:%s"), /** * Hive **/ - Hive("jdbc:hive2://%s:%s"), + HIVE("jdbc:hive2://%s:%s"), /** * ClickHouse **/ - ClickHouse("jdbc:clickhouse://%s:%s"); + CLICKHOUSE("jdbc:clickhouse://%s:%s"); private String urlString; @@ -76,7 +76,8 @@ public void setUrlString(String urlString) { public static DataBaseType matchType(String value) { if (StringUtils.isNotEmpty(value)) { for (DataBaseType dataBase : DataBaseType.values()) { - if (dataBase.name().equals(value.toUpperCase())) { + //kingbase -> kingbase8 + if (value.toUpperCase().contains(dataBase.name())) { return dataBase; } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java index 4382be94e..7907cd64a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/enums/ExportConstants.java @@ -17,10 +17,6 @@ package ai.chat2db.server.web.api.controller.ncx.enums; -import ai.chat2db.server.tools.common.util.ConfigUtils; - -import java.io.File; - /** * Import/Export constants */ @@ -29,8 +25,12 @@ public class ExportConstants { public static final String ARCHIVE_FILE_EXT = ".dbp"; //NON-NLS-1 public static final String CONFIG_FILE = ".dbeaver"; //NON-NLS-1 public static final String CONFIG_DATASOURCE_FILE = "data-sources.json"; //NON-NLS-1 + public static final String CONFIG_CREDENTIALS_FILE = "credentials-config.json"; //NON-NLS-1 public static final String DIR_PROJECTS = "projects"; //NON-NLS-1 public static final String DIR_DRIVERS = "drivers"; //NON-NLS-1 + public static final String DIR_CONNECTIONS = "connections"; //NON-NLS-1 + public static final String DIR_CONFIGURATION = "configuration"; //NON-NLS-1 + public static final String GENERIC = "generic"; //NON-NLS-1 public static final String META_FILENAME = "meta.xml"; //NON-NLS-1 diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index fe91401c1..0de1bc69c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -5,6 +5,7 @@ import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.common.util.ConfigUtils; import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; +import ai.chat2db.server.web.api.controller.ncx.dbeaver.DefaultValueEncryptor; import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType; import ai.chat2db.server.web.api.controller.ncx.enums.ExportConstants; import ai.chat2db.server.web.api.controller.ncx.enums.VersionEnum; @@ -16,14 +17,16 @@ import cn.hutool.core.io.FileUtil; import com.alibaba.excel.util.FileUtils; import com.alibaba.fastjson2.JSON; -import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; -import com.google.common.io.Files; import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.w3c.dom.*; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; @@ -31,15 +34,15 @@ import java.io.File; import java.io.FileInputStream; import java.io.InputStream; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -68,6 +71,10 @@ public class ConverterServiceImpl implements ConverterService { * xml连接信息开始标志位 **/ private static final String BEGIN = "#BEGIN#"; + /** + * 密码json的key + **/ + private static final String connection = "#connection"; @Autowired private DataSourceMapper dataSourceMapper; @@ -82,7 +89,6 @@ public class ConverterServiceImpl implements ConverterService { @Override public UploadVO uploadFile(File file) { - UploadVO vo = new UploadVO(); try { // List>> 要导入的连接 @@ -169,30 +175,64 @@ public UploadVO dbpUploadFile(File file) { //配置的json文件 File json = new File(config + File.separator + ExportConstants.CONFIG_DATASOURCE_FILE); JSONObject jsonObject = JSON.parseObject(new FileInputStream(json)); - JSONObject connections = jsonObject.getJSONObject("connections"); + JSONObject connections = jsonObject.getJSONObject(ExportConstants.DIR_CONNECTIONS); Set keys = connections.keySet(); for (String key : keys) { JSONObject configurations = connections.getJSONObject(key); - JSONObject configuration = configurations.getJSONObject("configuration"); - DataSourceDO dataSourceDO = new DataSourceDO(); - LocalDateTime dateTime = LocalDateTime.now(); - dataSourceDO.setGmtCreate(dateTime); - dataSourceDO.setGmtModified(dateTime); - dataSourceDO.setAlias(configurations.getString("name")); - dataSourceDO.setHost(configuration.getString("host")); - dataSourceDO.setPort(configuration.getString("port")); - dataSourceDO.setUrl(configuration.getString("url")); - //dataSourceDO.setUserName(configuration.getString("host")); - //dataSourceDO.setDriver(configuration.getString("host")); - dataSourceDO.setType(configurations.getString("provider").toUpperCase()); - dataSourceMapper.insert(dataSourceDO); + JSONObject configuration = configurations.getJSONObject(ExportConstants.DIR_CONFIGURATION); + //匹配数据库类型 + String provider = configurations.getString("provider"); + if (provider.equals(ExportConstants.GENERIC)) { + //自定义驱动 + JSONObject drivers = jsonObject.getJSONObject(ExportConstants.DIR_DRIVERS); + //获得驱动id + String driverId = configurations.getString("driver"); + //获得所有generic + JSONObject generics = drivers.getJSONObject(provider); + //获得自己的驱动 + JSONObject generic = generics.getJSONObject(driverId); + //如果不存在,则不导入 + if (null == generic) { + continue; + } + //赋值驱动名称,用来确定数据库的类型 + provider = generic.getString("name"); + } + DataBaseType dataBaseType = DataBaseType.matchType(provider.toUpperCase()); + DataSourceDO dataSourceDO; + //未匹配到数据库类型,如:dbeaver支持自定义驱动等,但chat2DB暂不支持 + if (null != dataBaseType) { + //密码信息 + File credentials = new File(config + File.separator + ExportConstants.CONFIG_CREDENTIALS_FILE); + DefaultValueEncryptor defaultValueEncryptor = new DefaultValueEncryptor(DefaultValueEncryptor.getLocalSecretKey()); + JSONObject credentialsJson = JSON.parseObject(defaultValueEncryptor.decryptValue(Files.readAllBytes(credentials.toPath()))); + dataSourceDO = new DataSourceDO(); + LocalDateTime dateTime = LocalDateTime.now(); + dataSourceDO.setGmtCreate(dateTime); + dataSourceDO.setGmtModified(dateTime); + dataSourceDO.setAlias(configurations.getString("name")); + dataSourceDO.setHost(configuration.getString("host")); + dataSourceDO.setPort(configuration.getString("port")); + dataSourceDO.setUrl(configuration.getString("url")); + if (null != credentialsJson) { + JSONObject userInfo = credentialsJson.getJSONObject(key); + JSONObject userPassword = userInfo.getJSONObject(connection); + dataSourceDO.setUserName(userPassword.getString("user")); + DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); + String password = userPassword.getString("password"); + String encryptStr = desUtil.encrypt(Optional.ofNullable(password).orElse(""), "CBC"); + dataSourceDO.setPassword(encryptStr); + } + dataSourceDO.setType(dataBaseType.name()); + dataSourceMapper.insert(dataSourceDO); + } } } } } //删除临时文件 FileUtils.delete(file); - //删除dbeaver存留的配置临时文件 + //删除导入dbeaver时,dbp产生的临时配置文件 //projects.forEach(v -> FileUtils.delete(new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + v))); return vo; } @@ -212,13 +252,10 @@ private static void importDbeaverConfig(File resource, Element resourceElement, if (!folder.exists()) { FileUtil.mkdir(folder); } - resource = folder; importDbeaverConfig(folder, childElement, entryPath + "/", zipFile); } else { File file = new File(resource.getPath() + File.separator + childName); - if (!file.exists()) { - FileUtil.writeFromStream(zipFile.getInputStream(resourceEntry), file); - } + FileUtil.writeFromStream(zipFile.getInputStream(resourceEntry), file, true); } } } From ff5862167847ddcf5928191e218bea40dedd9d15 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 26 Sep 2023 11:46:31 +0800 Subject: [PATCH 0809/1069] fix:create table to edit table --- .../src/blocks/DatabaseTableEditor/index.tsx | 25 +++++++--- .../components/WorkspaceRightNew/index.tsx | 48 +++++++++++-------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 24696f70d..9e425762c 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -7,7 +7,7 @@ import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams, IExecuteSqlParams } from '@/service/sql'; import MonacoEditor from '@/components/Console/MonacoEditor'; -import { IEditTableInfo } from '@/typings'; +import { IEditTableInfo, IWorkspaceTab } from '@/typings'; import i18n from '@/i18n'; import lodash from 'lodash'; @@ -16,6 +16,8 @@ interface IProps { databaseName: string; schemaName: string | undefined; tableName?: string; + changeTabDetails: (data: IWorkspaceTab) => void; + tabDetails: IWorkspaceTab; } interface ITabItem { @@ -35,11 +37,10 @@ interface IContext extends IProps { export const Context = createContext({} as any); export default memo((props: IProps) => { - const { databaseName, dataSourceId, tableName, schemaName } = props; + const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails } = props; const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); const [viewSqlModal, setViewSqlModal] = useState(false); - const [newTableName, setNewTableName] = useState(tableName); const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); @@ -78,11 +79,11 @@ export default memo((props: IProps) => { }, []); const getTableDetails = () => { - if (!newTableName) return; + if (!tableName) return; const params = { databaseName, dataSourceId, - tableName: newTableName, + tableName, schemaName, refresh: true, }; @@ -110,12 +111,11 @@ export default memo((props: IProps) => { newTable, }; - if (newTableName) { + if (tableName) { // params.tableName = tableName; params.oldTable = oldTableDetails; } sqlService.getModifyTableSql(params).then((res) => { - setNewTableName(newTable.name); setViewSqlModal(res?.[0].sql); }); } @@ -130,6 +130,17 @@ export default memo((props: IProps) => { }; sqlService.executeSql(executeSQLParams).then(() => { + if (!tableName) { + const newTableName = baseInfoRef.current?.getBaseInfo().name; + changeTabDetails({ + ...tabDetails, + title: `edit-${newTableName}`, + uniqueData: { + ...(tabDetails.uniqueData || {}), + tableName: newTableName, + }, + }); + } message.success(i18n('common.text.successfulExecution')); getTableDetails(); setViewSqlModal(false); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index 1ab7e148f..f98f2f30c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -30,14 +30,8 @@ const WorkspaceRight = memo((props: IProps) => { // 工作台tab列表 const [workspaceTabList, setWorkspaceTabList] = useState([]); - const { - curWorkspaceParams, - doubleClickTreeNodeData, - createTabIntro, - openConsoleList, - curConsoleId, - createConsoleIntro, - } = workspaceModel; + const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, createConsoleIntro } = + workspaceModel; // 根据保存的console列表生成tab列表 useEffect(() => { @@ -480,6 +474,16 @@ const WorkspaceRight = memo((props: IProps) => { }); } + const changeTabDetails = (data: IWorkspaceTab) => { + const list = workspaceTabList.map((t) => { + if (t.id === data.id) { + return data; + } + return t; + }); + setWorkspaceTabList(list); + }; + const tabsList = useMemo(() => { return workspaceTabList.map((t) => { const { uniqueData } = t; @@ -497,21 +501,23 @@ const WorkspaceRight = memo((props: IProps) => { WorkspaceTabType.TRIGGER, WorkspaceTabType.VIEW, ].includes(t.type) && ( - - )} + + )} {t.type === WorkspaceTabType.EditTable && ( Date: Tue, 26 Sep 2023 12:10:03 +0800 Subject: [PATCH 0810/1069] refactor model name --- .../server/web/api/controller/ai/ChatController.java | 6 +++--- .../ai/azure/client/AzureOpenAiStreamClient.java | 4 ++-- .../listener/AzureOpenAIEventSourceListener.java | 11 +++++------ .../ai/azure/{models => model}/AzureChatChoice.java | 2 +- .../azure/{models => model}/AzureChatCompletions.java | 2 +- .../AzureChatCompletionsOptions.java | 3 +-- .../ai/azure/{models => model}/AzureChatMessage.java | 2 +- .../ai/azure/{models => model}/AzureChatRole.java | 2 +- .../ai/azure/{models => model}/AzureChoice.java | 2 +- .../ai/azure/{models => model}/AzureCompletions.java | 2 +- .../AzureCompletionsFinishReason.java | 2 +- .../AzureCompletionsLogProbabilityModel.java | 2 +- .../{models => model}/AzureCompletionsUsage.java | 2 +- .../{models => model}/AzureExpandableStringEnum.java | 2 +- 14 files changed, 21 insertions(+), 23 deletions(-) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/{ => azure}/listener/AzureOpenAIEventSourceListener.java (91%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureChatChoice.java (97%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureChatCompletions.java (97%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureChatCompletionsOptions.java (99%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureChatMessage.java (95%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureChatRole.java (95%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureChoice.java (97%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureCompletions.java (97%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureCompletionsFinishReason.java (96%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureCompletionsLogProbabilityModel.java (98%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureCompletionsUsage.java (97%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/{models => model}/AzureExpandableStringEnum.java (98%) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 89ceb821b..8764ec7f9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -21,8 +21,8 @@ import ai.chat2db.server.tools.common.util.EasyEnumUtils; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatRole; +import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; +import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatRole; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; import ai.chat2db.server.web.api.controller.ai.claude.client.ClaudeAIClient; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatCompletionsOptions; @@ -30,7 +30,7 @@ import ai.chat2db.server.web.api.controller.ai.config.LocalCache; import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; -import ai.chat2db.server.web.api.controller.ai.listener.AzureOpenAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.azure.listener.AzureOpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.listener.ClaudeAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.listener.OpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.listener.RestAIEventSourceListener; 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 027ffce92..e232936b7 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 @@ -6,8 +6,8 @@ import ai.chat2db.server.tools.common.exception.ParamBusinessException; import ai.chat2db.server.web.api.controller.ai.azure.interceptor.AzureHeaderAuthorizationInterceptor; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatCompletionsOptions; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage; +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.ObjectMapper; import lombok.Getter; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java similarity index 91% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java index 81d9e329a..4488bd6b8 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/AzureOpenAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/listener/AzureOpenAIEventSourceListener.java @@ -1,13 +1,12 @@ -package ai.chat2db.server.web.api.controller.ai.listener; +package ai.chat2db.server.web.api.controller.ai.azure.listener; import java.io.IOException; import java.util.Objects; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatChoice; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatCompletions; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage; -import ai.chat2db.server.web.api.controller.ai.azure.models.AzureCompletionsUsage; -import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse; +import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatChoice; +import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletions; +import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage; +import ai.chat2db.server.web.api.controller.ai.azure.model.AzureCompletionsUsage; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.unfbx.chatgpt.entity.chat.Message; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java similarity index 97% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatChoice.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java index 531bfefff..e1cc20a61 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatChoice.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatChoice.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java similarity index 97% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatCompletions.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java index db849909b..f7e8356f7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatCompletions.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletions.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java similarity index 99% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatCompletionsOptions.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java index 0b268721b..1d6198e57 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatCompletionsOptions.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatCompletionsOptions.java @@ -1,10 +1,9 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; -import java.util.Map; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java similarity index 95% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatMessage.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java index 05e651df0..6ea6b8fee 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatMessage.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatMessage.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatRole.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java similarity index 95% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatRole.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java index e28d9a6b1..7b2d3c12e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChatRole.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChatRole.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.Collection; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java similarity index 97% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChoice.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java index 141a48fc1..060929ae7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureChoice.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureChoice.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java similarity index 97% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletions.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java index 4af232d34..b32fb5e98 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletions.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletions.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsFinishReason.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java similarity index 96% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsFinishReason.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java index 992bd4c61..144fe2b76 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsFinishReason.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsFinishReason.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.Collection; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsLogProbabilityModel.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java similarity index 98% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsLogProbabilityModel.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java index 7ce845480..dba305fc9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsLogProbabilityModel.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsLogProbabilityModel.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.util.List; import java.util.Map; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsUsage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java similarity index 97% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsUsage.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java index 2130a5f53..0d830ad91 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureCompletionsUsage.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureCompletionsUsage.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // Code generated by Microsoft (R) AutoRest Code Generator. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureExpandableStringEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java similarity index 98% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureExpandableStringEnum.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java index d67ed2d57..57eb97e23 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/models/AzureExpandableStringEnum.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/azure/model/AzureExpandableStringEnum.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package ai.chat2db.server.web.api.controller.ai.azure.models; +package ai.chat2db.server.web.api.controller.ai.azure.model; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; From 6f100a65b1f6b4a8ddc5e16b5acfc1d8e56d873b Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 26 Sep 2023 12:23:27 +0800 Subject: [PATCH 0811/1069] refactor model name --- .../server/web/api/controller/ai/ChatController.java | 8 ++++---- .../listener/ClaudeAIEventSourceListener.java | 2 +- .../ai/openai/client}/OpenAIClient.java | 3 ++- .../{ => openai}/listener/OpenAIEventSourceListener.java | 2 +- .../ai/{ => rest}/listener/RestAIEventSourceListener.java | 2 +- .../web/api/controller/config/ConfigController.java | 2 +- 6 files changed, 10 insertions(+), 9 deletions(-) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/{ => claude}/listener/ClaudeAIEventSourceListener.java (98%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/{util => controller/ai/openai/client}/OpenAIClient.java (96%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/{ => openai}/listener/OpenAIEventSourceListener.java (98%) rename chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/{ => rest}/listener/RestAIEventSourceListener.java (98%) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 8764ec7f9..5127f405e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -31,14 +31,14 @@ import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter; import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.azure.listener.AzureOpenAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.listener.ClaudeAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.listener.OpenAIEventSourceListener; -import ai.chat2db.server.web.api.controller.ai.listener.RestAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.claude.listener.ClaudeAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.openai.listener.OpenAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.rest.listener.RestAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.controller.ai.request.ChatRequest; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.util.ApplicationContextUtil; -import ai.chat2db.server.web.api.util.OpenAIClient; +import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; import ai.chat2db.spi.model.TableColumn; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java similarity index 98% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java index c6401867b..f3570fa6c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/ClaudeAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/claude/listener/ClaudeAIEventSourceListener.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.listener; +package ai.chat2db.server.web.api.controller.ai.claude.listener; import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeCompletionResponse; import com.fasterxml.jackson.databind.DeserializationFeature; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java similarity index 96% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java index 3a076074c..9ebf711c2 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/util/OpenAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/client/OpenAIClient.java @@ -1,5 +1,5 @@ -package ai.chat2db.server.web.api.util; +package ai.chat2db.server.web.api.controller.ai.openai.client; import java.net.InetSocketAddress; import java.net.Proxy; @@ -8,6 +8,7 @@ import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.service.ConfigService; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; import com.google.common.collect.Lists; import com.unfbx.chatgpt.OpenAiStreamClient; import com.unfbx.chatgpt.constant.OpenAIConst; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/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 similarity index 98% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/OpenAIEventSourceListener.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/openai/listener/OpenAIEventSourceListener.java index 3da02f8ea..2f0bcef01 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/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 @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.listener; +package ai.chat2db.server.web.api.controller.ai.openai.listener; import java.util.Objects; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java similarity index 98% rename from chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java rename to chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java index 49174a899..bb0e12caf 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/listener/RestAIEventSourceListener.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/rest/listener/RestAIEventSourceListener.java @@ -1,4 +1,4 @@ -package ai.chat2db.server.web.api.controller.ai.listener; +package ai.chat2db.server.web.api.controller.ai.rest.listener; import java.util.Objects; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index bc57e53d1..985db1278 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -17,7 +17,7 @@ import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.controller.config.request.AIConfigCreateRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; -import ai.chat2db.server.web.api.util.OpenAIClient; +import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; From b75920cafc3c19d31148fd57410bbe6593e63f4d Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Tue, 26 Sep 2023 12:38:32 +0800 Subject: [PATCH 0812/1069] =?UTF-8?q?dbeaver=E5=AF=BC=E5=85=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/controller/ncx/service/impl/ConverterServiceImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index 0de1bc69c..66dd04163 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -233,7 +233,7 @@ public UploadVO dbpUploadFile(File file) { //删除临时文件 FileUtils.delete(file); //删除导入dbeaver时,dbp产生的临时配置文件 - //projects.forEach(v -> FileUtils.delete(new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + v))); + projects.forEach(v -> FileUtils.delete(new File(ConfigUtils.CONFIG_BASE_PATH + File.separator + v))); return vo; } From 90a93d21aee0afeb65778f3dce5016ca56537618 Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Tue, 26 Sep 2023 14:48:39 +0800 Subject: [PATCH 0813/1069] =?UTF-8?q?dbeaver=E5=AF=BC=E5=85=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ncx/service/impl/ConverterServiceImpl.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index 879be3b9c..5213cfc52 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -36,15 +36,14 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; @@ -208,7 +207,7 @@ public UploadVO dbpUploadFile(File file) { DefaultValueEncryptor defaultValueEncryptor = new DefaultValueEncryptor(DefaultValueEncryptor.getLocalSecretKey()); JSONObject credentialsJson = JSON.parseObject(defaultValueEncryptor.decryptValue(Files.readAllBytes(credentials.toPath()))); dataSourceDO = new DataSourceDO(); - LocalDateTime dateTime = LocalDateTime.now(); + Date dateTime = new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(configurations.getString("name")); From 8f02f1159a890c0a254a0a1f6382c647161daeb7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 26 Sep 2023 16:28:16 +0800 Subject: [PATCH 0814/1069] style:execute result --- .../RealTimeSQL/index.less | 4 + .../DatabaseTableEditor/RealTimeSQL/index.tsx | 17 +++ .../src/blocks/DatabaseTableEditor/index.less | 87 +++++++++++- .../src/blocks/DatabaseTableEditor/index.tsx | 126 +++++++++++++----- .../src/components/Iconfont/index.tsx | 6 +- .../components/WorkspaceRightNew/index.tsx | 1 + chat2db-client/src/utils/index.ts | 4 +- 7 files changed, 207 insertions(+), 38 deletions(-) create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.less create mode 100644 chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.tsx diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.less new file mode 100644 index 000000000..c013c45a5 --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.less @@ -0,0 +1,4 @@ +@import '../../../styles/var.less'; + +.realTimeSQL { +} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.tsx new file mode 100644 index 000000000..e5e4005ab --- /dev/null +++ b/chat2db-client/src/blocks/DatabaseTableEditor/RealTimeSQL/index.tsx @@ -0,0 +1,17 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; + +interface IProps { + className?: string; +} + +export default memo((props) => { + const { className } = props; + return ( +
    +
    实时 SQL
    +
    实时 SQL
    +
    + ); +}); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index 1a5466661..617560a52 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -79,6 +79,91 @@ opacity: 0; } -.monacoEditor { +.monacoEditorModal { + display: flex; height: 400px; + border: 1px solid var(--color-border); + border-radius: 4px; + + .monacoEditorHeader { + height: 38px; + padding: 0px 10px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--color-border); + .formatButton { + border-radius: 3px; + background-color: var(--color-fill-quaternary); + height: 24px; + padding: 0px 7px; + cursor: pointer; + i { + margin-right: 6px; + } + &:hover { + color: var(--color-primary); + } + } + .executeButton { + height: 24px; + line-height: 24px; + display: flex; + justify-content: center; + align-items: center; + i { + margin-right: 4px; + } + } + } + .monacoEditorContent { + width: 0px; + flex: 1; + display: flex; + flex-direction: column; + padding: 0px -6px; + border-right: 1px solid var(--color-border); + .monacoEditor { + flex: 1; + margin: 0px 6px; + } + } + .result { + width: 0px; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .resultHeader { + width: 100%; + line-height: 38px; + text-align: center; + border-bottom: 1px solid var(--color-border); + } + .resultContent { + flex: 1; + padding: 0px 10px; + overflow-y: auto; + .errorTitle { + display: flex; + align-items: center; + padding: 4px 10px; + // background-color: var(--color-bg-subtle); + border-radius: 8px; + margin-top: 10px; + i { + color: var(--color-error); + margin-right: 4px; + } + } + + .errorMessage { + margin: 10px 0px; + padding: 4px 10px; + background-color: var(--color-bg-subtle); + border-radius: 8px; + } + } + } } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 9e425762c..03f0cb8cc 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -6,16 +6,20 @@ import IndexList, { IIndexListRef } from './IndexList'; import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams, IExecuteSqlParams } from '@/service/sql'; -import MonacoEditor from '@/components/Console/MonacoEditor'; -import { IEditTableInfo, IWorkspaceTab } from '@/typings'; +import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; +import { IEditTableInfo, IWorkspaceTab, IManageResultData } from '@/typings'; +import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; +import Iconfont from '@/components/Iconfont'; +import { formatSql } from '@/utils'; interface IProps { dataSourceId: number; databaseName: string; schemaName: string | undefined; tableName?: string; + databaseType: DatabaseTypeCode; changeTabDetails: (data: IWorkspaceTab) => void; tabDetails: IWorkspaceTab; } @@ -37,13 +41,17 @@ interface IContext extends IProps { export const Context = createContext({} as any); export default memo((props: IProps) => { - const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails } = props; + const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails, databaseType } = props; const [tableDetails, setTableDetails] = useState({} as any); const [oldTableDetails, setOldTableDetails] = useState({} as any); - const [viewSqlModal, setViewSqlModal] = useState(false); + const [viewSqlModal, setViewSqlModal] = useState(false); const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); + const monacoEditorRef = useRef(null); + const [executeSqlResult, setExecuteSqlResult] = useState(); + const [executeLoading, setExecuteLoading] = useState(false); + const [appendValue, setAppendValue] = useState(''); const tabList = useMemo(() => { return [ { @@ -116,34 +124,65 @@ export default memo((props: IProps) => { params.oldTable = oldTableDetails; } sqlService.getModifyTableSql(params).then((res) => { - setViewSqlModal(res?.[0].sql); + setViewSqlModal(true); + setAppendValue(res?.[0].sql); }); } } const executeSql = () => { const executeSQLParams: IExecuteSqlParams = { - sql: viewSqlModal || '', + sql: monacoEditorRef.current?.getAllContent() || '', dataSourceId, databaseName, schemaName, }; + setExecuteLoading(true); + sqlService + .executeSql(executeSQLParams) + .then((res) => { + if (!tableName) { + const newTableName = baseInfoRef.current?.getBaseInfo().name; + changeTabDetails({ + ...tabDetails, + title: `edit-${newTableName}`, + uniqueData: { + ...(tabDetails.uniqueData || {}), + tableName: newTableName, + }, + }); + } + if (res.filter((t) => !t.success).length === 0) { + setViewSqlModal(false); + message.success(i18n('common.text.successfulExecution')); + } + setExecuteSqlResult(res); + getTableDetails(); + }) + .finally(() => { + setExecuteLoading(false); + }); + }; - sqlService.executeSql(executeSQLParams).then(() => { - if (!tableName) { - const newTableName = baseInfoRef.current?.getBaseInfo().name; - changeTabDetails({ - ...tabDetails, - title: `edit-${newTableName}`, - uniqueData: { - ...(tabDetails.uniqueData || {}), - tableName: newTableName, - }, - }); - } - message.success(i18n('common.text.successfulExecution')); - getTableDetails(); - setViewSqlModal(false); + // + const renderMonacoEditor = useMemo(() => { + return ( + + ); + }, [appendValue]); + + const handleFormatSql = () => { + const sql = monacoEditorRef.current?.getAllContent() || ''; + formatSql(sql, databaseType).then((res) => { + setAppendValue(res); }); }; @@ -194,19 +233,44 @@ export default memo((props: IProps) => { onCancel={() => { setViewSqlModal(false); }} - okText="执行" - onOk={executeSql} width="60vw" maskClosable={false} + footer={false} > -
    - +
    +
    +
    +
    + + {i18n('common.button.format')} +
    + +
    + {renderMonacoEditor} +
    + {executeSqlResult && ( +
    +
    错误信息
    +
    + {executeSqlResult + ?.filter((t) => !t.success) + .map((t, i) => { + return ( +
    +
    + + 执行错误sql{i + 1}:{t.sql} +
    +
    {t.message}
    +
    + ); + })} +
    +
    + )}
    diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 7292796dd..ff64a0aab 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_m8g5g53rp.woff2?t=1695636919643') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_m8g5g53rp.woff?t=1695636919643') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_m8g5g53rp.ttf?t=1695636919643') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_rlj1v1fcjnk.woff2?t=1695716733221') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_rlj1v1fcjnk.woff?t=1695716733221') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_rlj1v1fcjnk.ttf?t=1695716733221') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index f98f2f30c..775710add 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -520,6 +520,7 @@ const WorkspaceRight = memo((props: IProps) => { changeTabDetails={changeTabDetails} dataSourceId={curWorkspaceParams.dataSourceId} databaseName={curWorkspaceParams.databaseName!} + databaseType={curWorkspaceParams?.databaseType} schemaName={curWorkspaceParams?.schemaName!} tableName={uniqueData.tableName} /> diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 0bf77fdd9..87b17ce32 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -239,9 +239,7 @@ export function formatSql(sql: string, dbType: DatabaseTypeCode) { try { formatRes = format(sql || ''); } - catch { - - } + catch {} // 如果格式化失败,直接返回原始sql if (!formatRes) { sqlServer.sqlFormat({ From 6df5d8cd4f1a0544404f512a7951c1befb971f80 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 26 Sep 2023 16:31:35 +0800 Subject: [PATCH 0815/1069] i18n --- .../src/blocks/DatabaseTableEditor/ColumnList/index.tsx | 2 +- chat2db-client/src/blocks/DatabaseTableEditor/index.tsx | 6 +++--- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/zh-cn/common.ts | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 074c0ad09..6b8a304c0 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -417,7 +417,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const renderOtherInfoForm = () => { const labelCol = { - style: { width: 90 }, + style: { width: 100 }, }; return ( diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 03f0cb8cc..dea772f87 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -246,14 +246,14 @@ export default memo((props: IProps) => {
    {renderMonacoEditor}
    {executeSqlResult && (
    -
    错误信息
    +
    {i18n('common.text.errorMessage')}
    {executeSqlResult ?.filter((t) => !t.success) @@ -262,7 +262,7 @@ export default memo((props: IProps) => {
    - 执行错误sql{i + 1}:{t.sql} + sql{i + 1}:{t.sql}
    {t.message}
    diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 621da4067..4af4fccaf 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -86,5 +86,6 @@ export default { 'common.tips.createSuccess': 'Create Successfully', 'common.text.action': 'Action', 'common.button.add': 'Add', + 'common.text.errorMessage': 'Error Message', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index f52e48250..88948ddd8 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -84,5 +84,6 @@ export default { 'common.tips.createSuccess': '创建成功', 'common.text.action': '操作', 'common.button.add': '添加', + 'common.text.errorMessage': '错误信息', }; From eea4f4850864e57edb579ecc8ed8d64d5e042cf6 Mon Sep 17 00:00:00 2001 From: lzy <963565242@qq.com> Date: Tue, 26 Sep 2023 16:33:43 +0800 Subject: [PATCH 0816/1069] =?UTF-8?q?=E5=AF=BC=E5=85=A5=E9=93=BE=E6=8E=A5?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0userId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ncx/service/impl/ConverterServiceImpl.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java index 5213cfc52..9f3cd6aaa 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ncx/service/impl/ConverterServiceImpl.java @@ -4,6 +4,7 @@ import ai.chat2db.server.domain.repository.entity.DataSourceDO; import ai.chat2db.server.domain.repository.mapper.DataSourceMapper; import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.tools.common.util.ContextUtils; import ai.chat2db.server.web.api.controller.ncx.cipher.CommonCipher; import ai.chat2db.server.web.api.controller.ncx.dbeaver.DefaultValueEncryptor; import ai.chat2db.server.web.api.controller.ncx.enums.DataBaseType; @@ -210,10 +211,16 @@ public UploadVO dbpUploadFile(File file) { Date dateTime = new Date(); dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); + //插入用户id + dataSourceDO.setUserId(ContextUtils.getUserId()); dataSourceDO.setAlias(configurations.getString("name")); dataSourceDO.setHost(configuration.getString("host")); dataSourceDO.setPort(configuration.getString("port")); dataSourceDO.setUrl(configuration.getString("url")); + //ssh设置为false + SSHInfo sshInfo = new SSHInfo(); + sshInfo.setUse(false); + dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); if (null != credentialsJson) { JSONObject userInfo = credentialsJson.getJSONObject(key); JSONObject userPassword = userInfo.getJSONObject(connection); @@ -291,6 +298,8 @@ public UploadVO datagripUploadFile(String text) { dataSourceDO.setGmtCreate(dateTime); dataSourceDO.setGmtModified(dateTime); dataSourceDO.setAlias(rootElement.getAttribute("name")); + //插入用户id + dataSourceDO.setUserId(ContextUtils.getUserId()); // 获取子元素 database-info Element databaseInfoElement = (Element) rootElement.getElementsByTagName("database-info").item(0); @@ -319,6 +328,10 @@ public UploadVO datagripUploadFile(String text) { } } + //ssh设置为false + SSHInfo sshInfo = new SSHInfo(); + sshInfo.setUse(false); + dataSourceDO.setSsh(JSON.toJSONString(sshInfo)); dataSourceDO.setHost(host); dataSourceDO.setPort(port); dataSourceDO.setUrl(jdbcUrl); @@ -361,6 +374,8 @@ public void insertDBConfig(List>> list) { dataSourceDO.setAlias(resultMap.get("ConnectionName")); dataSourceDO.setUserName(resultMap.get("UserName")); dataSourceDO.setType(resultMap.get("ConnType")); + //插入用户id + dataSourceDO.setUserId(ContextUtils.getUserId()); //password 为解密出来的密文,再使用chat2db的加密 DesUtil desUtil = new DesUtil(DesUtil.DES_KEY); String encryptStr = desUtil.encrypt(password, "CBC"); From 7d69f11286c6337f9fd82662b47068a368007ed5 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 26 Sep 2023 20:18:30 +0800 Subject: [PATCH 0817/1069] add common ai support --- .../domain/api/enums/AiSqlSourceEnum.java | 5 + .../web/api/controller/ai/ChatController.java | 36 ++++ .../ai/fastchat/client/FastChatAIClient.java | 75 +++++++ .../client/FastChatAIStreamClient.java | 187 ++++++++++++++++++ ...astChatHeaderAuthorizationInterceptor.java | 37 ++++ .../FastChatAIEventSourceListener.java | 148 ++++++++++++++ .../ai/fastchat/model/FastChatChoice.java | 97 +++++++++ .../fastchat/model/FastChatCompletions.java | 110 +++++++++++ .../FastChatCompletionsFinishReason.java | 51 +++++ .../model/FastChatCompletionsOptions.java | 92 +++++++++ .../model/FastChatCompletionsUsage.java | 78 ++++++++ .../model/FastChatExpandableStringEnum.java | 147 ++++++++++++++ .../ai/fastchat/model/FastChatMessage.java | 61 ++++++ .../ai/fastchat/model/FastChatRole.java | 46 +++++ .../controller/config/ConfigController.java | 179 +++-------------- 15 files changed, 1200 insertions(+), 149 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java index 522d57646..be6ee3f80 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java @@ -37,6 +37,11 @@ public enum AiSqlSourceEnum implements BaseEnum { */ CLAUDEAI("CLAUDE AI"), + /** + * FAST CHAT AI + */ + FASTCHATAI("FAST CHAT AI"), + ; final String description; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 5127f405e..d7d311878 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -32,6 +32,10 @@ import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.azure.listener.AzureOpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.claude.listener.ClaudeAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; +import ai.chat2db.server.web.api.controller.ai.fastchat.listener.FastChatAIEventSourceListener; +import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; +import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatRole; import ai.chat2db.server.web.api.controller.ai.openai.listener.OpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.rest.listener.RestAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; @@ -218,6 +222,8 @@ private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sse return chatWithAzureAi(queryRequest, sseEmitter, uid); case CLAUDEAI: return chatWithClaudeAi(queryRequest, sseEmitter, uid); + case FASTCHATAI: + return chatWithFastChatAi(queryRequest, sseEmitter, uid); } return chatWithOpenAi(queryRequest, sseEmitter, uid); } @@ -332,6 +338,36 @@ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sse return sseEmitter; } + /** + * chat with fast chat openai + * + * @param queryRequest + * @param sseEmitter + * @param uid + * @return + * @throws IOException + */ + private SseEmitter chatWithFastChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { + String prompt = buildPrompt(queryRequest); + List messages = (List)LocalCache.CACHE.get(uid); + if (CollectionUtils.isNotEmpty(messages)) { + if (messages.size() >= contextLength) { + messages = messages.subList(1, contextLength); + } + } else { + messages = Lists.newArrayList(); + } + FastChatMessage currentMessage = new FastChatMessage(FastChatRole.USER).setContent(prompt); + messages.add(currentMessage); + + buildSseEmitter(sseEmitter, uid); + + FastChatAIEventSourceListener sourceListener = new FastChatAIEventSourceListener(sseEmitter); + FastChatAIClient.getInstance().streamCompletions(messages, sourceListener); + LocalCache.CACHE.put(uid, messages, LocalCache.TIMEOUT); + return sseEmitter; + } + /** * chat with claude ai diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java new file mode 100644 index 000000000..710309901 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java @@ -0,0 +1,75 @@ + +package ai.chat2db.server.web.api.controller.ai.fastchat.client; + +import ai.chat2db.server.domain.api.model.Config; +import ai.chat2db.server.domain.api.service.ConfigService; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * @author moji + * @date 23/09/26 + */ +@Slf4j +public class FastChatAIClient { + + /** + * FASTCHAT OPENAI KEY + */ + public static final String FASTCHAT_API_KEY = "fastchat.chatgpt.apiKey"; + + /** + * FASTCHAT OPENAI HOST + */ + public static final String FASTCHAT_HOST = "fastchat.host"; + + /** + * FASTCHAT OPENAI model + */ + public static final String FASTCHAT_MODEL= "fastchat.model"; + + private static FastChatAIStreamClient FASTCHAT_AI_CLIENT; + + + public static FastChatAIStreamClient getInstance() { + if (FASTCHAT_AI_CLIENT != null) { + return FASTCHAT_AI_CLIENT; + } else { + return singleton(); + } + } + + private static FastChatAIStreamClient singleton() { + if (FASTCHAT_AI_CLIENT == null) { + synchronized (FastChatAIClient.class) { + if (FASTCHAT_AI_CLIENT == null) { + refresh(); + } + } + } + return FASTCHAT_AI_CLIENT; + } + + public static void refresh() { + String apiKey = ""; + String apiHost = ""; + String model = ""; + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config apiHostConfig = configService.find(FASTCHAT_HOST).getData(); + if (apiHostConfig != null && StringUtils.isNotBlank(apiHostConfig.getContent())) { + apiHost = apiHostConfig.getContent(); + } + Config config = configService.find(FASTCHAT_API_KEY).getData(); + if (config != null && StringUtils.isNotBlank(config.getContent())) { + apiKey = config.getContent(); + } + Config deployConfig = configService.find(FASTCHAT_MODEL).getData(); + if (deployConfig != null && StringUtils.isNotBlank(deployConfig.getContent())) { + model = deployConfig.getContent(); + } + FASTCHAT_AI_CLIENT = FastChatAIStreamClient.builder().apiKey(apiKey).apiHost(apiHost).model(model) + .build(); + } + +} 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 new file mode 100644 index 000000000..0e5dd475a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIStreamClient.java @@ -0,0 +1,187 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.client; + +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; +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.ObjectMapper; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import okhttp3.sse.EventSources; +import org.apache.commons.collections4.CollectionUtils; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +/** + * Fast Chat Aligned Client + * + * @author moji + */ +@Slf4j +public class FastChatAIStreamClient { + + /** + * apikey + */ + @Getter + @NotNull + private String apiKey; + + /** + * apiHost + */ + @Getter + @NotNull + private String apiHost; + + /** + * model + */ + @Getter + private String model; + + /** + * okHttpClient + */ + @Getter + private OkHttpClient okHttpClient; + + + /** + * @param builder + */ + private FastChatAIStreamClient(Builder builder) { + this.apiKey = builder.apiKey; + this.apiHost = builder.apiHost; + this.model = builder.model; + if (Objects.isNull(builder.okHttpClient)) { + builder.okHttpClient = this.okHttpClient(); + } + okHttpClient = builder.okHttpClient; + } + + /** + * okhttpclient + */ + private OkHttpClient okHttpClient() { + OkHttpClient okHttpClient = new OkHttpClient + .Builder() + .addInterceptor(new FastChatHeaderAuthorizationInterceptor(this.apiKey)) + .connectTimeout(10, TimeUnit.SECONDS) + .writeTimeout(50, TimeUnit.SECONDS) + .readTimeout(50, TimeUnit.SECONDS) + .build(); + return okHttpClient; + } + + /** + * 构造 + * + * @return + */ + public static FastChatAIStreamClient.Builder builder() { + return new FastChatAIStreamClient.Builder(); + } + + /** + * builder + */ + public static final class Builder { + private String apiKey; + + private String apiHost; + + private String model; + + /** + * OkhttpClient + */ + private OkHttpClient okHttpClient; + + public Builder() { + } + + public FastChatAIStreamClient.Builder apiKey(String apiKeyValue) { + this.apiKey = apiKeyValue; + return this; + } + + /** + * @param apiHostValue + * @return + */ + public FastChatAIStreamClient.Builder apiHost(String apiHostValue) { + this.apiHost = apiHostValue; + return this; + } + + /** + * @param modelValue + * @return + */ + public FastChatAIStreamClient.Builder model(String modelValue) { + this.model = modelValue; + return this; + } + + public FastChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { + this.okHttpClient = val; + return this; + } + + public FastChatAIStreamClient build() { + return new FastChatAIStreamClient(this); + } + + } + + /** + * 问答接口 stream 形式 + * + * @param chatMessages + * @param eventSourceListener + */ + public void streamCompletions(List chatMessages, EventSourceListener eventSourceListener) { + if (CollectionUtils.isEmpty(chatMessages)) { + log.error("param error:Fast Chat Prompt cannot be empty"); + throw new ParamBusinessException("prompt"); + } + if (Objects.isNull(eventSourceListener)) { + log.error("param error:FastChatEventSourceListener cannot be empty"); + throw new ParamBusinessException(); + } + log.info("Fast Chat AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent()); + try { + + FastChatCompletionsOptions chatCompletionsOptions = new FastChatCompletionsOptions(chatMessages); + chatCompletionsOptions.setStream(true); + chatCompletionsOptions.setModel(this.model); + + EventSource.Factory factory = EventSources.createFactory(this.okHttpClient); + ObjectMapper mapper = new ObjectMapper(); + String requestBody = mapper.writeValueAsString(chatCompletionsOptions); + Request request = new Request.Builder() + .url(apiHost) + .post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody)) + .build(); + //创建事件 + EventSource eventSource = factory.newEventSource(request, eventSourceListener); + log.info("finish invoking fast chat ai"); + } catch (Exception e) { + log.error("fast chat ai error", e); + eventSourceListener.onFailure(null, e, null); + throw new ParamBusinessException(); + } + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java new file mode 100644 index 000000000..3c4dc716b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java @@ -0,0 +1,37 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.interceptor; + +import cn.hutool.http.ContentType; +import cn.hutool.http.Header; +import lombok.Getter; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + +/** + * header apikey + * + * @author grt + * @since 2023-03-23 + */ +@Getter +public class FastChatHeaderAuthorizationInterceptor implements Interceptor { + + private String apiKey; + + public FastChatHeaderAuthorizationInterceptor(String apiKey) { + this.apiKey = apiKey; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Request original = chain.request(); + Request request = original.newBuilder() + .header("apiKey", apiKey) // replace to your corresponding field and value + .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) + .method(original.method(), original.body()) + .build(); + return chain.proceed(request); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java new file mode 100644 index 000000000..7ad62178f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/listener/FastChatAIEventSourceListener.java @@ -0,0 +1,148 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.listener; + +import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatChoice; +import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletions; +import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatCompletionsUsage; +import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.unfbx.chatgpt.entity.chat.Message; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.Objects; + +/** + * 描述:OpenAIEventSourceListener + * + * @author https:www.unfbx.com + * @date 2023-02-22 + */ +@Slf4j +public class FastChatAIEventSourceListener extends EventSourceListener { + + private SseEmitter sseEmitter; + + private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + public FastChatAIEventSourceListener(SseEmitter sseEmitter) { + this.sseEmitter = sseEmitter; + } + + /** + * {@inheritDoc} + */ + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("Fast Chat Sse connecting..."); + } + + /** + * {@inheritDoc} + */ + @SneakyThrows + @Override + public void onEvent(EventSource eventSource, String id, String type, String data) { + log.info("Fast Chat AI response data:{}", data); + if (data.equals("[DONE]")) { + log.info("Fast Chat AI closed"); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]") + .reconnectTime(3000)); + sseEmitter.complete(); + return; + } + + FastChatCompletions chatCompletions = mapper.readValue(data, FastChatCompletions.class); + String text = ""; + log.info("Model={} is created at {}.", chatCompletions.getId(), + chatCompletions.getCreated()); + for (FastChatChoice choice : chatCompletions.getChoices()) { + FastChatMessage message = choice.getMessage(); + if (message != null) { + log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole()); + if (message.getContent() != null) { + text = message.getContent(); + } + } + } + + FastChatCompletionsUsage usage = chatCompletions.getUsage(); + if (usage != null) { + log.info( + "Usage: number of prompt token is {}, number of completion token is {}, and number of total " + + "tokens in request and response is {}.%n", usage.getPromptTokens(), + usage.getCompletionTokens(), usage.getTotalTokens()); + } + + Message message = new Message(); + message.setContent(text); + sseEmitter.send(SseEmitter.event() + .id(null) + .data(message) + .reconnectTime(3000)); + } + + @Override + public void onClosed(EventSource eventSource) { + try { + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + } catch (IOException e) { + throw new RuntimeException(e); + } + sseEmitter.complete(); + log.info("FastChatAI close sse connection..."); + } + + @Override + public void onFailure(EventSource eventSource, Throwable t, Response response) { + try { + if (Objects.isNull(response)) { + String message = t.getMessage(); + Message sseMessage = new Message(); + sseMessage.setContent(message); + sseEmitter.send(SseEmitter.event() + .id("[ERROR]") + .data(sseMessage)); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + sseEmitter.complete(); + return; + } + ResponseBody body = response.body(); + String bodyString = Objects.nonNull(t) ? t.getMessage() : ""; + if (Objects.nonNull(body)) { + bodyString = body.string(); + if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) { + bodyString = t.getMessage(); + } + log.error("Fast Chat AI sse response:{}", bodyString); + } else { + log.error("Fast Chat AI sse response:{},error:{}", response, t); + } + eventSource.cancel(); + Message message = new Message(); + message.setContent("Fast Chat AI error:" + bodyString); + sseEmitter.send(SseEmitter.event() + .id("[ERROR]") + .data(message)); + sseEmitter.send(SseEmitter.event() + .id("[DONE]") + .data("[DONE]")); + sseEmitter.complete(); + } catch (Exception exception) { + log.error("Fast Chat AI send data error:", exception); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java new file mode 100644 index 000000000..5ac0c364f --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatChoice.java @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices + * are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of + * choices generated. + */ +@Data +public final class FastChatChoice { + + /* + * The generated text for a given completions prompt. + */ + @JsonProperty(value = "text") + private String text; + + /* + * The ordered index associated with this completions choice. + */ + @JsonProperty(value = "index") + private int index; + + /* + * The log probabilities model for tokens associated with this completions choice. + */ + @JsonProperty(value = "message") + private FastChatMessage message; + + /* + * Reason for finishing + */ + @JsonProperty(value = "finish_reason") + private FastChatCompletionsFinishReason finishReason; + + /** + * Creates an instance of Choice class. + * + * @param text the text value to set. + * @param index the index value to set. + * @param message the message value to set + * @param finishReason the finishReason value to set. + */ + @JsonCreator + private FastChatChoice( + @JsonProperty(value = "text") String text, + @JsonProperty(value = "index") int index, + @JsonProperty(value = "message") FastChatMessage message, + @JsonProperty(value = "finish_reason") FastChatCompletionsFinishReason finishReason) { + this.text = text; + this.index = index; + this.message = message; + this.finishReason = finishReason; + } + + /** + * Get the text property: The generated text for a given completions prompt. + * + * @return the text value. + */ + public String getText() { + return this.text; + } + + /** + * Get the index property: The ordered index associated with this completions choice. + * + * @return the index value. + */ + public int getIndex() { + return this.index; + } + + /** + * Get the logprobs property: The log probabilities model for tokens associated with this completions choice. + * + * @return the logprobs value. + */ + public FastChatMessage getMessage() { + return this.message; + } + + /** + * Get the finishReason property: Reason for finishing. + * + * @return the finishReason value. + */ + public FastChatCompletionsFinishReason getFinishReason() { + return this.finishReason; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java new file mode 100644 index 000000000..fa63281af --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletions.java @@ -0,0 +1,110 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +@Data +public class FastChatCompletions { + + /* + * A unique identifier associated with this chat completions response. + */ + private String id; + + /* + * The first timestamp associated with generation activity for this completions response, + * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + */ + private int created; + + /** + * model + */ + private String model; + + /** + * object + */ + private String object; + + /* + * The collection of completions choices associated with this completions response. + * Generally, `n` choices are generated per provided prompt with a default value of 1. + * Token limits and other settings may limit the number of choices generated. + */ + @JsonProperty(value = "choices") + private List choices; + + /* + * Usage information for tokens processed and generated as part of this completions operation. + */ + private FastChatCompletionsUsage usage; + + /** + * Creates an instance of ChatCompletions class. + * + * @param id the id value to set. + * @param created the created value to set. + * @param choices the choices value to set. + * @param usage the usage value to set. + */ + @JsonCreator + private FastChatCompletions( + @JsonProperty(value = "id") String id, + @JsonProperty(value = "created") int created, + @JsonProperty(value = "model") String model, + @JsonProperty(value = "object") String object, + @JsonProperty(value = "choices") List choices, + @JsonProperty(value = "usage") FastChatCompletionsUsage usage) { + this.id = id; + this.created = created; + this.model = model; + this.object = object; + this.choices = choices; + this.usage = usage; + } + + /** + * Get the id property: A unique identifier associated with this chat completions response. + * + * @return the id value. + */ + public String getId() { + return this.id; + } + + /** + * Get the created property: The first timestamp associated with generation activity for this completions response, + * represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970. + * + * @return the created value. + */ + public int getCreated() { + return this.created; + } + + /** + * Get the choices property: The collection of completions choices associated with this completions response. + * Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other + * settings may limit the number of choices generated. + * + * @return the choices value. + */ + public List getChoices() { + return this.choices; + } + + /** + * Get the usage property: Usage information for tokens processed and generated as part of this completions + * operation. + * + * @return the usage value. + */ + public FastChatCompletionsUsage getUsage() { + return this.usage; + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java new file mode 100644 index 000000000..b2c61795b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsFinishReason.java @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Collection; + +/** Representation of the manner in which a completions response concluded. */ +public final class FastChatCompletionsFinishReason extends FastChatExpandableStringEnum { + + /** Completions ended normally and reached its end of token generation. */ + public static final FastChatCompletionsFinishReason STOPPED = fromString("stopped"); + + /** Completions exhausted available token limits before generation could complete. */ + public static final FastChatCompletionsFinishReason TOKEN_LIMIT_REACHED = fromString("tokenLimitReached"); + + /** + * Completions generated a response that was identified as potentially sensitive per content moderation policies. + */ + public static final FastChatCompletionsFinishReason CONTENT_FILTERED = fromString("contentFiltered"); + + /** + * Creates a new instance of CompletionsFinishReason value. + * + * @deprecated Use the {@link #fromString(String)} factory method. + */ + @Deprecated + public FastChatCompletionsFinishReason() {} + + /** + * Creates or finds a CompletionsFinishReason from its string representation. + * + * @param name a name to look for. + * @return the corresponding CompletionsFinishReason. + */ + @JsonCreator + public static FastChatCompletionsFinishReason fromString(String name) { + return fromString(name, FastChatCompletionsFinishReason.class); + } + + /** + * Gets known CompletionsFinishReason values. + * + * @return known CompletionsFinishReason values. + */ + public static Collection values() { + return values(FastChatCompletionsFinishReason.class); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java new file mode 100644 index 000000000..a4e8a328e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsOptions.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.List; + +/** + * The configuration information for a chat completions request. Completions support a wide variety of tasks and + * generate text that continues from or "completes" provided prompt data. + */ +@Data +public final class FastChatCompletionsOptions { + + /* + * The collection of context messages associated with this chat completions request. + * Typical usage begins with a chat message for the System role that provides instructions for + * the behavior of the assistant, followed by alternating messages between the User and + * Assistant roles. + */ + private List messages; + + + /* + * A value indicating whether chat completions should be streamed for this request. + */ + private Boolean stream; + // + /* + * The model name to provide as part of this completions request. + * Not applicable to Fast Chat AI, where deployment information should be included in the Fast Chat + * resource URI that's connected to. + */ + private String model; + + /** + * Creates an instance of ChatCompletionsOptions class. + * + * @param messages the messages value to set. + */ + @JsonCreator + public FastChatCompletionsOptions(@JsonProperty(value = "messages") List messages) { + this.messages = messages; + } + + + /** + * Get the stream property: A value indicating whether chat completions should be streamed for this request. + * + * @return the stream value. + */ + public Boolean isStream() { + return this.stream; + } + + /** + * Set the stream property: A value indicating whether chat completions should be streamed for this request. + * + * @param stream the stream value to set. + * @return the ChatCompletionsOptions object itself. + */ + public FastChatCompletionsOptions setStream(Boolean stream) { + this.stream = stream; + return this; + } + + /** + * Get the model property: The model name to provide as part of this completions request. Not applicable to Fast Chat AI, + * where deployment information should be included in the Fast Chat AI resource URI that's connected to. + * + * @return the model value. + */ + public String getModel() { + return this.model; + } + + /** + * Set the model property: The model name to provide as part of this completions request. Not applicable to Fast Chat AI, + * where deployment information should be included in the Fast Chat AI resource URI that's connected to. + * + * @param model the model value to set. + * @return the ChatCompletionsOptions object itself. + */ + public FastChatCompletionsOptions setModel(String model) { + this.model = model; + return this; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java new file mode 100644 index 000000000..8091b79ab --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatCompletionsUsage.java @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) AutoRest Code Generator. +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * Representation of the token counts processed for a completions request. Counts consider all tokens across prompts, + * choices, choice alternates, best_of generations, and other consumers. + */ +@Data +public final class FastChatCompletionsUsage { + + /* + * The number of tokens generated across all completions emissions. + */ + @JsonProperty(value = "completion_tokens") + private int completionTokens; + + /* + * The number of tokens in the provided prompts for the completions request. + */ + @JsonProperty(value = "prompt_tokens") + private int promptTokens; + + /* + * The total number of tokens processed for the completions request and response. + */ + @JsonProperty(value = "total_tokens") + private int totalTokens; + + /** + * Creates an instance of CompletionsUsage class. + * + * @param completionTokens the completionTokens value to set. + * @param promptTokens the promptTokens value to set. + * @param totalTokens the totalTokens value to set. + */ + @JsonCreator + private FastChatCompletionsUsage( + @JsonProperty(value = "completion_tokens") int completionTokens, + @JsonProperty(value = "prompt_tokens") int promptTokens, + @JsonProperty(value = "total_tokens") int totalTokens) { + this.completionTokens = completionTokens; + this.promptTokens = promptTokens; + this.totalTokens = totalTokens; + } + + /** + * Get the completionTokens property: The number of tokens generated across all completions emissions. + * + * @return the completionTokens value. + */ + public int getCompletionTokens() { + return this.completionTokens; + } + + /** + * Get the promptTokens property: The number of tokens in the provided prompts for the completions request. + * + * @return the promptTokens value. + */ + public int getPromptTokens() { + return this.promptTokens; + } + + /** + * Get the totalTokens property: The total number of tokens processed for the completions request and response. + * + * @return the totalTokens value. + */ + public int getTotalTokens() { + return this.totalTokens; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java new file mode 100644 index 000000000..ffde27bb7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatExpandableStringEnum.java @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import ai.chat2db.server.web.api.controller.ai.azure.util.AzureReflectionUtils; +import com.fasterxml.jackson.annotation.JsonValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import static java.lang.invoke.MethodType.methodType; + +/** + * Base implementation for expandable, single string enums. + * + * @param a specific expandable enum type + */ +public abstract class FastChatExpandableStringEnum> { + private static final Map, MethodHandle> CONSTRUCTORS = new ConcurrentHashMap<>(); + private static final Map, ConcurrentHashMap>> VALUES + = new ConcurrentHashMap<>(); + + private static final Logger LOGGER = LoggerFactory.getLogger(FastChatExpandableStringEnum.class); + private String name; + private Class clazz; + + /** + * Creates a new instance of {@link FastChatExpandableStringEnum} without a {@link #toString()} value. + *

    + * This constructor shouldn't be called as it will produce a {@link FastChatExpandableStringEnum} which doesn't + * have a String enum value. + * + * @deprecated Use the {@link #fromString(String, Class)} factory method. + */ + @Deprecated + public FastChatExpandableStringEnum() { + } + + /** + * Creates an instance of the specific expandable string enum from a String. + * + * @param name The value to create the instance from. + * @param clazz The class of the expandable string enum. + * @param the class of the expandable string enum. + * @return The expandable string enum instance. + * + * @throws RuntimeException wrapping implementation class constructor exception (if any is thrown). + */ + @SuppressWarnings({"unchecked", "deprecation"}) + protected static > T fromString(String name, Class clazz) { + if (name == null) { + return null; + } + + ConcurrentHashMap clazzValues = VALUES.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>()); + T value = (T) clazzValues.get(name); + + if (value != null) { + return value; + } else { + MethodHandle ctor = CONSTRUCTORS.computeIfAbsent(clazz, FastChatExpandableStringEnum::getDefaultConstructor); + + if (ctor == null) { + // logged in ExpandableStringEnum::getDefaultConstructor + return null; + } + + try { + value = (T) ctor.invoke(); + } catch (Throwable e) { + LOGGER.warn("Failed to create {}, default constructor threw exception", clazz.getName(), e); + return null; + } + + return value.nameAndAddValue(name, value, clazz); + } + } + + private static MethodHandle getDefaultConstructor(Class clazz) { + try { + MethodHandles.Lookup lookup = AzureReflectionUtils.getLookupToUse(clazz); + return lookup.findConstructor(clazz, methodType(void.class)); + } catch (NoSuchMethodException | IllegalAccessException e) { + LOGGER.info("Can't find or access default constructor for {}", clazz.getName(), e); + } catch (Exception e) { + LOGGER.info("Failed to get lookup for {}", clazz.getName(), e); + } + + return null; + } + + @SuppressWarnings("unchecked") + T nameAndAddValue(String name, T value, Class clazz) { + this.name = name; + this.clazz = clazz; + + ((ConcurrentHashMap) VALUES.get(clazz)).put(name, value); + return (T) this; + } + + /** + * Gets a collection of all known values to an expandable string enum type. + * + * @param clazz the class of the expandable string enum. + * @param the class of the expandable string enum. + * @return A collection of all known values for the given {@code clazz}. + */ + @SuppressWarnings("unchecked") + protected static > Collection values(Class clazz) { + return new ArrayList((Collection) VALUES.getOrDefault(clazz, new ConcurrentHashMap<>()).values()); + } + + @Override + @JsonValue + public String toString() { + return this.name; + } + + @Override + public int hashCode() { + return Objects.hash(this.clazz, this.name); + } + + @SuppressWarnings("unchecked") + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } else if (clazz == null || !clazz.isAssignableFrom(obj.getClass())) { + return false; + } else if (obj == this) { + return true; + } else if (this.name == null) { + return ((FastChatExpandableStringEnum) obj).name == null; + } else { + return this.name.equals(((FastChatExpandableStringEnum) obj).name); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java new file mode 100644 index 000000000..d74d3c1be --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatMessage.java @@ -0,0 +1,61 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class FastChatMessage { + + + /* + * The role associated with this message payload. + */ + @JsonProperty(value = "role") + private FastChatRole role; + + /* + * The text associated with this message payload. + */ + @JsonProperty(value = "content") + private String content; + + /** + * Creates an instance of ChatMessage class. + * + * @param role the role value to set. + */ + @JsonCreator + public FastChatMessage(@JsonProperty(value = "role") FastChatRole role) { + this.role = role; + } + + /** + * Get the role property: The role associated with this message payload. + * + * @return the role value. + */ + public FastChatRole getRole() { + return this.role; + } + + /** + * Get the content property: The text associated with this message payload. + * + * @return the content value. + */ + public String getContent() { + return this.content; + } + + /** + * Set the content property: The text associated with this message payload. + * + * @param content the content value to set. + * @return the ChatMessage object itself. + */ + public FastChatMessage setContent(String content) { + this.content = content; + return this; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java new file mode 100644 index 000000000..41069d969 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/model/FastChatRole.java @@ -0,0 +1,46 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.model; + +import com.fasterxml.jackson.annotation.JsonCreator; + +import java.util.Collection; + +public class FastChatRole extends FastChatExpandableStringEnum { + + /** The role that instructs or sets the behavior of the assistant. */ + public static final FastChatRole SYSTEM = fromString("system"); + + /** The role that provides responses to system-instructed, user-prompted input. */ + public static final FastChatRole ASSISTANT = fromString("assistant"); + + /** The role that provides input for chat completions. */ + public static final FastChatRole USER = fromString("user"); + + /** + * Creates a new instance of ChatRole value. + * + * @deprecated Use the {@link #fromString(String)} factory method. + */ + @Deprecated + public FastChatRole() {} + + /** + * Creates or finds a ChatRole from its string representation. + * + * @param name a name to look for. + * @return the corresponding ChatRole. + */ + @JsonCreator + public static FastChatRole fromString(String name) { + return fromString(name, FastChatRole.class); + } + + + /** + * Gets known ChatRole values. + * + * @return known ChatRole values. + */ + public static Collection values() { + return values(FastChatRole.class); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java index 985db1278..f7e96552d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/config/ConfigController.java @@ -14,6 +14,7 @@ import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient; import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; +import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.controller.config.request.AIConfigCreateRequest; import ai.chat2db.server.web.api.controller.config.request.SystemConfigRequest; @@ -83,6 +84,9 @@ public ActionResult addChatGptSystemConfig(@RequestBody AIConfigCreateRequest re case AZUREAI: saveAzureAIConfig(request); break; + case FASTCHATAI: + saveFastChatAIConfig(request); + break; } return ActionResult.isSuccess(); } @@ -159,6 +163,24 @@ private void saveAzureAIConfig(AIConfigCreateRequest request) { AzureOpenAIClient.refresh(); } + /** + * save common fast chat ai config + * + * @param request + */ + private void saveFastChatAIConfig(AIConfigCreateRequest request) { + SystemConfigParam apikeyParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_API_KEY) + .content(request.getApiKey()).build(); + configService.createOrUpdate(apikeyParam); + SystemConfigParam apiHostParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_HOST) + .content(request.getApiHost()).build(); + configService.createOrUpdate(apiHostParam); + SystemConfigParam modelParam = SystemConfigParam.builder().code(FastChatAIClient.FASTCHAT_MODEL) + .content(request.getModel()).build(); + configService.createOrUpdate(modelParam); + FastChatAIClient.refresh(); + } + @GetMapping("/system_config/{code}") public DataResult getSystemConfig(@PathVariable("code") String code) { DataResult result = configService.find(code); @@ -221,6 +243,14 @@ public DataResult getChatAiSystemConfig(String aiSqlSource) { config.setStream(Objects.nonNull(restAiHttpMethod.getData()) ? Boolean.valueOf( restAiHttpMethod.getData().getContent()) : Boolean.TRUE); break; + case FASTCHATAI: + DataResult fastChatApiKey = configService.find(FastChatAIClient.FASTCHAT_API_KEY); + DataResult fastChatApiHost = configService.find(FastChatAIClient.FASTCHAT_HOST); + DataResult fastChatModel = configService.find(FastChatAIClient.FASTCHAT_MODEL); + config.setApiKey(Objects.nonNull(fastChatApiKey.getData()) ? fastChatApiKey.getData().getContent() : ""); + config.setApiHost(Objects.nonNull(fastChatApiHost.getData()) ? fastChatApiHost.getData().getContent() : ""); + config.setModel(Objects.nonNull(fastChatModel.getData()) ? fastChatModel.getData().getContent() : ""); + break; default: break; } @@ -228,153 +258,4 @@ public DataResult getChatAiSystemConfig(String aiSqlSource) { return DataResult.of(config); } - ///** - // * save ai config - // * - // * @param request - // * @return - // */ - //@PostMapping("/system_config/chatgpt") - //public ActionResult addAiSystemConfig(@RequestBody AISystemConfigRequest request) { - // String sqlSource = StringUtils.isNotBlank(request.getAiSqlSource()) ? request.getAiSqlSource() - // : AiSqlSourceEnum.CHAT2DBAI.getCode(); - // AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); - // if (Objects.isNull(aiSqlSourceEnum)) { - // aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI; - // sqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); - // } - // SystemConfigParam param = SystemConfigParam.builder().code(RestAIClient.AI_SQL_SOURCE).content(sqlSource) - // .build(); - // configService.createOrUpdate(param); - // - // switch (Objects.requireNonNull(aiSqlSourceEnum)) { - // case OPENAI : - // saveOpenAIConfig(request); - // break; - // case CHAT2DBAI: - // saveChat2dbAIConfig(request); - // break; - // case RESTAI : - // saveRestAIConfig(request); - // break; - // case AZUREAI : - // saveAzureAIConfig(request); - // break; - // } - // return ActionResult.isSuccess(); - //} - // - ///** - // * 保存OPENAI相关配置 - // * - // * @param request - // */ - //private void saveOpenAIConfig(AISystemConfigRequest request) { - // SystemConfigParam param = SystemConfigParam.builder().code(OpenAIClient.OPENAI_KEY).content( - // request.getApiKey()).build(); - // configService.createOrUpdate(param); - // SystemConfigParam hostParam = SystemConfigParam.builder().code(OpenAIClient.OPENAI_HOST).content( - // request.getApiHost()).build(); - // configService.createOrUpdate(hostParam); - // SystemConfigParam httpProxyHostParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_HOST).content( - // request.getHttpProxyHost()).build(); - // configService.createOrUpdate(httpProxyHostParam); - // SystemConfigParam httpProxyPortParam = SystemConfigParam.builder().code(OpenAIClient.PROXY_PORT).content( - // request.getHttpProxyPort()).build(); - // configService.createOrUpdate(httpProxyPortParam); - // OpenAIClient.refresh(); - //} - // - ///** - // * 保存RESTAI接口相关配置 - // * - // * @param request - // */ - //private void saveRestAIConfig(AISystemConfigRequest request) { - // SystemConfigParam restParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_URL).content( - // request.getRestAiUrl()) - // .build(); - // configService.createOrUpdate(restParam); - // SystemConfigParam methodParam = SystemConfigParam.builder().code(RestAIClient.REST_AI_STREAM_OUT).content( - // request.getRestAiStream().toString()).build(); - // configService.createOrUpdate(methodParam); - // RestAIClient.refresh(); - //} - // - ///** - // * 保存azure配置 - // * - // * @param request - // */ - //private void saveAzureAIConfig(AISystemConfigRequest request) { - // SystemConfigParam apikeyParam = SystemConfigParam.builder().code(AzureOpenAIClient.AZURE_CHATGPT_API_KEY) - // .content( - // request.getAzureApiKey()).build(); - // configService.createOrUpdate(apikeyParam); - // SystemConfigParam endpointParam = SystemConfigParam.builder().code(AzureOpenAIClient - // .AZURE_CHATGPT_ENDPOINT).content( - // request.getAzureEndpoint()).build(); - // configService.createOrUpdate(endpointParam); - // SystemConfigParam modelParam = SystemConfigParam.builder().code(AzureOpenAIClient - // .AZURE_CHATGPT_DEPLOYMENT_ID).content( - // request.getAzureDeploymentId()).build(); - // configService.createOrUpdate(modelParam); - // AzureOpenAIClient.refresh(); - //} - - ///** - // * 查询ChatGPT相关配置 - // * - // * @return - // */ - //@GetMapping("/system_config/chatgpt") - //public DataResult getChatGptSystemConfig() { - // DataResult apiKey = configService.find(OpenAIClient.OPENAI_KEY); - // DataResult apiHost = configService.find(OpenAIClient.OPENAI_HOST); - // DataResult httpProxyHost = configService.find(OpenAIClient.PROXY_HOST); - // DataResult httpProxyPort = configService.find(OpenAIClient.PROXY_PORT); - // DataResult aiSqlSource = configService.find(RestAIClient.AI_SQL_SOURCE); - // DataResult restAiUrl = configService.find(RestAIClient.REST_AI_URL); - // DataResult restAiHttpMethod = configService.find(RestAIClient.REST_AI_STREAM_OUT); - // DataResult azureApiKey = configService.find(AzureOpenAIClient.AZURE_CHATGPT_API_KEY); - // DataResult azureEndpoint = configService.find(AzureOpenAIClient.AZURE_CHATGPT_ENDPOINT); - // DataResult azureDeployId = configService.find(AzureOpenAIClient.AZURE_CHATGPT_DEPLOYMENT_ID); - // ChatGptConfig config = new ChatGptConfig(); - // - // String sqlSource = Objects.nonNull(aiSqlSource.getData()) ? aiSqlSource.getData().getContent() : - // AiSqlSourceEnum.CHAT2DBAI.getCode(); - // AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(sqlSource); - // if (Objects.isNull(aiSqlSourceEnum)) { - // aiSqlSourceEnum = AiSqlSourceEnum.CHAT2DBAI; - // sqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); - // } - // config.setAiSqlSource(sqlSource); - // switch (Objects.requireNonNull(aiSqlSourceEnum)) { - // case OPENAI : - // config.setApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : null); - // config.setApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : null); - // config.setChat2dbApiKey(""); - // config.setChat2dbApiHost(""); - // break; - // case CHAT2DBAI: - // config.setApiKey(""); - // config.setApiHost(""); - // config.setChat2dbApiKey(Objects.nonNull(apiKey.getData()) ? apiKey.getData().getContent() : null); - // config.setChat2dbApiHost(Objects.nonNull(apiHost.getData()) ? apiHost.getData().getContent() : null); - // break; - // } - // config.setRestAiUrl(Objects.nonNull(restAiUrl.getData()) ? restAiUrl.getData().getContent() : null); - // config.setRestAiStream(Objects.nonNull(restAiHttpMethod.getData()) ? Boolean.valueOf( - // restAiHttpMethod.getData().getContent()) : Boolean.TRUE); - // config.setHttpProxyHost(Objects.nonNull(httpProxyHost.getData()) ? httpProxyHost.getData().getContent() : - // null); - // config.setHttpProxyPort(Objects.nonNull(httpProxyPort.getData()) ? httpProxyPort.getData().getContent() : - // null); - // config.setAzureApiKey(Objects.nonNull(azureApiKey.getData()) ? azureApiKey.getData().getContent() : null); - // config.setAzureEndpoint(Objects.nonNull(azureEndpoint.getData()) ? azureEndpoint.getData().getContent() : - // null); - // config.setAzureDeploymentId(Objects.nonNull(azureDeployId.getData()) ? azureDeployId.getData().getContent() - // : null); - // return DataResult.of(config); - //} } From 22628692bc7bb146fd815370a52ec690018c4312 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Thu, 28 Sep 2023 16:55:45 +0800 Subject: [PATCH 0818/1069] support table edit --- .../plugin/mysql/type/MysqlCollationEnum.java | 1 + .../api/controller/rdb/RdbDmlController.java | 46 ++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java index 1a03b1422..120fb3e25 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlCollationEnum.java @@ -62,6 +62,7 @@ public Collation getCollation() { return collation; } + public static List getCollations() { return Arrays.asList(MysqlCollationEnum.values()).stream().map(MysqlCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 7a963257e..54c96f6a1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -1,5 +1,6 @@ package ai.chat2db.server.web.api.controller.rdb; +import java.sql.Connection; import java.util.List; import ai.chat2db.server.domain.api.param.DlExecuteParam; @@ -12,7 +13,9 @@ import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.spi.model.ExecuteResult; +import ai.chat2db.spi.sql.Chat2DBContext; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -42,7 +45,7 @@ public class RdbDmlController { * @param request * @return */ - @RequestMapping(value = "/execute",method = {RequestMethod.POST, RequestMethod.PUT}) + @RequestMapping(value = "/execute", method = {RequestMethod.POST, RequestMethod.PUT}) public ListResult manage(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); ListResult resultDTOListResult = dlTemplateService.execute(param); @@ -50,6 +53,47 @@ public ListResult manage(@RequestBody DmlRequest request) { return ListResult.of(resultVOS); } + + /** + * 增删改查等数据运维 + * + * @param request + * @return + */ + @RequestMapping(value = "/execute_ddl", method = {RequestMethod.POST, RequestMethod.PUT}) + public DataResult executeDDL(@RequestBody DmlRequest request) { + DlExecuteParam param = rdbWebConverter.request2param(request); + Connection connection = Chat2DBContext.getConnection(); + if (connection != null) { + try { + boolean flag = true; + String message = ""; + connection.setAutoCommit(false); + ListResult resultDTOListResult = dlTemplateService.execute(param); + List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); + if (!CollectionUtils.isEmpty(resultVOS)) { + for (ExecuteResultVO resultVO : resultVOS) { + if (!resultVO.getSuccess()) { + flag = false; + } + } + } + if (flag) { + connection.commit(); + return DataResult.of(1L); + }else { + connection.rollback(); + return DataResult.error("sql error",message); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + + } else { + return DataResult.error("connection error", ""); + } + } + /** * 统计行的数量 * From f2fe1c3b675f4f43b7dc4286b8cf63959d920c7c Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Thu, 28 Sep 2023 17:29:33 +0800 Subject: [PATCH 0819/1069] support table edit --- .../web/api/controller/rdb/RdbDmlController.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 54c96f6a1..154a19386 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -61,13 +61,13 @@ public ListResult manage(@RequestBody DmlRequest request) { * @return */ @RequestMapping(value = "/execute_ddl", method = {RequestMethod.POST, RequestMethod.PUT}) - public DataResult executeDDL(@RequestBody DmlRequest request) { + public DataResult executeDDL(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); Connection connection = Chat2DBContext.getConnection(); if (connection != null) { try { boolean flag = true; - String message = ""; + ExecuteResultVO executeResult = null; connection.setAutoCommit(false); ListResult resultDTOListResult = dlTemplateService.execute(param); List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); @@ -75,15 +75,18 @@ public DataResult executeDDL(@RequestBody DmlRequest request) { for (ExecuteResultVO resultVO : resultVOS) { if (!resultVO.getSuccess()) { flag = false; + executeResult = resultVO; + break; + } } } if (flag) { connection.commit(); - return DataResult.of(1L); + return DataResult.of(resultVOS.get(0)); }else { connection.rollback(); - return DataResult.error("sql error",message); + return DataResult.of(executeResult); } } catch (Exception e) { throw new RuntimeException(e); From d0924f14171844e3b1f7daabbb9ed1ed0447e159 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 28 Sep 2023 18:47:38 +0800 Subject: [PATCH 0820/1069] VARCHAR default value --- chat2db-client/.vscode/settings.json | 1 + .../DatabaseTableEditor/ColumnList/index.tsx | 20 ++++-- .../src/blocks/DatabaseTableEditor/index.tsx | 61 +++++++++---------- chat2db-client/src/service/sql.ts | 6 +- 4 files changed, 49 insertions(+), 39 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index c6ee5882f..759fad4f7 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -78,6 +78,7 @@ "umijs", "USERANDPASSWORD", "uuidv", + "VARCHAR", "VIEWCOLUMN", "wireframe" ], diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 6b8a304c0..3489b325f 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -348,6 +348,12 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) if (editStatus !== EditColumnOperationType.Add) { editStatus = EditColumnOperationType.Modify; } + const editingDataItem = { + ...item, + [name]: value, + editStatus, + }; + if (name === 'columnType') { // 根据当前字段类型,设置编辑配置 databaseSupportField.columnTypes.forEach((i) => { @@ -358,15 +364,19 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }); } }); + // 特殊处理VARCHAR的默认长度 为255 + if (value === 'VARCHAR' && editingDataItem.columnSize === null) { + editingDataItem.columnSize = 255; + form.setFieldsValue({ + columnSize: 255, + }); + } } - return { - ...item, - [name]: value, - editStatus, - }; + return editingDataItem; } return item; }); + console.log(newData); setDataSource(newData); }; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index dea772f87..77a44ad43 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -7,7 +7,7 @@ import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams, IExecuteSqlParams } from '@/service/sql'; import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; -import { IEditTableInfo, IWorkspaceTab, IManageResultData } from '@/typings'; +import { IEditTableInfo, IWorkspaceTab } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; @@ -49,7 +49,7 @@ export default memo((props: IProps) => { const columnListRef = useRef(null); const indexListRef = useRef(null); const monacoEditorRef = useRef(null); - const [executeSqlResult, setExecuteSqlResult] = useState(); + const [executeSqlResult, setExecuteSqlResult] = useState(null); const [executeLoading, setExecuteLoading] = useState(false); const [appendValue, setAppendValue] = useState(''); const tabList = useMemo(() => { @@ -80,18 +80,24 @@ export default memo((props: IProps) => { setCurrentTab(item); } + useEffect(() => { + if (!viewSqlModal) { + setExecuteSqlResult(null); + } + }, [viewSqlModal]); + useEffect(() => { if (tableName) { - getTableDetails(); + getTableDetails({}); } }, []); - const getTableDetails = () => { + const getTableDetails = ({ tableNameProps }: { tableNameProps?: string }) => { if (!tableName) return; const params = { databaseName, dataSourceId, - tableName, + tableName: tableNameProps || tableName, schemaName, refresh: true, }; @@ -139,25 +145,26 @@ export default memo((props: IProps) => { }; setExecuteLoading(true); sqlService - .executeSql(executeSQLParams) + .executeDDL(executeSQLParams) .then((res) => { - if (!tableName) { - const newTableName = baseInfoRef.current?.getBaseInfo().name; - changeTabDetails({ - ...tabDetails, - title: `edit-${newTableName}`, - uniqueData: { - ...(tabDetails.uniqueData || {}), - tableName: newTableName, - }, - }); - } - if (res.filter((t) => !t.success).length === 0) { + if (res.success) { setViewSqlModal(false); message.success(i18n('common.text.successfulExecution')); + const newTableName = baseInfoRef.current?.getBaseInfo().name; + getTableDetails({ tableNameProps: newTableName }); + if (!tableName) { + changeTabDetails({ + ...tabDetails, + title: `edit-${newTableName}`, + uniqueData: { + ...(tabDetails.uniqueData || {}), + tableName: newTableName, + }, + }); + } + } else { + setExecuteSqlResult(res.message); } - setExecuteSqlResult(res); - getTableDetails(); }) .finally(() => { setExecuteLoading(false); @@ -255,19 +262,7 @@ export default memo((props: IProps) => {

    {i18n('common.text.errorMessage')}
    - {executeSqlResult - ?.filter((t) => !t.success) - .map((t, i) => { - return ( -
    -
    - - sql{i + 1}:{t.sql} -
    -
    {t.message}
    -
    - ); - })} +
    {executeSqlResult}
    )} diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index c0e0e4729..5f002bbcf 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -205,9 +205,13 @@ export interface IModifyTableSqlParams { } /** 获取修改表的sql */ -const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); +const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); + +/** 执行编辑表的sql, 专为编辑表而生 */ +const executeDDL = createRequest('/api/rdb/dml/execute_ddl', {method:'post'}); export default { + executeDDL, getModifyTableSql, getTableDetails, getDatabaseFieldTypeList, From 4629526b026ff86446c90a06420b9730638ad81a Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 1 Oct 2023 22:43:16 +0800 Subject: [PATCH 0821/1069] support oracle table edit --- .../mysql/type/MysqlColumnTypeEnum.java | 11 +- .../plugin/mysql/type/MysqlIndexTypeEnum.java | 2 +- .../chat2db/plugin/oracle/OracleMetaData.java | 199 ++++++++++++-- .../oracle/builder/OracleSqlBuilder.java | 98 +++++++ .../oracle/type/OracleColumnTypeEnum.java | 252 ++++++++++++++++++ .../oracle/type/OracleIndexTypeEnum.java | 106 ++++++++ .../api/controller/rdb/TableController.java | 17 +- .../chat2db/spi/jdbc/DefaultMetaService.java | 15 +- .../java/ai/chat2db/spi/model/ColumnType.java | 2 + .../ai/chat2db/spi/model/TableColumn.java | 3 + 10 files changed, 666 insertions(+), 39 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java index a34ea0bb6..bd89f7868 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlColumnTypeEnum.java @@ -125,7 +125,7 @@ public ColumnType getColumnType() { MysqlColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent,boolean supportValue) { - this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent,supportValue); + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent,supportValue,false); } private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); @@ -227,6 +227,15 @@ private String buildDefaultValue(TableColumn column, MysqlColumnTypeEnum type) { if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ return ""; } + + if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT ''"); + } + + if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT NULL"); + } + if(Arrays.asList(CHAR,VARCHAR,BINARY,VARBINARY, SET,ENUM).contains(type)){ return StringUtils.join("DEFAULT '",column.getDefaultValue(),"'"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index 2c0623dea..b4266434c 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -106,6 +106,6 @@ private String buildDropIndex(TableIndex tableIndex) { if (MysqlIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } - return StringUtils.join("DROP INDEX `", tableIndex.getOldName()); + return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); } } 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 634bee8fe..fa26e8210 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 @@ -1,24 +1,28 @@ package ai.chat2db.plugin.oracle; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; +import ai.chat2db.plugin.oracle.builder.OracleSqlBuilder; +import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; +import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.lang3.StringUtils; public class OracleMetaData extends DefaultMetaService implements MetaData { @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String sql = "select dbms_metadata.get_ddl('TABLE','" + tableName + "') as sql from dual," - + "user_tables where table_name = '" + tableName + "'"; + + "user_tables where table_name = '" + tableName + "'"; return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { try { if (resultSet.next()) { @@ -32,15 +36,69 @@ public String tableDDL(Connection connection, String databaseName, String schema }); } + private static String SELECT_TABLE_SQL = "SELECT A.OWNER, A.TABLE_NAME, B.COMMENTS " + + "FROM ALL_TABLES A LEFT JOIN ALL_TAB_COMMENTS B ON A.OWNER = B.OWNER AND A.TABLE_NAME = B.TABLE_NAME\n" + + "where A.OWNER = '%s' "; + + @Override + public List
    tables(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = String.format(SELECT_TABLE_SQL, schemaName); + if(StringUtils.isNotBlank(tableName)){ + sql = sql + " and A.TABLE_NAME = '" + tableName + "'"; + } + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + List
    tables = new ArrayList<>(); + while (resultSet.next()) { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(resultSet.getString("TABLE_NAME")); + table.setComment(resultSet.getString("COMMENTS")); + tables.add(table); + } + return tables; + }); + } + + private static String SELECT_TAB_COLS = "SELECT atc.column_id , atc.column_name as COLUMN_NAME, atc.data_type as DATA_TYPE , atc.data_length as DATA_LENGTH , atc.data_type_mod , atc.nullable , atc.data_default , acc.comments , atc.DATA_PRECISION , atc.DATA_SCALE , atc.CHAR_USED FROM all_tab_columns atc, all_col_comments acc WHERE atc.owner = acc.owner AND atc.table_name = acc.table_name AND atc.column_name = acc.column_name AND atc.owner = '%s' AND atc.table_name = '%s' order by atc.column_id"; + @Override + public List columns(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = String.format(SELECT_TAB_COLS, schemaName, tableName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + List tableColumns = new ArrayList<>(); + while (resultSet.next()) { + TableColumn tableColumn = new TableColumn(); + tableColumn.setTableName(tableName); + tableColumn.setSchemaName(schemaName); + tableColumn.setName(resultSet.getString("COLUMN_NAME")); + tableColumn.setColumnType(resultSet.getString("DATA_TYPE")); + tableColumn.setColumnSize(resultSet.getInt("DATA_LENGTH")); + tableColumn.setDefaultValue(resultSet.getString("DATA_DEFAULT")); + tableColumn.setComment(resultSet.getString("COMMENTS")); + tableColumn.setNullable("Y".equalsIgnoreCase(resultSet.getString("NULLABLE"))?1:0); + tableColumn.setOrdinalPosition(resultSet.getInt("COLUMN_ID")); + tableColumn.setDecimalDigits(resultSet.getInt("DATA_SCALE")); + String charUsed = resultSet.getString("CHAR_USED"); + if("B".equalsIgnoreCase(charUsed)){ + tableColumn.setUnit("BYTE"); + }else if("C".equalsIgnoreCase(charUsed)){ + tableColumn.setUnit("CHAR"); + } + tableColumns.add(tableColumn); + } + return tableColumns; + }); + } + private static String ROUTINES_SQL - = "SELECT LINE, TEXT " - + "FROM ALL_SOURCE " - + "WHERE TYPE = '%s' AND NAME = '%s' " - + "ORDER BY LINE"; + = "SELECT LINE, TEXT " + + "FROM ALL_SOURCE " + + "WHERE TYPE = '%s' AND NAME = '%s' " + + "ORDER BY LINE"; @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, - String functionName) { + String functionName) { String sql = String.format(ROUTINES_SQL, "FUNCTION", functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -60,28 +118,101 @@ public Function function(Connection connection, @NotEmpty String databaseName, S } private static String TRIGGER_SQL_LIST - = "SELECT TRIGGER_NAME " - + "FROM ALL_TRIGGERS WHERE OWNER = '%s'"; + = "SELECT TRIGGER_NAME " + + "FROM ALL_TRIGGERS WHERE OWNER = '%s'"; + + private static String SELECT_PK_SQL = "select acc.CONSTRAINT_NAME from all_cons_columns acc, all_constraints ac where acc.constraint_name = ac.constraint_name and acc.owner = ac.owner and acc.owner = '%s' and ac.constraint_type = 'P' and ac.table_name = '%s' "; + + private static String SELECT_TABLE_INDEX = "SELECT ai.index_name AS Key_name, aic.column_name AS Column_name, ai.index_type AS Index_type, ai.uniqueness AS Unique_name, aic.COLUMN_POSITION as Seq_in_index, aic.descend AS Collation, ex.COLUMN_EXPRESSION as COLUMN_EXPRESSION FROM all_ind_columns aic JOIN all_indexes ai ON aic.table_owner = ai.table_owner and aic.table_name = ai.table_name and aic.index_name = ai.index_name LEFT JOIN ALL_IND_EXPRESSIONS ex ON aic.table_owner = ex.table_owner and aic.table_name = ex.table_name and aic.index_name = ex.index_name where ai.table_owner = '%s' AND ai.table_name = '%s' "; + + + @Override + public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { + String pkSql = String.format(SELECT_PK_SQL, schemaName, tableName); + Set pkSet = new HashSet<>(); + SQLExecutor.getInstance().execute(connection, pkSql, resultSet -> { + while (resultSet.next()) { + pkSet.add(resultSet.getString("CONSTRAINT_NAME")); + } + return null; + } + ); + + String sql = String.format(SELECT_TABLE_INDEX, schemaName, tableName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + LinkedHashMap map = new LinkedHashMap(); + while (resultSet.next()) { + String keyName = resultSet.getString("Key_name"); + TableIndex tableIndex = map.get(keyName); + if (tableIndex != null) { + List columnList = tableIndex.getColumnList(); + columnList.add(getTableIndexColumn(resultSet)); + columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) + .collect(Collectors.toList()); + tableIndex.setColumnList(columnList); + } else { + TableIndex index = new TableIndex(); + index.setDatabaseName(databaseName); + index.setSchemaName(schemaName); + index.setTableName(tableName); + index.setName(keyName); + index.setUnique("unique".equalsIgnoreCase(resultSet.getString("Unique_name"))); + index.setType(resultSet.getString("Index_type")); + List tableIndexColumns = new ArrayList<>(); + tableIndexColumns.add(getTableIndexColumn(resultSet)); + index.setColumnList(tableIndexColumns); + if (index.getUnique()) { + index.setType(OracleIndexTypeEnum.UNIQUE.getName()); + } else if ("NORMAL".equalsIgnoreCase(index.getType())) { + index.setType(OracleIndexTypeEnum.NORMAL.getName()); + } else if ("BITMAP".equalsIgnoreCase(index.getType())) { + index.setType(OracleIndexTypeEnum.BITMAP.getName()); + } else if(StringUtils.isNotBlank(index.getType()) && index.getType().toUpperCase().contains("NORMAL")){ + index.setType(OracleIndexTypeEnum.NORMAL.getName()); + } + if(pkSet.contains(keyName)){ + index.setType(OracleIndexTypeEnum.PRIMARY_KEY.getName()); + } + map.put(keyName, index); + } + } + return map.values().stream().collect(Collectors.toList()); + }); + + } + + private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { + TableIndexColumn tableIndexColumn = new TableIndexColumn(); + tableIndexColumn.setColumnName(resultSet.getString("Column_name")); + String expression = resultSet.getString("COLUMN_EXPRESSION"); + if(!StringUtils.isBlank(expression)){ + tableIndexColumn.setColumnName(expression.replace("\"","")); + } + tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); + tableIndexColumn.setCollation(resultSet.getString("Collation")); + tableIndexColumn.setAscOrDesc(resultSet.getString("Collation")); + return tableIndexColumn; + } @Override public List triggers(Connection connection, String databaseName, String schemaName) { List triggers = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, String.format(TRIGGER_SQL_LIST, schemaName), - resultSet -> { - while (resultSet.next()) { - Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); - trigger.setSchemaName(schemaName); - trigger.setDatabaseName(databaseName); - triggers.add(trigger); - } - return triggers; - }); + resultSet -> { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + return triggers; + }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, - String triggerName) { + String triggerName) { String sql = String.format(ROUTINES_SQL, "TRIGGER", triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -100,7 +231,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, - String procedureName) { + String procedureName) { String sql = String.format(ROUTINES_SQL, "PROCEDURE", procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { StringBuilder sb = new StringBuilder(); @@ -117,7 +248,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, } private static String VIEW_SQL - = "SELECT VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER = '%s' AND VIEW_NAME = '%s'"; + = "SELECT VIEW_NAME, TEXT FROM ALL_VIEWS WHERE OWNER = '%s' AND VIEW_NAME = '%s'"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { @@ -133,4 +264,18 @@ public Table view(Connection connection, String databaseName, String schemaName, return table; }); } + + @Override + public SqlBuilder getSqlBuilder() { + return new OracleSqlBuilder(); + } + + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return TableMeta.builder() + .columnTypes(OracleColumnTypeEnum.getTypes()) + .charsets(Lists.newArrayList()) + .collations(Lists.newArrayList()) + .build(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java new file mode 100644 index 000000000..7394d1006 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -0,0 +1,98 @@ +package ai.chat2db.plugin.oracle.builder; + +import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; +import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import org.apache.commons.lang3.StringUtils; + +public class OracleSqlBuilder implements SqlBuilder { + @Override + public String buildCreateTableSql(Table table) { + StringBuilder script = new StringBuilder(); + + script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); + + for (TableColumn column : table.getColumnList()) { + if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType())){ + continue; + } + OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(column.getColumnType()); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append("\n);"); + + for (TableIndex tableIndex : table.getIndexList()) { + if(StringUtils.isBlank(tableIndex.getName())|| StringUtils.isBlank(tableIndex.getType())){ + continue; + } + OracleIndexTypeEnum oracleColumnTypeEnum = OracleIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\n").append("").append(oracleColumnTypeEnum.buildIndexScript(tableIndex)).append(";"); + } + + for (TableColumn column : table.getColumnList()) { + if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())){ + continue; + } + script.append("\n").append(buildComment(column)).append(";"); + } + + if (StringUtils.isNotBlank(table.getComment())) { + script.append("\n").append(buildTableComment(table)).append(";"); + } + + + return script.toString(); + } + + private String buildTableComment(Table table) { + StringBuilder script = new StringBuilder(); + script.append("COMMENT ON TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" IS '").append(table.getComment()).append("'"); + return script.toString(); + } + + private String buildComment(TableColumn column) { + StringBuilder script = new StringBuilder(); + script.append("COMMENT ON COLUMN ").append("\"").append(column.getSchemaName()).append("\".\"").append(column.getTableName()).append("\".\"").append(column.getName()).append("\" IS '").append(column.getComment()).append("'"); + return script.toString(); + } + + @Override + public String buildModifyTaleSql(Table oldTable, Table newTable) { + StringBuilder script = new StringBuilder(); + + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("ALTER TABLE "). append("\"").append(oldTable.getSchemaName()).append("\"").append("").append("\"").append(oldTable.getName()).append("\""); + script.append(" ").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";"); + } + if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { + script.append("\n").append(buildTableComment(newTable)).append(";"); + } + + + // append modify column + for (TableColumn tableColumn : newTable.getColumnList()) { + if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { + OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); + } + } + + // append modify index + for (TableIndex tableIndex : newTable.getIndexList()) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { + OracleIndexTypeEnum mysqlIndexTypeEnum = OracleIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); + } + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append(";"); + + return script.toString(); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java new file mode 100644 index 000000000..f80ac88f9 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java @@ -0,0 +1,252 @@ +package ai.chat2db.plugin.oracle.type; + +import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum OracleColumnTypeEnum implements ColumnBuilder { + //JSON("JSON", false, false, true, false, false, false, true, false, false, false) + + BFILE("BFILE", false, false, true, false, false, false, true, true, false, false), + + BINARY_DOUBLE("BINARY_DOUBLE", false, false, true, false, false, false, true, true, false, false), + + + BINARY_FLOAT("BINARY_FLOAT", false, false, true, false, false, false, true, true, false, false), + + + BLOB("BLOB", false, false, true, false, false, false, true, true, false, false), + + + CHAR("CHAR", true, false, true, false, false, false, true, true, false, true), + + CHAR_VARYING("CHAR VARYING", true, false, true, false, false, false, true, true, false, true), + + CHARACTER("CHARACTER", true, false, true, false, false, false, true, true, false, true), + + CHARACTER_VARYING("CHARACTER VARYING", true, false, true, false, false, false, true, true, false, true), + + CLOB("CLOB", false, false, true, false, false, false, true, true, false, false), + + DATE("DATE", false, false, true, false, false, false, true, true, false, false), + + DECIMAL("DECIMAL", true, true, true, false, false, false, true, true, false, false), + + DOUBLE_PRECISION("DOUBLE PRECISION", false, false, true, false, false, false, true, true, false, false), + + + FLOAT("FLOAT", true, false, true, false, false, false, true, true, false, false), + + INT("INT", false, false, true, false, false, false, true, true, false, false), + + INTEGER("INTEGER", false, false, true, false, false, false, true, true, false, false), + + LONG("LONG", false, false, true, false, false, false, true, true, false, false), + + LONG_RAW("LONG RAW", false, false, true, false, false, false, true, true, false, false), + + + LONG_VARCHAR("LONG VARCHAR", false, false, true, false, false, false, true, true, false, false), + + NATIONAL_CHAR("NATIONAL CHAR", true, false, true, false, false, false, true, true, false, true), + + + NATIONAL_CHAR_VARYING("NATIONAL CHAR VARYING", true, false, true, false, false, false, true, true, false, true), + + + NATIONAL_CHARACTER("NATIONAL CHARACTER", true, false, true, false, false, false, true, true, false, true), + + + NATIONAL_CHARACTER_VARYING("NATIONAL CHARACTER VARYING", true, false, true, false, false, false, true, true, false, true), + + NCHAR("NCHAR", true, false, true, false, false, false, true, true, false, false), + + NCHAR_VARYING("NCHAR VARYING", true, false, true, false, false, false, true, true, false, false), + + NCLOB("NCLOB", false, false, true, false, false, false, true, true, false, false), + + NUMBER("NUMBER", true, true, true, false, false, false, true, true, false, false), + + + NVARCHAR2("NVARCHAR2", true, false, true, false, false, false, true, true, false, true), + + RAW("RAW", true, false, true, false, false, false, true, true, false, false), + + REAL("REAL", false, false, true, false, false, false, true, true, false, false), + + ROWID("ROWID", false, false, true, false, false, false, true, true, false, false), + + + SMALLINT("SMALLINT", false, false, true, false, false, false, true, true, false, false), + + TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, false, false), + + TIMESTAMP_WITH_LOCAL_TIME_ZONE("TIMESTAMP WITH LOCAL TIME ZONE", true, false, true, false, false, false, true, true, false, false), + + + TIMESTAMP_WITH_TIME_ZONE("TIMESTAMP WITH TIME ZONE", true, false, true, false, false, false, true, true, false, false), + + UROWID("UROWID", true, false, true, false, false, false, true, true, false, false), + + VARCHAR("VARCHAR", true, false, true, false, false, false, true, true, false, true), + + VARCHAR2("VARCHAR2", true, false, true, false, false, false, true, true, false, true), + + ; + private ColumnType columnType; + + public static OracleColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (OracleColumnTypeEnum value : OracleColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); + } + } + + public ColumnType getColumnType() { + return columnType; + } + + + OracleColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportUnit) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, false, supportUnit); + } + + @Override + public String buildCreateColumnSql(TableColumn column) { + OracleColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + if (type == null) { + return ""; + } + StringBuilder script = new StringBuilder(); + + script.append("\"").append(column.getName()).append("\"").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + script.append(buildDefaultValue(column,type)).append(" "); + + script.append(buildNullable(column, type)).append(" "); + + return script.toString(); + } + + + private String buildNullable(TableColumn column,OracleColumnTypeEnum type) { + if(!type.getColumnType().isSupportNullable()){ + return ""; + } + if (column.getNullable()!=null && 1==column.getNullable()) { + return "NULL"; + } else { + return "NOT NULL"; + } + } + + private String buildDefaultValue(TableColumn column, OracleColumnTypeEnum type) { + if(!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())){ + return ""; + } + + if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT ''"); + } + + if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT NULL"); + } + + return StringUtils.join("DEFAULT ",column.getDefaultValue()); + } + + private String buildDataType(TableColumn column, OracleColumnTypeEnum type) { + String columnType = type.columnType.getTypeName(); + if (Arrays.asList(CHAR, CHAR_VARYING, CHARACTER, CHARACTER_VARYING, + NVARCHAR2, VARCHAR, VARCHAR2,NATIONAL_CHAR, + NATIONAL_CHAR_VARYING,NATIONAL_CHARACTER, + NATIONAL_CHARACTER_VARYING,NCHAR,NCHAR_VARYING).contains(type)) { + StringBuilder script = new StringBuilder(); + script.append(columnType); + if (column.getColumnSize() != null && StringUtils.isEmpty(column.getUnit())) { + script.append("(").append(column.getColumnSize()).append(")"); + } else if (column.getColumnSize() != null && !StringUtils.isEmpty(column.getUnit())) { + script.append("(").append(column.getColumnSize()).append(" ").append(column.getUnit()).append(")"); + } + return script.toString(); + } + + if (Arrays.asList(DECIMAL, FLOAT, NUMBER, UROWID,RAW,TIMESTAMP).contains(type)) { + StringBuilder script = new StringBuilder(); + script.append(columnType); + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + script.append("(").append(column.getColumnSize()).append(")"); + } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) { + script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")"); + } + return script.toString(); + } + + if (Arrays.asList(TIMESTAMP_WITH_TIME_ZONE,TIMESTAMP_WITH_LOCAL_TIME_ZONE).contains(type)) { + StringBuilder script = new StringBuilder(); + if(column.getColumnSize() == null){ + script.append(columnType); + }else { + String [] split = columnType.split("TIMESTAMP"); + script.append("TIMESTAMP").append("(").append(column.getColumnSize()).append(")").append(split[1]); + } + return script.toString(); + } + + + return columnType; + } + + + + @Override + public String buildModifyColumn(TableColumn tableColumn) { + + if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\"").append(";"); + return script.toString(); + } + if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(");"); + return script.toString(); + } + if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append("); \n" ); + + if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\"").append(";"); + + } + return script.toString(); + + } + return ""; + } + public static List getTypes(){ + return Arrays.stream(OracleColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java new file mode 100644 index 000000000..f0f183f16 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java @@ -0,0 +1,106 @@ +package ai.chat2db.plugin.oracle.type; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import org.apache.commons.lang3.StringUtils; + +public enum OracleIndexTypeEnum { + + PRIMARY_KEY("Primary", "PRIMARY KEY"), + + NORMAL("Normal", "INDEX"), + + UNIQUE("Unique", "UNIQUE INDEX"), + + BITMAP("BITMAP", "BITMAP INDEX"); + + + public String getName() { + return name; + } + + private String name; + + + public String getKeyword() { + return keyword; + } + + private String keyword; + + OracleIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + } + + + public static OracleIndexTypeEnum getByType(String type) { + for (OracleIndexTypeEnum value : OracleIndexTypeEnum.values()) { + if (value.name.equalsIgnoreCase(type)) { + return value; + } + } + return null; + } + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + if (PRIMARY_KEY.equals(this)) { + script.append("CREATE PRIMARY KEY "); + } else if (UNIQUE.equals(this)) { + script.append("CREATE UNIQUE INDEX "); + } else { + script.append("CREATE INDEX "); + } + script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); + + return script.toString(); + } + + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + if (StringUtils.isNotBlank(column.getColumnName())) { + script.append("\"").append(column.getColumnName()).append("\""); + if(!StringUtils.isBlank(column.getAscOrDesc())){ + script.append(" ").append(column.getAscOrDesc()); + } + script .append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + return "\"" + tableIndex.getSchemaName() + "\"" + "\"" + tableIndex.getName() + "\""; + } + + public String buildModifyIndex(TableIndex tableIndex) { + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex), ",\n" , buildIndexScript(tableIndex)); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join( buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (OracleIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { + return StringUtils.join("DROP PRIMARY KEY"); + } + StringBuilder script = new StringBuilder(); + script.append("DROP INDEX "); + script.append(buildIndexName(tableIndex)); + + return script.toString(); + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index ac985e8e0..bcbcca69a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -161,9 +161,20 @@ public DataResult
    query(@Valid TableDetailQueryRequest request) { */ @PostMapping("/modify/sql") public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest request) { - return tableService.buildSql( - rdbWebConverter.tableRequest2param(request.getOldTable()), - rdbWebConverter.tableRequest2param(request.getNewTable())) + Table table = rdbWebConverter.tableRequest2param(request.getNewTable()); + table.setSchemaName(request.getSchemaName()); + table.setDatabaseName(request.getDatabaseName()); + for (TableColumn tableColumn : table.getColumnList()) { + tableColumn.setSchemaName(request.getSchemaName()); + tableColumn.setTableName(table.getName()); + tableColumn.setDatabaseName(request.getDatabaseName()); + } + for (TableIndex tableIndex : table.getIndexList()) { + tableIndex.setSchemaName(request.getSchemaName()); + tableIndex.setTableName(table.getName()); + tableIndex.setDatabaseName(request.getDatabaseName()); + } + return tableService.buildSql(rdbWebConverter.tableRequest2param(request.getOldTable()),table) .map(rdbWebConverter::dto2vo); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index ca2ca8eba..bdd4aa780 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -8,6 +8,7 @@ import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import org.apache.commons.lang3.StringUtils; /** * @author jipengfei @@ -31,7 +32,7 @@ public String tableDDL(Connection connection, String databaseName, String schema @Override public List
    tables(Connection connection, String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(connection, databaseName, schemaName, tableName, new String[]{"TABLE"}); + return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE"}); } @Override @@ -41,12 +42,12 @@ public Table view(Connection connection, String databaseName, String schemaName, @Override public List
    views(Connection connection, String databaseName, String schemaName) { - return SQLExecutor.getInstance().tables(connection, databaseName, schemaName, null, new String[]{"VIEW"}); + return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, null, new String[]{"VIEW"}); } @Override public List functions(Connection connection, String databaseName, String schemaName) { - return SQLExecutor.getInstance().functions(connection, databaseName, schemaName); + return SQLExecutor.getInstance().functions(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); } @Override @@ -56,23 +57,23 @@ public List triggers(Connection connection, String databaseName, String @Override public List procedures(Connection connection, String databaseName, String schemaName) { - return SQLExecutor.getInstance().procedures(connection, databaseName, schemaName); + return SQLExecutor.getInstance().procedures(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName); } @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().columns(connection, databaseName, schemaName, tableName, null); + return SQLExecutor.getInstance().columns(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, null); } @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName, String columnName) { - return SQLExecutor.getInstance().columns(connection, databaseName, schemaName, tableName, columnName); + return SQLExecutor.getInstance().columns(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, columnName); } @Override public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().indexes(connection, databaseName, schemaName, tableName); + return SQLExecutor.getInstance().indexes(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName); } @Override diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java index 715b8200f..5d4862f3d 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java @@ -19,4 +19,6 @@ public class ColumnType { private boolean supportExtent; private boolean supportValue; + + private boolean supportUnit; } 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 13588a12f..91ede3413 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 @@ -168,4 +168,7 @@ public class TableColumn { private String value; + + private String unit; + } From 0e2fb189d8858b1a62e6ea5df36201ac8c1f002b Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 1 Oct 2023 22:47:24 +0800 Subject: [PATCH 0822/1069] support oracle table edit --- .../java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java index f0f183f16..3d69d221f 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java @@ -77,7 +77,7 @@ private String buildIndexColumn(TableIndex tableIndex) { } private String buildIndexName(TableIndex tableIndex) { - return "\"" + tableIndex.getSchemaName() + "\"" + "\"" + tableIndex.getName() + "\""; + return "\"" + tableIndex.getSchemaName() + "\"." + "\"" + tableIndex.getName() + "\""; } public String buildModifyIndex(TableIndex tableIndex) { From 7ce10219d481f94d4fa6bfc2a390cf2f1ab0003c Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 1 Oct 2023 23:11:41 +0800 Subject: [PATCH 0823/1069] support oracle table edit --- .../plugin/oracle/builder/OracleSqlBuilder.java | 2 +- .../oracle/type/OracleColumnTypeEnum.java | 17 +++++++++-------- .../plugin/oracle/type/OracleIndexTypeEnum.java | 4 ++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java index 7394d1006..2a5664150 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -66,7 +66,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { - script.append("ALTER TABLE "). append("\"").append(oldTable.getSchemaName()).append("\"").append("").append("\"").append(oldTable.getName()).append("\""); + script.append("ALTER TABLE "). append("\"").append(oldTable.getSchemaName()).append("\".\"").append(oldTable.getName()).append("\""); script.append(" ").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java index f80ac88f9..876f8d8ae 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleColumnTypeEnum.java @@ -219,24 +219,25 @@ public String buildModifyColumn(TableColumn tableColumn) { if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); - script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); - script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\"").append(";"); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("DROP COLUMN ").append("\"").append(tableColumn.getName()).append("\""); return script.toString(); } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); - script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); - script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(");"); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("ADD (").append(buildCreateColumnSql(tableColumn)).append(")"); return script.toString(); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { StringBuilder script = new StringBuilder(); - script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); - script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append("); \n" ); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("MODIFY (").append(buildCreateColumnSql(tableColumn)).append(") \n" ); if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { - script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\"").append("").append("\"").append(tableColumn.getTableName()).append("\""); - script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\"").append(";"); + script.append(";"); + script.append("ALTER TABLE "). append("\"").append(tableColumn.getSchemaName()).append("\".\"").append(tableColumn.getTableName()).append("\""); + script.append(" ").append("RENAME COLUMN ").append("\"").append(tableColumn.getOldName()).append("\"").append(" TO ").append("\"").append(tableColumn.getName()).append("\""); } return script.toString(); diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java index 3d69d221f..2ce16e7dd 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java @@ -53,7 +53,7 @@ public String buildIndexScript(TableIndex tableIndex) { } else { script.append("CREATE INDEX "); } - script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); + script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); return script.toString(); } @@ -85,7 +85,7 @@ public String buildModifyIndex(TableIndex tableIndex) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { - return StringUtils.join(buildDropIndex(tableIndex), ",\n" , buildIndexScript(tableIndex)); + return StringUtils.join(buildDropIndex(tableIndex), ";\n" , buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { return StringUtils.join( buildIndexScript(tableIndex)); From 04bd5925e331c01132e49f134a91152ae1e64785 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 2 Oct 2023 11:39:12 +0800 Subject: [PATCH 0824/1069] support oracle table edit --- .../chat2db/plugin/oracle/OracleMetaData.java | 23 +++++++++++-------- .../oracle/type/OracleIndexTypeEnum.java | 21 +++++++++-------- 2 files changed, 24 insertions(+), 20 deletions(-) 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 fa26e8210..7204d0d81 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 @@ -19,10 +19,12 @@ import org.apache.commons.lang3.StringUtils; public class OracleMetaData extends DefaultMetaService implements MetaData { + + private static final String TABLE_DDL_SQL = "select dbms_metadata.get_ddl('TABLE','%s','%s') as sql from dual"; + @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { - String sql = "select dbms_metadata.get_ddl('TABLE','" + tableName + "') as sql from dual," - + "user_tables where table_name = '" + tableName + "'"; + String sql = String.format(TABLE_DDL_SQL, tableName, schemaName); return SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> { try { if (resultSet.next()) { @@ -43,7 +45,7 @@ public String tableDDL(Connection connection, String databaseName, String schema @Override public List
    tables(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TABLE_SQL, schemaName); - if(StringUtils.isNotBlank(tableName)){ + if (StringUtils.isNotBlank(tableName)) { sql = sql + " and A.TABLE_NAME = '" + tableName + "'"; } return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -61,6 +63,7 @@ public List
    tables(Connection connection, String databaseName, String sch } private static String SELECT_TAB_COLS = "SELECT atc.column_id , atc.column_name as COLUMN_NAME, atc.data_type as DATA_TYPE , atc.data_length as DATA_LENGTH , atc.data_type_mod , atc.nullable , atc.data_default , acc.comments , atc.DATA_PRECISION , atc.DATA_SCALE , atc.CHAR_USED FROM all_tab_columns atc, all_col_comments acc WHERE atc.owner = acc.owner AND atc.table_name = acc.table_name AND atc.column_name = acc.column_name AND atc.owner = '%s' AND atc.table_name = '%s' order by atc.column_id"; + @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(SELECT_TAB_COLS, schemaName, tableName); @@ -75,13 +78,13 @@ public List columns(Connection connection, String databaseName, Str tableColumn.setColumnSize(resultSet.getInt("DATA_LENGTH")); tableColumn.setDefaultValue(resultSet.getString("DATA_DEFAULT")); tableColumn.setComment(resultSet.getString("COMMENTS")); - tableColumn.setNullable("Y".equalsIgnoreCase(resultSet.getString("NULLABLE"))?1:0); + tableColumn.setNullable("Y".equalsIgnoreCase(resultSet.getString("NULLABLE")) ? 1 : 0); tableColumn.setOrdinalPosition(resultSet.getInt("COLUMN_ID")); tableColumn.setDecimalDigits(resultSet.getInt("DATA_SCALE")); String charUsed = resultSet.getString("CHAR_USED"); - if("B".equalsIgnoreCase(charUsed)){ + if ("B".equalsIgnoreCase(charUsed)) { tableColumn.setUnit("BYTE"); - }else if("C".equalsIgnoreCase(charUsed)){ + } else if ("C".equalsIgnoreCase(charUsed)) { tableColumn.setUnit("CHAR"); } tableColumns.add(tableColumn); @@ -167,10 +170,10 @@ public List indexes(Connection connection, String databaseName, Stri index.setType(OracleIndexTypeEnum.NORMAL.getName()); } else if ("BITMAP".equalsIgnoreCase(index.getType())) { index.setType(OracleIndexTypeEnum.BITMAP.getName()); - } else if(StringUtils.isNotBlank(index.getType()) && index.getType().toUpperCase().contains("NORMAL")){ + } else if (StringUtils.isNotBlank(index.getType()) && index.getType().toUpperCase().contains("NORMAL")) { index.setType(OracleIndexTypeEnum.NORMAL.getName()); } - if(pkSet.contains(keyName)){ + if (pkSet.contains(keyName)) { index.setType(OracleIndexTypeEnum.PRIMARY_KEY.getName()); } map.put(keyName, index); @@ -185,8 +188,8 @@ private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLExce TableIndexColumn tableIndexColumn = new TableIndexColumn(); tableIndexColumn.setColumnName(resultSet.getString("Column_name")); String expression = resultSet.getString("COLUMN_EXPRESSION"); - if(!StringUtils.isBlank(expression)){ - tableIndexColumn.setColumnName(expression.replace("\"","")); + if (!StringUtils.isBlank(expression)) { + tableIndexColumn.setColumnName(expression.replace("\"", "")); } tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); tableIndexColumn.setCollation(resultSet.getString("Collation")); diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java index 2ce16e7dd..de6142005 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java @@ -47,14 +47,15 @@ public static OracleIndexTypeEnum getByType(String type) { public String buildIndexScript(TableIndex tableIndex) { StringBuilder script = new StringBuilder(); if (PRIMARY_KEY.equals(this)) { - script.append("CREATE PRIMARY KEY "); - } else if (UNIQUE.equals(this)) { - script.append("CREATE UNIQUE INDEX "); + script.append("ALTER TABLE \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ADD PRIMARY KEY ").append(buildIndexColumn(tableIndex)); } else { - script.append("CREATE INDEX "); + if (UNIQUE.equals(this)) { + script.append("CREATE UNIQUE INDEX "); + } else { + script.append("CREATE INDEX "); + } + script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); } - script.append(buildIndexName(tableIndex)).append(" ON \"").append(tableIndex.getSchemaName()).append("\".\"").append(tableIndex.getTableName()).append("\" ").append(buildIndexColumn(tableIndex)); - return script.toString(); } @@ -65,10 +66,10 @@ private String buildIndexColumn(TableIndex tableIndex) { for (TableIndexColumn column : tableIndex.getColumnList()) { if (StringUtils.isNotBlank(column.getColumnName())) { script.append("\"").append(column.getColumnName()).append("\""); - if(!StringUtils.isBlank(column.getAscOrDesc())){ + if (!StringUtils.isBlank(column.getAscOrDesc())) { script.append(" ").append(column.getAscOrDesc()); } - script .append(","); + script.append(","); } } script.deleteCharAt(script.length() - 1); @@ -85,10 +86,10 @@ public String buildModifyIndex(TableIndex tableIndex) { return buildDropIndex(tableIndex); } if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { - return StringUtils.join(buildDropIndex(tableIndex), ";\n" , buildIndexScript(tableIndex)); + return StringUtils.join(buildDropIndex(tableIndex), ";\n", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { - return StringUtils.join( buildIndexScript(tableIndex)); + return StringUtils.join(buildIndexScript(tableIndex)); } return ""; } From 913b7854c07feb81a7e98f9be763cb94b4b4ec36 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 3 Oct 2023 11:13:51 +0800 Subject: [PATCH 0825/1069] add common ai support --- .../chat2db/server/web/api/controller/ai/ChatController.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index d7d311878..8c0d1f491 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -217,13 +217,12 @@ private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sse case CHAT2DBAI: return chatWithChat2dbAi(queryRequest, sseEmitter, uid); case RESTAI : - return chatWithRestAi(queryRequest, sseEmitter); + case FASTCHATAI: + return chatWithFastChatAi(queryRequest, sseEmitter, uid); case AZUREAI : return chatWithAzureAi(queryRequest, sseEmitter, uid); case CLAUDEAI: return chatWithClaudeAi(queryRequest, sseEmitter, uid); - case FASTCHATAI: - return chatWithFastChatAi(queryRequest, sseEmitter, uid); } return chatWithOpenAi(queryRequest, sseEmitter, uid); } From e3b726612cff61f13ec270e7a70141189fcaa3d4 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 3 Oct 2023 16:47:50 +0800 Subject: [PATCH 0826/1069] add common ai support --- .../interceptor/FastChatHeaderAuthorizationInterceptor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java index 3c4dc716b..f91719612 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/interceptor/FastChatHeaderAuthorizationInterceptor.java @@ -1,7 +1,9 @@ package ai.chat2db.server.web.api.controller.ai.fastchat.interceptor; +import cn.hutool.core.util.RandomUtil; import cn.hutool.http.ContentType; import cn.hutool.http.Header; +import com.google.common.collect.Lists; import lombok.Getter; import okhttp3.Interceptor; import okhttp3.Request; @@ -28,7 +30,8 @@ public FastChatHeaderAuthorizationInterceptor(String apiKey) { public Response intercept(Chain chain) throws IOException { Request original = chain.request(); Request request = original.newBuilder() - .header("apiKey", apiKey) // replace to your corresponding field and value + // replace to your corresponding field and value + .header(Header.AUTHORIZATION.getValue(), "Bearer " + apiKey) .header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue()) .method(original.method(), original.body()) .build(); From c2705a67afb65a3eabbe3cad8acbc2a9a1936c35 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 4 Oct 2023 11:28:57 +0800 Subject: [PATCH 0827/1069] add knowledge support --- .../chat2db-server-web-api/pom.xml | 7 +- .../web/api/controller/ai/ChatController.java | 47 ++++++- .../ai/DocParser/AbstractParser.java | 13 ++ .../api/controller/ai/DocParser/PdfParse.java | 46 ++++++ .../controller/ai/KnowledgeController.java | 131 ++++++++++++++++++ .../api/controller/ai/enums/PromptType.java | 5 + .../ai/fastchat/client/FastChatAIClient.java | 5 + .../client/FastChatAIStreamClient.java | 45 ++++++ .../ai/fastchat/client/FastChatOpenAiApi.java | 54 ++++++++ .../embeddings/FastChatEmbedding.java | 73 ++++++++++ .../embeddings/FastChatEmbeddingResponse.java | 22 +++ .../ai/fastchat/embeddings/FastChatItem.java | 14 ++ .../web/api/http/GatewayClientService.java | 48 ++++++- .../server/web/api/http/model/Knowledge.java | 27 ++++ .../web/api/http/model/TableSchema.java | 30 ++++ .../api/http/request/KnowledgeRequest.java | 20 +++ .../api/http/request/TableSchemaRequest.java | 22 +++ .../api/http/response/KnowledgeResponse.java | 18 +++ .../http/response/TableSchemaResponse.java | 18 +++ chat2db-server/pom.xml | 25 ++++ 20 files changed, 662 insertions(+), 8 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/AbstractParser.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/PdfParse.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/Knowledge.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/TableSchema.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/KnowledgeRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/KnowledgeResponse.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/TableSchemaResponse.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml index 69a586af5..0ec584a79 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/pom.xml @@ -64,18 +64,19 @@ com.deepoove poi-tl - 1.10.5 com.itextpdf itext-asian - 5.2.0 com.itextpdf itextpdf - 5.5.13 + + + org.apache.pdfbox + pdfbox diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 8c0d1f491..dcc56ad8e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -33,6 +33,7 @@ import ai.chat2db.server.web.api.controller.ai.azure.listener.AzureOpenAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.claude.listener.ClaudeAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient; +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.fastchat.listener.FastChatAIEventSourceListener; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatMessage; import ai.chat2db.server.web.api.controller.ai.fastchat.model.FastChatRole; @@ -199,7 +200,7 @@ public SseEmitter completions(ChatQueryRequest queryRequest, @RequestHeader Map< * * @return */ - private SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { + public SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException { ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); @@ -453,6 +454,10 @@ private Map> buildTableColumn(TableQueryParam tableQue * @return */ private String buildPrompt(ChatQueryRequest queryRequest) { + if (PromptType.QUESTION_ANSWERING.getCode().equals(queryRequest.getPromptType())) { + return queryRequest.getMessage(); + } + // 查询schema信息 DataResult dataResult = dataSourceService.queryById(queryRequest.getDataSourceId()); String dataSourceType = dataResult.getData().getType(); @@ -487,6 +492,46 @@ private String buildPrompt(ChatQueryRequest queryRequest) { return schemaProperty; } + /** + * distribute embedding with different AI + * + * @return + */ + public FastChatEmbeddingResponse distributeAIEmbedding(String input) throws IOException { + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); + String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); + if (Objects.nonNull(config)) { + aiSqlSource = config.getContent(); + } + AiSqlSourceEnum aiSqlSourceEnum = AiSqlSourceEnum.getByName(aiSqlSource); + if (Objects.isNull(aiSqlSourceEnum)) { + aiSqlSourceEnum = AiSqlSourceEnum.OPENAI; + } + switch (Objects.requireNonNull(aiSqlSourceEnum)) { + case OPENAI : + case CHAT2DBAI: + case RESTAI : + case FASTCHATAI: + case AZUREAI : + case CLAUDEAI: + return distributeAIEmbedding(input); + } + return distributeAIEmbedding(input); + } + + /** + * embedding with fast chat openai + * + * @param input + * @return + * @throws IOException + */ + private FastChatEmbeddingResponse embeddingWithFastChatAi(String input) throws IOException { + FastChatEmbeddingResponse response = FastChatAIClient.getInstance().embeddings(input); + return response; + } + ///** // * 问答对话模型 // * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/AbstractParser.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/AbstractParser.java new file mode 100644 index 000000000..5dbf1007a --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/AbstractParser.java @@ -0,0 +1,13 @@ +package ai.chat2db.server.web.api.controller.ai.DocParser; + +import java.io.InputStream; +import java.util.List; + +/** + * @author CYY + * @date 2023年03月20日 上午8:13 + * @description + */ +public abstract class AbstractParser { + public abstract List parse(InputStream inputStream) throws Exception; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/PdfParse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/PdfParse.java new file mode 100644 index 000000000..ae79abb46 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/DocParser/PdfParse.java @@ -0,0 +1,46 @@ +package ai.chat2db.server.web.api.controller.ai.DocParser; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.text.PDFTextStripper; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * @author CYY + * @date 2023年03月11日 下午3:23 + * @description + */ +public class PdfParse extends AbstractParser { + private static final int MAX_LENGTH = 200; + + @Override + public List parse(InputStream inputStream) throws IOException { + // 打开 PDF 文件 + PDDocument document = PDDocument.load(inputStream); + // 创建 PDFTextStripper 对象 + PDFTextStripper stripper = new PDFTextStripper(); + // 获取文本内容 + String text = stripper.getText(document); + //过滤字符 + text = text.replaceAll("\\s", " ").replaceAll("(\\r\\n|\\r|\\n|\\n\\r)"," "); + String[] sentence = text.split("。"); + List ans = new ArrayList<>(); + for (String s : sentence) { + if (s.length() > MAX_LENGTH) { + for (int index = 0; index < sentence.length; index = (index + 1) * MAX_LENGTH) { + String substring = s.substring(index, MAX_LENGTH); + if(substring.length() < 5) continue; + ans.add(substring); + } + } else { + ans.add(s); + } + } + // 关闭文档 + document.close(); + return ans; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java new file mode 100644 index 000000000..09b6d8357 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java @@ -0,0 +1,131 @@ +package ai.chat2db.server.web.api.controller.ai; + +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.ai.DocParser.AbstractParser; +import ai.chat2db.server.web.api.controller.ai.DocParser.PdfParse; +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.http.GatewayClientService; +import ai.chat2db.server.web.api.http.model.Knowledge; +import ai.chat2db.server.web.api.http.request.KnowledgeRequest; +import ai.chat2db.server.web.api.http.response.KnowledgeResponse; +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson2.JSON; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.math.BigDecimal; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author moji + */ +@RestController +@ConnectionInfoAspect +@RequestMapping("/api/ai/knowledge") +@Slf4j +public class KnowledgeController extends ChatController { + + + /** + * chat的超时时间 + */ + private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); + + + @Resource + private GatewayClientService gatewayClientService; + + /** + * save knowledge embeddings from pdf file + * + * @param file + * @return + * @throws IOException + */ + @PostMapping("/embeddings") + @CrossOrigin + public ActionResult embeddings(MultipartFile file, HttpServletRequest request) + throws Exception { + AbstractParser pdfParse = new PdfParse(); + List sentenceList = pdfParse.parse(file.getInputStream()); + + List contentWordCount = new ArrayList<>(); + List> contentVector = new ArrayList<>(); + for(String str : sentenceList){ + contentWordCount.add(str.length()); + + // request embedding + FastChatEmbeddingResponse response = distributeAIEmbedding(str); + if(response == null){ + continue; + } + contentVector.add(response.getData().get(0).getEmbedding()); + } + + KnowledgeRequest knowledgeRequest = new KnowledgeRequest(); + knowledgeRequest.setContentVector(contentVector); + knowledgeRequest.setSentenceList(sentenceList); + // save knowledge embedding + ActionResult actionResult = gatewayClientService.knowledgeVectorSave(knowledgeRequest); + return actionResult; + } + + /** + * search knowledge embeddings + * + * @param queryRequest + * @return + * @throws IOException + */ + @GetMapping("/search") + @CrossOrigin + public SseEmitter search(ChatQueryRequest queryRequest, @RequestHeader Map headers) + throws Exception { + // request embedding + FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); + List> contentVector = new ArrayList<>(); + contentVector.add(response.getData().get(0).getEmbedding()); + + // search embedding + DataResult result = gatewayClientService.knowledgeVectorSearch(contentVector); + + String prompt = queryRequest.getMessage(); + if (CollectionUtils.isNotEmpty(result.getData().getKnowledgeList())) { + List contents = new ArrayList<>(); + for(Knowledge data: result.getData().getKnowledgeList()){ + contents.add(data.getContent()); + } + + prompt = String.format("基于%s。请回答%s。", JSON.toJSONString(contents), prompt); + queryRequest.setMessage(prompt); + } + + // chat with AI + SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); + String uid = headers.get("uid"); + if (StrUtil.isBlank(uid)) { + throw new ParamBusinessException("uid"); + } + + if (StringUtils.isBlank(queryRequest.getMessage())) { + throw new ParamBusinessException("message"); + } + + return distributeAISql(queryRequest, sseEmitter, uid); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java index 72f213874..99cf52603 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java @@ -33,6 +33,11 @@ public enum PromptType implements BaseEnum { * SQL转换 */ SQL_2_SQL("进行SQL转换"), + + /** + * knowledge qa + */ + QUESTION_ANSWERING("问答"), ; final String description; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java index 710309901..f027b8eca 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatAIClient.java @@ -29,6 +29,11 @@ public class FastChatAIClient { */ public static final String FASTCHAT_MODEL= "fastchat.model"; + /** + * FASTCHAT OPENAI embedding model + */ + public static final String FASTCHAT_embedding_MODEL= "fastchat.embedding.model"; + private static FastChatAIStreamClient FASTCHAT_AI_CLIENT; 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 0e5dd475a..6a08ba117 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 @@ -1,11 +1,14 @@ package ai.chat2db.server.web.api.controller.ai.fastchat.client; import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.fastchat.interceptor.FastChatHeaderAuthorizationInterceptor; 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.ObjectMapper; +import io.reactivex.Single; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import okhttp3.MediaType; @@ -16,6 +19,7 @@ import okhttp3.sse.EventSourceListener; import okhttp3.sse.EventSources; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -50,12 +54,21 @@ public class FastChatAIStreamClient { @Getter private String model; + /** + * embeddingModel + */ + @Getter + private String embeddingModel; + /** * okHttpClient */ @Getter private OkHttpClient okHttpClient; + @Getter + private FastChatOpenAiApi fastChatOpenAiApi; + /** * @param builder @@ -64,6 +77,7 @@ private FastChatAIStreamClient(Builder builder) { this.apiKey = builder.apiKey; this.apiHost = builder.apiHost; this.model = builder.model; + this.embeddingModel = builder.embeddingModel; if (Objects.isNull(builder.okHttpClient)) { builder.okHttpClient = this.okHttpClient(); } @@ -103,6 +117,8 @@ public static final class Builder { private String model; + private String embeddingModel; + /** * OkhttpClient */ @@ -134,6 +150,11 @@ public FastChatAIStreamClient.Builder model(String modelValue) { return this; } + public FastChatAIStreamClient.Builder embeddingModel(String embeddingModelValue) { + this.embeddingModel = embeddingModelValue; + return this; + } + public FastChatAIStreamClient.Builder okHttpClient(OkHttpClient val) { this.okHttpClient = val; return this; @@ -184,4 +205,28 @@ public void streamCompletions(List chatMessages, EventSourceLis } } + /** + * Creates an embedding vector representing the input text. + * + * @param input + * @return EmbeddingResponse + */ + public FastChatEmbeddingResponse embeddings(String input) { + FastChatEmbedding embedding = FastChatEmbedding.builder().input(input).build(); + if (StringUtils.isNotBlank(this.embeddingModel)) { + embedding.setModel(this.embeddingModel); + } + return this.embeddings(embedding); + } + + /** + * Creates an embedding vector representing the input text. + * + * @param embedding + * @return EmbeddingResponse + */ + public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { + Single embeddings = this.fastChatOpenAiApi.embeddings(embedding); + return embeddings.blockingGet(); + } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java new file mode 100644 index 000000000..ed12c5f54 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/client/FastChatOpenAiApi.java @@ -0,0 +1,54 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.client; + +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbedding; +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; +import com.unfbx.chatgpt.entity.billing.CreditGrantsResponse; +import com.unfbx.chatgpt.entity.chat.ChatCompletion; +import com.unfbx.chatgpt.entity.chat.ChatCompletionResponse; +import com.unfbx.chatgpt.entity.common.DeleteResponse; +import com.unfbx.chatgpt.entity.common.OpenAiResponse; +import com.unfbx.chatgpt.entity.completions.Completion; +import com.unfbx.chatgpt.entity.completions.CompletionResponse; +import com.unfbx.chatgpt.entity.edits.Edit; +import com.unfbx.chatgpt.entity.edits.EditResponse; +import com.unfbx.chatgpt.entity.embeddings.Embedding; +import com.unfbx.chatgpt.entity.embeddings.EmbeddingResponse; +import com.unfbx.chatgpt.entity.engines.Engine; +import com.unfbx.chatgpt.entity.files.File; +import com.unfbx.chatgpt.entity.files.UploadFileResponse; +import com.unfbx.chatgpt.entity.fineTune.Event; +import com.unfbx.chatgpt.entity.fineTune.FineTune; +import com.unfbx.chatgpt.entity.fineTune.FineTuneResponse; +import com.unfbx.chatgpt.entity.images.Image; +import com.unfbx.chatgpt.entity.images.ImageResponse; +import com.unfbx.chatgpt.entity.models.Model; +import com.unfbx.chatgpt.entity.models.ModelResponse; +import com.unfbx.chatgpt.entity.moderations.Moderation; +import com.unfbx.chatgpt.entity.moderations.ModerationResponse; +import com.unfbx.chatgpt.entity.whisper.WhisperResponse; +import io.reactivex.Single; +import okhttp3.MultipartBody; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; +import retrofit2.http.*; + +import java.util.Map; + +/** + * 描述: open ai官方api接口 + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +public interface FastChatOpenAiApi { + + /** + * Creates an embedding vector representing the input text. + * + * @param embedding + * @return Single EmbeddingResponse + */ + @POST("v1/embeddings") + Single embeddings(@Body FastChatEmbedding embedding); + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java new file mode 100644 index 000000000..54d147196 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbedding.java @@ -0,0 +1,73 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.unfbx.chatgpt.exception.BaseException; +import com.unfbx.chatgpt.exception.CommonError; +import lombok.*; +import lombok.extern.slf4j.Slf4j; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Getter +@Slf4j +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +@NoArgsConstructor +@AllArgsConstructor +public class FastChatEmbedding implements Serializable { + @NonNull + @Builder.Default + private String model = Model.TEXT_EMBEDDING_ADA_002.getName(); + /** + * 必选项:长度不能超过:8192 + */ + @NonNull + private String input; + + private String user; + + public void setModel(Model model) { + if (Objects.isNull(model)) { + model = Model.TEXT_EMBEDDING_ADA_002; + } + this.model = model.getName(); + } + + public void setModel(String model) { + if (Objects.isNull(model)) { + model = Model.TEXT_EMBEDDING_ADA_002.getName(); + } + this.model = model; + } + + public void setInput(String input) { + if (input == null || "".equals(input)) { + log.error("input不能为空"); + throw new BaseException(CommonError.PARAM_ERROR); + } + if (input.length() > 8192) { + log.error("input超长"); + throw new BaseException(CommonError.PARAM_ERROR); + } + this.input = input; + } + + public void setUser(String user) { + this.user = user; + } + + @Getter + @AllArgsConstructor + public enum Model { + TEXT_EMBEDDING_ADA_002("text-embedding-ada-002"), + ; + private String name; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java new file mode 100644 index 000000000..a364ffc23 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatEmbeddingResponse.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; + +import com.unfbx.chatgpt.entity.common.Usage; +import lombok.Data; + +import java.io.Serializable; +import java.util.List; + +/** + * 描述: + * + * @author https:www.unfbx.com + * 2023-02-15 + */ +@Data +public class FastChatEmbeddingResponse implements Serializable { + + private String object; + private List data; + private String model; + private Usage usage; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java new file mode 100644 index 000000000..d9df8df2b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/fastchat/embeddings/FastChatItem.java @@ -0,0 +1,14 @@ +package ai.chat2db.server.web.api.controller.ai.fastchat.embeddings; + +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +@Data +public class FastChatItem implements Serializable { + private String object; + private List embedding; + private Integer index; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index ec2f1f81c..f94d6262d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -1,13 +1,17 @@ package ai.chat2db.server.web.api.http; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.http.request.KnowledgeRequest; +import ai.chat2db.server.web.api.http.request.TableSchemaRequest; import ai.chat2db.server.web.api.http.response.ApiKeyResponse; import ai.chat2db.server.web.api.http.response.InviteQrCodeResponse; +import ai.chat2db.server.web.api.http.response.KnowledgeResponse; import ai.chat2db.server.web.api.http.response.QrCodeResponse; -import com.dtflys.forest.annotation.BaseRequest; -import com.dtflys.forest.annotation.Get; -import com.dtflys.forest.annotation.Query; -import com.dtflys.forest.annotation.Var; +import com.dtflys.forest.annotation.*; + +import java.math.BigDecimal; +import java.util.List; /** * Gateway 的http 服务 @@ -54,4 +58,40 @@ public interface GatewayClientService { @Get("/api/client/inviteQrCode") DataResult getInviteQrCode(@Query("apiKey") String apiKey); + + /** + * save knowledge vector + * + * @param request + * @return + */ + @Post("/api/milvus/knowledge/save") + ActionResult knowledgeVectorSave(KnowledgeRequest request); + + /** + * save table schema vector + * + * @param request + * @return + */ + @Post("/api/milvus/schema/save") + ActionResult schemaVectorSave(TableSchemaRequest request); + + /** + * save knowledge vector + * + * @param searchVectors + * @return + */ + @Get("/api/milvus/knowledge/search") + DataResult knowledgeVectorSearch(List> searchVectors); + + /** + * save table schema vector + * + * @param searchVectors + * @return + */ + @Get("/api/milvus/schema/search") + DataResult schemaVectorSearch(List> searchVectors); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/Knowledge.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/Knowledge.java new file mode 100644 index 000000000..23a120b81 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/Knowledge.java @@ -0,0 +1,27 @@ +package ai.chat2db.server.web.api.http.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class Knowledge { + + private Long id; + + private String content; + + private String contentVector; + + private Integer wordCount; + + public Knowledge(Long id, String content, Integer wordCount) { + this.id = id; + this.content = content; + this.wordCount = wordCount; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/TableSchema.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/TableSchema.java new file mode 100644 index 000000000..4625e7c26 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/TableSchema.java @@ -0,0 +1,30 @@ +package ai.chat2db.server.web.api.http.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TableSchema { + + private Long id; + + private Long dataSourceId; + + private String tableSchema; + + private String tableSchemaVector; + + private Integer wordCount; + + public TableSchema(Long id, Long dataSourceId, String tableSchema, Integer wordCount) { + this.id = id; + this.dataSourceId = dataSourceId; + this.tableSchema = tableSchema; + this.wordCount = wordCount; + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/KnowledgeRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/KnowledgeRequest.java new file mode 100644 index 000000000..9ea0a05f7 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/KnowledgeRequest.java @@ -0,0 +1,20 @@ +package ai.chat2db.server.web.api.http.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeRequest { + + private List> contentVector; + + private List sentenceList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java new file mode 100644 index 000000000..60a4528f4 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java @@ -0,0 +1,22 @@ +package ai.chat2db.server.web.api.http.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.math.BigDecimal; +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TableSchemaRequest { + + private Long dataSourceId; + + private List> contentVector; + + private List sentenceList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/KnowledgeResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/KnowledgeResponse.java new file mode 100644 index 000000000..c2bb4dcaa --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/KnowledgeResponse.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.http.response; + +import ai.chat2db.server.web.api.http.model.Knowledge; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class KnowledgeResponse { + + private List knowledgeList; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/TableSchemaResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/TableSchemaResponse.java new file mode 100644 index 000000000..f1d9b6c43 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/TableSchemaResponse.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.http.response; + +import ai.chat2db.server.web.api.http.model.TableSchema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class TableSchemaResponse { + + private List tableSchemas; +} diff --git a/chat2db-server/pom.xml b/chat2db-server/pom.xml index 6990637ac..cb5b08dba 100644 --- a/chat2db-server/pom.xml +++ b/chat2db-server/pom.xml @@ -264,6 +264,31 @@ easyexcel 3.3.2 + + + + com.deepoove + poi-tl + 1.10.5 + + + + com.itextpdf + itext-asian + 5.2.0 + + + com.itextpdf + itextpdf + 5.5.13 + + + + + org.apache.pdfbox + pdfbox + 2.0.24 + From cd0b4b5e8768425af5ffe5dbfab15fa75732ddcb Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 4 Oct 2023 11:34:45 +0800 Subject: [PATCH 0828/1069] add knowledge support --- .../chat2db/server/web/api/controller/ai/ChatController.java | 2 +- .../server/web/api/controller/ai/KnowledgeController.java | 3 ++- .../server/web/api/controller/ai/enums/PromptType.java | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index dcc56ad8e..bcf47c062 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -454,7 +454,7 @@ private Map> buildTableColumn(TableQueryParam tableQue * @return */ private String buildPrompt(ChatQueryRequest queryRequest) { - if (PromptType.QUESTION_ANSWERING.getCode().equals(queryRequest.getPromptType())) { + if (PromptType.TEXT_GENERATION.getCode().equals(queryRequest.getPromptType())) { return queryRequest.getMessage(); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java index 09b6d8357..5eed686f4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java @@ -6,6 +6,7 @@ import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.ai.DocParser.AbstractParser; import ai.chat2db.server.web.api.controller.ai.DocParser.PdfParse; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.http.GatewayClientService; @@ -102,7 +103,7 @@ public SseEmitter search(ChatQueryRequest queryRequest, @RequestHeader Map result = gatewayClientService.knowledgeVectorSearch(contentVector); - + queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); String prompt = queryRequest.getMessage(); if (CollectionUtils.isNotEmpty(result.getData().getKnowledgeList())) { List contents = new ArrayList<>(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java index 99cf52603..9e9745c75 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/enums/PromptType.java @@ -35,9 +35,9 @@ public enum PromptType implements BaseEnum { SQL_2_SQL("进行SQL转换"), /** - * knowledge qa + * text generation */ - QUESTION_ANSWERING("问答"), + TEXT_GENERATION("文本生成"), ; final String description; From 0ce5d770717443ca147497b0b11e5b4b381f6556 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 4 Oct 2023 12:09:31 +0800 Subject: [PATCH 0829/1069] add prompt support --- .../web/api/controller/ai/ChatController.java | 57 +++++++++++-- .../ai/TextGenerationController.java | 85 +++++++++++++++++++ .../web/api/http/GatewayClientService.java | 7 +- 3 files changed, 139 insertions(+), 10 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index bcf47c062..478a17e5f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -1,6 +1,7 @@ package ai.chat2db.server.web.api.controller.ai; import java.io.IOException; +import java.math.BigDecimal; import java.time.Duration; import java.time.LocalDateTime; import java.util.ArrayList; @@ -42,14 +43,19 @@ import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; import ai.chat2db.server.web.api.controller.ai.request.ChatRequest; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; +import ai.chat2db.server.web.api.http.GatewayClientService; +import ai.chat2db.server.web.api.http.model.TableSchema; +import ai.chat2db.server.web.api.http.response.TableSchemaResponse; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; import ai.chat2db.spi.model.TableColumn; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; +import com.alibaba.fastjson2.JSON; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.unfbx.chatgpt.entity.chat.Message; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -91,6 +97,9 @@ public class ChatController { @Value("${chatgpt.version}") private String gptVersion; + @Resource + private GatewayClientService gatewayClientService; + /** * chat的超时时间 */ @@ -459,11 +468,7 @@ private String buildPrompt(ChatQueryRequest queryRequest) { } // 查询schema信息 - DataResult dataResult = dataSourceService.queryById(queryRequest.getDataSourceId()); - String dataSourceType = dataResult.getData().getType(); - if (StringUtils.isBlank(dataSourceType)) { - dataSourceType = "MYSQL"; - } + String dataSourceType = queryDatabaseType(queryRequest); TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); Map> tableColumns = buildTableColumn(queryParam, queryRequest.getTableNames()); List tableSchemas = tableColumns.entrySet().stream().map( @@ -492,6 +497,48 @@ private String buildPrompt(ChatQueryRequest queryRequest) { return schemaProperty; } + /** + * query database type + * + * @param queryRequest + * @return + */ + public String queryDatabaseType(ChatQueryRequest queryRequest) { + // 查询schema信息 + DataResult dataResult = dataSourceService.queryById(queryRequest.getDataSourceId()); + String dataSourceType = dataResult.getData().getType(); + if (StringUtils.isBlank(dataSourceType)) { + dataSourceType = "MYSQL"; + } + return dataSourceType; + } + + + /** + * query database schema + * + * @param queryRequest + * @return + * @throws IOException + */ + public String queryDatabaseSchema(ChatQueryRequest queryRequest) throws IOException { + // request embedding + FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); + List> contentVector = new ArrayList<>(); + contentVector.add(response.getData().get(0).getEmbedding()); + + // search embedding + DataResult result = gatewayClientService.schemaVectorSearch(contentVector); + + List schemas = Lists.newArrayList(); + if (CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { + for(TableSchema data: result.getData().getTableSchemas()){ + schemas.add(data.getTableSchema()); + } + } + return JSON.toJSONString(schemas); + } + /** * distribute embedding with different AI * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java new file mode 100644 index 000000000..066fb37f3 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java @@ -0,0 +1,85 @@ +package ai.chat2db.server.web.api.controller.ai; + +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.ai.enums.PromptType; +import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest; +import ai.chat2db.server.web.api.http.GatewayClientService; +import cn.hutool.core.util.StrUtil; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.time.Duration; +import java.util.Map; + +/** + * @author moji + */ +@RestController +@ConnectionInfoAspect +@RequestMapping("/api/ai/text/generation") +@Slf4j +public class TextGenerationController extends ChatController { + + + /** + * chat的超时时间 + */ + private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); + + + @Resource + private GatewayClientService gatewayClientService; + + /** + * sql auto complete + * + * @param queryRequest + * @return + * @throws IOException + */ + @GetMapping("/prompt") + @CrossOrigin + public SseEmitter prompt(ChatQueryRequest queryRequest, @RequestHeader Map headers) + throws Exception { + queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); + String promptTemplate = "### Instructions:\n" + + "Your task is generate a SQL query according to the prompt, given a %s database schema.\n" + + "Adhere to these rules:\n" + + "- **Deliberately go through the prompt and database schema word by word** to appropriately answer the question\n" + + "- **Use Table Aliases** to prevent ambiguity. For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.\n" + + "\n" + + "### Input:\n" + + "Generate a SQL query according to the prompt `%s`.\n" + + "This query will run on a database whose schema is represented in this string:\n" + + "{%s}\n" + + "\n" + + "### Response:\n" + + "Based on your instructions, here is the SQL query I have generated to complete the prompt `{%s}`:\n" + + "```sql"; + + // query database schema info + String databaseType = queryDatabaseType(queryRequest); + String schemas = queryDatabaseSchema(queryRequest); + String prompt = String.format(promptTemplate, databaseType, queryRequest.getMessage(), schemas, queryRequest.getMessage()); + queryRequest.setMessage(prompt); + + // chat with AI + SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT); + String uid = headers.get("uid"); + if (StrUtil.isBlank(uid)) { + throw new ParamBusinessException("uid"); + } + + if (StringUtils.isBlank(queryRequest.getMessage())) { + throw new ParamBusinessException("message"); + } + + return distributeAISql(queryRequest, sseEmitter, uid); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index f94d6262d..445acb730 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -4,10 +4,7 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.http.request.KnowledgeRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; -import ai.chat2db.server.web.api.http.response.ApiKeyResponse; -import ai.chat2db.server.web.api.http.response.InviteQrCodeResponse; -import ai.chat2db.server.web.api.http.response.KnowledgeResponse; -import ai.chat2db.server.web.api.http.response.QrCodeResponse; +import ai.chat2db.server.web.api.http.response.*; import com.dtflys.forest.annotation.*; import java.math.BigDecimal; @@ -93,5 +90,5 @@ public interface GatewayClientService { * @return */ @Get("/api/milvus/schema/search") - DataResult schemaVectorSearch(List> searchVectors); + DataResult schemaVectorSearch(List> searchVectors); } From cd2447971010419fa829571e4b020583dd1c2cb3 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 4 Oct 2023 12:18:26 +0800 Subject: [PATCH 0830/1069] add prompt support --- .../api/controller/ai/TextGenerationController.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java index 066fb37f3..506e007d9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java @@ -47,16 +47,16 @@ public class TextGenerationController extends ChatController { public SseEmitter prompt(ChatQueryRequest queryRequest, @RequestHeader Map headers) throws Exception { queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); + String promptTemplate = "### Instructions:\n" + - "Your task is generate a SQL query according to the prompt, given a %s database schema.\n" + + "Your task is generate a SQL query according to the prompt %s.\n" + "Adhere to these rules:\n" + "- **Deliberately go through the prompt and database schema word by word** to appropriately answer the question\n" + "- **Use Table Aliases** to prevent ambiguity. For example, `SELECT table1.col1, table2.col1 FROM table1 JOIN table2 ON table1.id = table2.id`.\n" + "\n" + "### Input:\n" + "Generate a SQL query according to the prompt `%s`.\n" + - "This query will run on a database whose schema is represented in this string:\n" + - "{%s}\n" + + "%s\n" + "\n" + "### Response:\n" + "Based on your instructions, here is the SQL query I have generated to complete the prompt `{%s}`:\n" + @@ -65,6 +65,13 @@ public SseEmitter prompt(ChatQueryRequest queryRequest, @RequestHeader Map Date: Wed, 4 Oct 2023 14:55:50 +0800 Subject: [PATCH 0831/1069] query schema update --- .../chat2db/server/web/api/controller/ai/ChatController.java | 2 +- .../ai/chat2db/server/web/api/http/GatewayClientService.java | 3 ++- .../server/web/api/http/request/TableSchemaRequest.java | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 478a17e5f..77cf9bdec 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -528,7 +528,7 @@ public String queryDatabaseSchema(ChatQueryRequest queryRequest) throws IOExcept contentVector.add(response.getData().get(0).getEmbedding()); // search embedding - DataResult result = gatewayClientService.schemaVectorSearch(contentVector); + DataResult result = gatewayClientService.schemaVectorSearch(contentVector, queryRequest.getDataSourceId()); List schemas = Lists.newArrayList(); if (CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index 445acb730..fbbf3308f 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -87,8 +87,9 @@ public interface GatewayClientService { * save table schema vector * * @param searchVectors + * @param datasourceId * @return */ @Get("/api/milvus/schema/search") - DataResult schemaVectorSearch(List> searchVectors); + DataResult schemaVectorSearch(List> searchVectors, Long datasourceId); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java index 60a4528f4..b74256c8c 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java @@ -19,4 +19,6 @@ public class TableSchemaRequest { private List> contentVector; private List sentenceList; + + private Boolean deleteBeforeInsert; } From 2f4f027c9bd885161084c50e0f2e5a37e8ebe7f8 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 4 Oct 2023 15:46:58 +0800 Subject: [PATCH 0832/1069] save schema embedding update --- .../web/api/controller/ai/ChatController.java | 11 +- .../controller/ai/EmbeddingController.java | 175 ++++++++++++++++++ .../controller/ai/KnowledgeController.java | 4 +- .../web/api/http/GatewayClientService.java | 7 +- .../api/http/request/TableSchemaRequest.java | 6 +- 5 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 77cf9bdec..38367d89e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -45,6 +45,7 @@ import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.http.GatewayClientService; import ai.chat2db.server.web.api.http.model.TableSchema; +import ai.chat2db.server.web.api.http.request.TableSchemaRequest; import ai.chat2db.server.web.api.http.response.TableSchemaResponse; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; @@ -528,7 +529,15 @@ public String queryDatabaseSchema(ChatQueryRequest queryRequest) throws IOExcept contentVector.add(response.getData().get(0).getEmbedding()); // search embedding - DataResult result = gatewayClientService.schemaVectorSearch(contentVector, queryRequest.getDataSourceId()); + TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); + tableSchemaRequest.setSchemaVector(contentVector); + tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); + String databaseName = StringUtils.isNotBlank(queryRequest.getDatabaseName()) ? queryRequest.getDatabaseName() : queryRequest.getSchemaName(); + if (Objects.isNull(databaseName)) { + databaseName = ""; + } + tableSchemaRequest.setDatabaseName(databaseName); + DataResult result = gatewayClientService.schemaVectorSearch(tableSchemaRequest); List schemas = Lists.newArrayList(); if (CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java new file mode 100644 index 000000000..e3d9b033d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java @@ -0,0 +1,175 @@ +package ai.chat2db.server.web.api.controller.ai; + +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; +import ai.chat2db.server.domain.api.param.TablePageQueryParam; +import ai.chat2db.server.domain.api.param.TableSelector; +import ai.chat2db.server.domain.api.service.TableService; +import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.base.wrapper.result.PageResult; +import ai.chat2db.server.tools.common.exception.ParamBusinessException; +import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse; +import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; +import ai.chat2db.server.web.api.http.GatewayClientService; +import ai.chat2db.server.web.api.http.request.TableSchemaRequest; +import ai.chat2db.spi.model.Table; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author moji + */ +@RestController +@ConnectionInfoAspect +@RequestMapping("/api/ai/embedding") +@Slf4j +public class EmbeddingController extends ChatController { + + + @Resource + private GatewayClientService gatewayClientService; + + @Autowired + private RdbWebConverter rdbWebConverter; + + @Autowired + private TableService tableService; + + /** + * save knowledge embeddings from pdf file + * + * @param request + * @return + * @throws IOException + */ + @PostMapping("/datasource") + @CrossOrigin + public ActionResult embeddings(@Valid TableBriefQueryRequest request) + throws Exception { + + // query tables + request.setPageSize(1000); + TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(false); + tableSelector.setIndexList(false); + PageResult
    tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); + + List
    tables = tableDTOPageResult.getData(); + if (CollectionUtils.isEmpty(tables)) { + return ActionResult.isSuccess(); + } + + String tableName = tables.get(0).getName(); + String tableSchema = queryTableDdl(tableName, request); + + if (StringUtils.isBlank(tableSchema)) { + throw new ParamBusinessException("tableSchema is empty"); + } + + // save first table embedding + TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); + tableSchemaRequest.setDataSourceId(request.getDataSourceId()); + tableSchemaRequest.setDeleteBeforeInsert(true); + String databaseName = StringUtils.isNotBlank(request.getDatabaseName()) ? request.getDatabaseName() : request.getSchemaName(); + if (Objects.isNull(databaseName)) { + databaseName = ""; + } + tableSchemaRequest.setDatabaseName(databaseName); + + saveTableEmbedding(tableSchema, tableSchemaRequest); + + // save other table embedding + tableSchemaRequest.setDeleteBeforeInsert(false); + for (int i = 1; i < tables.size(); i++) { + tableName = tables.get(i).getName(); + tableSchema = queryTableDdl(tableName, request); + if (StringUtils.isBlank(tableSchema)) { + continue; + } + saveTableEmbedding(tableSchema, tableSchemaRequest); + } + + // query all the tables + Long totalTableCount = tableDTOPageResult.getTotal(); + Integer pageSize = queryParam.getPageSize(); + if (pageSize < totalTableCount) { + for (int i = 2; i < totalTableCount/pageSize + 1; i++) { + queryParam.setPageNo(i); + tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); + tables = tableDTOPageResult.getData(); + for (Table table : tables) { + tableName = table.getName(); + tableSchema = queryTableDdl(tableName, request); + if (StringUtils.isBlank(tableSchema)) { + continue; + } + saveTableEmbedding(tableSchema, tableSchemaRequest); + } + } + } + + return ActionResult.isSuccess(); + } + + /** + * save table embedding + * + * @param tableSchema + * @param tableSchemaRequest + * @throws Exception + */ + private void saveTableEmbedding(String tableSchema, TableSchemaRequest tableSchemaRequest) throws Exception{ + List schemaList = Lists.newArrayList(tableSchema); + tableSchemaRequest.setSchemaList(schemaList); + + List> contentVector = new ArrayList<>(); + for(String str : schemaList){ + // request embedding + FastChatEmbeddingResponse response = distributeAIEmbedding(str); + if(response == null){ + continue; + } + contentVector.add(response.getData().get(0).getEmbedding()); + } + tableSchemaRequest.setSchemaVector(contentVector); + + // save table embedding + gatewayClientService.schemaVectorSave(tableSchemaRequest); + } + + /** + * query table schema + * + * @param tableName + * @param request + * @return + */ + private String queryTableDdl(String tableName, TableBriefQueryRequest request) { + ShowCreateTableParam param = new ShowCreateTableParam(); + param.setTableName(tableName); + param.setDataSourceId(request.getDataSourceId()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + DataResult tableSchema = tableService.showCreateTable(param); + return tableSchema.getData(); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java index 5eed686f4..e4a35c393 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java @@ -102,7 +102,9 @@ public SseEmitter search(ChatQueryRequest queryRequest, @RequestHeader Map result = gatewayClientService.knowledgeVectorSearch(contentVector); + KnowledgeRequest knowledgeRequest = new KnowledgeRequest(); + knowledgeRequest.setContentVector(contentVector); + DataResult result = gatewayClientService.knowledgeVectorSearch(knowledgeRequest); queryRequest.setPromptType(PromptType.TEXT_GENERATION.getCode()); String prompt = queryRequest.getMessage(); if (CollectionUtils.isNotEmpty(result.getData().getKnowledgeList())) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index fbbf3308f..4f5017286 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -81,15 +81,14 @@ public interface GatewayClientService { * @return */ @Get("/api/milvus/knowledge/search") - DataResult knowledgeVectorSearch(List> searchVectors); + DataResult knowledgeVectorSearch(KnowledgeRequest searchVectors); /** * save table schema vector * - * @param searchVectors - * @param datasourceId + * @param request * @return */ @Get("/api/milvus/schema/search") - DataResult schemaVectorSearch(List> searchVectors, Long datasourceId); + DataResult schemaVectorSearch(TableSchemaRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java index b74256c8c..3747f5d5d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java @@ -16,9 +16,11 @@ public class TableSchemaRequest { private Long dataSourceId; - private List> contentVector; + private String databaseName; - private List sentenceList; + private List> schemaVector; + + private List schemaList; private Boolean deleteBeforeInsert; } From 0addeedbe27084294105b439a20ac103cc30a392 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 7 Oct 2023 11:08:59 +0800 Subject: [PATCH 0833/1069] feat:web supports PWA --- chat2db-client/.umirc.ts | 20 +++++++++++ chat2db-client/public/logo_192.png | Bin 0 -> 79918 bytes chat2db-client/public/logo_512.png | Bin 0 -> 381327 bytes chat2db-client/public/manifest.json | 23 ++++++++++++ chat2db-client/public/sw.js | 54 ++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 chat2db-client/public/logo_192.png create mode 100644 chat2db-client/public/logo_512.png create mode 100644 chat2db-client/public/manifest.json create mode 100644 chat2db-client/public/sw.js diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index d2c9db13f..64d0866f1 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -49,12 +49,32 @@ export default defineConfig({ targets: { chrome: 80, }, + links: [{ + rel: 'manifest', + href: 'manifest.json', + }], headScripts: [ `if (localStorage.getItem('app-local-storage-versions') !== 'v2') { localStorage.clear(); localStorage.setItem('app-local-storage-versions', 'v2'); }`, `if (window.myAPI) { window.myAPI.startServerForSpawn() }`, + `if ("serviceWorker" in navigator) { + window.addEventListener("load", function () { + navigator.serviceWorker + .register("sw.js") + .then(res => console.log("service worker registered")) + .catch(err => console.log("service worker not registered", err)); + }) + }`, + `var deferredPrompt = null; + window.addEventListener("beforeinstallprompt", e => { + e.preventDefault(); + deferredPrompt = e; + }); + window.addEventListener("appinstalled", () => { + deferredPrompt = null; + })`, { src: 'https://www.googletagmanager.com/gtag/js?id=G-V8M4E5SF61', async: true, diff --git a/chat2db-client/public/logo_192.png b/chat2db-client/public/logo_192.png new file mode 100644 index 0000000000000000000000000000000000000000..73066f81516bf15e34e3437b89765afac74436b6 GIT binary patch literal 79918 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 literal 0 HcmV?d00001 diff --git a/chat2db-client/public/logo_512.png b/chat2db-client/public/logo_512.png new file mode 100644 index 0000000000000000000000000000000000000000..286b969b165c1b125bda3094964a50de60fbe501 GIT binary patch literal 381327 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�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

    VS+Vnhh(6k~!;$ni_>)9e^5 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 z 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

    5o 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%0AY7b1;3 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%0DV^izwA*{EZmmcK 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 literal 0 HcmV?d00001 diff --git a/chat2db-client/public/manifest.json b/chat2db-client/public/manifest.json new file mode 100644 index 000000000..7fe485066 --- /dev/null +++ b/chat2db-client/public/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Chat2DB-WPA", + "short_name": "Chat2DB", + "description": "Chat2DB-WPA", + "icons": [ + { + "src": "/logo_192.png", + "sizes": "any", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/logo_512.png", + "type": "image/png" + } + ], + "background_color": "#fff", + "theme_color": "#fff", + "display": "fullscreen", + "orientation": "portrait", + "start_url": "/", + "scope": "/" +} \ No newline at end of file diff --git a/chat2db-client/public/sw.js b/chat2db-client/public/sw.js new file mode 100644 index 000000000..ee08643fc --- /dev/null +++ b/chat2db-client/public/sw.js @@ -0,0 +1,54 @@ +// cache名, +const cacheName = 'cache'; +// cache文件 +const cacheFiles = ['/']; + +/** + * 安装 Service Worker + * install事件是 Service Worker 执行的第一个事件,同一个 Service Worker 只会调用一次 + * 即使 Service Worker 脚本文件只有一个字节不同,浏览器也将视为一个新的 Service Worker + */ +self.addEventListener('install', (e) => { + e.waitUntil( + caches.open(cacheName).then((cache) => { + return cache.addAll(cacheFiles); + }), + ); +}); + +/** + * 激活 Service Worker + * Service Worker 安装成功之后,会触发activate事件 + * 在这个阶段我们一般做一些清理旧缓存相关的工作 + */ +self.addEventListener('activate', (e) => { + // e.waitUntil(caches.delete(cacheName)); + e.waitUntil( + caches + .keys() + .then((keys) => { + return Promise.all( + keys.map((key) => { + // 清理缓存 + if (cacheName !== key) { + return caches.delete(key); + } + }), + ); + }) + .then(() => { + console.log('cache deleted'); + }), + ); +}); + +self.addEventListener('fetch', (event) => { + event.respondWith( + caches + .open(cacheName) + .then((cache) => cache.match(event.request, { ignoreSearch: true })) + .then((response) => { + return response || fetch(event.request); + }), + ); +}); From 60800d6848acb1444310ccb11de7cace4c9a3ecd Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sat, 7 Oct 2023 19:26:18 +0800 Subject: [PATCH 0834/1069] support select result data update --- .../plugin/sqlserver/builder/form.json | 0 .../domain/api/service/DlTemplateService.java | 9 ++ .../core/impl/DlTemplateServiceImpl.java | 101 ++++++++++++++---- .../ai/chat2db/spi/enums/DataTypeEnum.java | 58 ++++++++++ .../chat2db/spi/jdbc/DefaultMetaService.java | 10 ++ .../java/ai/chat2db/spi/model/ColumnType.java | 4 +- .../ai/chat2db/spi/model/ExecuteResult.java | 6 ++ 7 files changed, 167 insertions(+), 21 deletions(-) delete mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/form.json diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/form.json b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/form.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java index f5a15e297..a3836e74f 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DlTemplateService.java @@ -2,6 +2,7 @@ import ai.chat2db.server.domain.api.param.DlCountParam; import ai.chat2db.server.domain.api.param.DlExecuteParam; +import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; @@ -32,4 +33,12 @@ public interface DlTemplateService { */ DataResult count(DlCountParam param); + + /** + * 更新查询结果 + * @param param + * @return + */ + DataResult updateSelectResult(UpdateSelectResultParam param); + } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index c61644bc3..2e94e3528 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -6,6 +6,8 @@ import java.util.List; import java.util.Optional; +import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; +import ai.chat2db.spi.MetaData; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.PagerUtils; import com.alibaba.druid.sql.SQLUtils; @@ -52,12 +54,12 @@ public ListResult execute(DlExecuteParam param) { return ListResult.empty(); } // 解析sql - SqlAnalyseParam sqlAnalyseParam = new SqlAnalyseParam(); - sqlAnalyseParam.setDataSourceId(param.getDataSourceId()); - sqlAnalyseParam.setSql(param.getSql()); + RemoveSpecialGO(param); DbType dbType = - JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); + JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); List sqlList = SqlUtils.parse(param.getSql(), dbType); + + if (CollectionUtils.isEmpty(sqlList)) { throw new BusinessException("dataSource.sqlAnalysisError"); } @@ -77,6 +79,16 @@ public ListResult execute(DlExecuteParam param) { return listResult; } + private void RemoveSpecialGO(DlExecuteParam param) { + String sql = param.getSql(); + if (StringUtils.isBlank(sql)) { + return; + } + sql = sql.replaceAll("(?i)\\s*go\\s*", ";"); + param.setSql(sql); + } + + private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecuteParam param) { int pageNo = 1; int pageSize = 0; @@ -103,12 +115,13 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar ExecuteResult executeResult = execute(originalSql, offset, count); executeResult.setSqlType(sqlType); executeResult.setOriginalSql(originalSql); + executeResult.setCanEdit(SqlUtils.canEdit(originalSql)); if (SqlTypeEnum.SELECT.getCode().equals(sqlType)) { executeResult.setPageNo(pageNo); executeResult.setPageSize(pageSize); executeResult.setHasNextPage( - CollectionUtils.size(executeResult.getDataList()) >= executeResult.getPageSize()); + CollectionUtils.size(executeResult.getDataList()) >= executeResult.getPageSize()); } else { executeResult.setPageNo(pageNo); executeResult.setPageSize(CollectionUtils.size(executeResult.getDataList())); @@ -118,9 +131,9 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar // Splice row numbers List

    newHeaderList = new ArrayList<>(); newHeaderList.add(Header.builder() - .name(I18nUtils.getMessage("sqlResult.rowNumber")) - .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER - .getCode()).build()); + .name(I18nUtils.getMessage("sqlResult.rowNumber")) + .dataType(DataTypeEnum.CHAT2DB_ROW_NUMBER + .getCode()).build()); if (executeResult.getHeaderList() != null) { newHeaderList.addAll(executeResult.getHeaderList()); } @@ -158,7 +171,7 @@ public DataResult count(DlCountParam param) { return DataResult.of(0L); } DbType dbType = - JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); + JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); String sql = param.getSql(); // 解析sql分页 SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); @@ -173,14 +186,66 @@ public DataResult count(DlCountParam param) { return DataResult.of(0L); } String count = EasyCollectionUtils.stream(executeResult.getDataList()) - .findFirst() - .orElse(Collections.emptyList()) - .stream() - .findFirst() - .orElse("0"); + .findFirst() + .orElse(Collections.emptyList()) + .stream() + .findFirst() + .orElse("0"); return DataResult.of(Long.valueOf(count)); } + @Override + public DataResult updateSelectResult(UpdateSelectResultParam param) { + StringBuilder stringBuilder = new StringBuilder(); + MetaData metaSchema = Chat2DBContext.getMetaData(); + for (int i = 0; i < param.getDataList().size(); i++) { + List row = param.getDataList().get(i); + if (CollectionUtils.isEmpty(row)) { + continue; + } + List odlRow = param.getOldDataList().get(i); + + String sql = getUpdateSql(param, row, odlRow, metaSchema); + stringBuilder.append(sql+";\n"); + } + return DataResult.of(stringBuilder.toString()); + } + + private String getUpdateSql(UpdateSelectResultParam param, List row, List odlRow, MetaData metaSchema) { + StringBuilder script = new StringBuilder(); + script.append("UPDATE ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) + .append(" set "); + for (int i = 0; i < row.size(); i++) { + String newValue = row.get(i); + String oldValue = odlRow.get(i); + if (StringUtils.equals(newValue, oldValue)) { + continue; + } + Header header = param.getHeaderList().get(i); + String newSqlValue = SqlUtils.getSqlValue(newValue, header.getDataType()); + script.append(metaSchema.getMetaDataName(header.getName())) + .append(" = ") + .append(newSqlValue) + .append(","); + } + script.deleteCharAt(script.length() - 1); + script.append(" where "); + for (int i = 0; i < odlRow.size(); i++) { + String oldValue = odlRow.get(i); + if (oldValue == null) { + continue; + } + Header header = param.getHeaderList().get(i); + script.append(metaSchema.getMetaDataName(header.getName())) + .append(" = ") + .append(SqlUtils.getSqlValue(oldValue, header.getDataType())) + .append(" and "); + } + + script.delete(script.length() - 4, script.length()); + return script.toString(); + } + private ExecuteResult execute(String sql, Integer offset, Integer count) { ExecuteResult executeResult; try { @@ -188,10 +253,10 @@ private ExecuteResult execute(String sql, Integer offset, Integer count) { } catch (SQLException e) { log.warn("执行sql:{}异常", sql, e); executeResult = ExecuteResult.builder() - .sql(sql) - .success(Boolean.FALSE) - .message(e.getMessage()) - .build(); + .sql(sql) + .success(Boolean.FALSE) + .message(e.getMessage()) + .build(); } return executeResult; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java index 9cfbc3b4c..6376cc77b 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/enums/DataTypeEnum.java @@ -96,4 +96,62 @@ public enum DataTypeEnum implements BaseEnum { public String getCode() { return this.name(); } + + public static DataTypeEnum getByCode(String code) { + for (DataTypeEnum value : DataTypeEnum.values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return DataTypeEnum.UNKNOWN; + } + + public String getSqlValue(String value) { + if (this == DataTypeEnum.BOOLEAN) { + return value; + } + if (this == DataTypeEnum.NUMERIC) { + return value; + } + if (this == DataTypeEnum.STRING) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.DATETIME) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.BINARY) { + return "''"; + } + if (this == DataTypeEnum.CONTENT) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.STRUCT) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.DOCUMENT) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.ARRAY) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.OBJECT) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.REFERENCE) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.ROWID) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.ANY) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.UNKNOWN) { + return "'" + value + "'"; + } + if (this == DataTypeEnum.CHAT2DB_ROW_NUMBER) { + return "'" + value + "'"; + } + return "'" + value + "'"; + } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index bdd4aa780..193d0ff3e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -1,8 +1,10 @@ package ai.chat2db.spi.jdbc; import java.sql.Connection; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.SqlBuilder; @@ -105,4 +107,12 @@ public SqlBuilder getSqlBuilder() { public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return null; } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).collect(Collectors.joining(".")); + } + + + } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java index 5d4862f3d..3fd15c23a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ColumnType.java @@ -6,7 +6,6 @@ @Data @AllArgsConstructor public class ColumnType { - private String typeName; private boolean supportLength; private boolean supportScale; @@ -17,8 +16,7 @@ public class ColumnType { private boolean supportComments; private boolean supportDefaultValue; private boolean supportExtent; - private boolean supportValue; - private boolean supportUnit; + } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java index e720f9040..552bcb8a8 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java @@ -94,4 +94,10 @@ public class ExecuteResult { * 执行持续时间 */ private Long duration; + + + /** + * 返回结果是否可以编辑 + */ + private boolean canEdit; } From 67c9174f08e0dda39fb8760c1c7183bfe281fd76 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sat, 7 Oct 2023 19:27:06 +0800 Subject: [PATCH 0835/1069] support sqlserver metadata update --- .../chat2db-plugins/chat2db-mariadb/pom.xml | 5 + .../plugin/mariadb/MariaDBMetaData.java | 211 +++++------ .../chat2db/plugin/mysql/MysqlMetaData.java | 18 +- .../chat2db/plugin/oracle/OracleMetaData.java | 5 + .../oracle/builder/OracleSqlBuilder.java | 3 + .../chat2db/plugin/sqlite/SqliteMetaData.java | 7 + .../chat2db-plugins/chat2db-sqlserver/pom.xml | 6 + .../builder/SqlServerSqlBuilder.java | 124 +++++++ .../plugin/sqlserver/builder/item.json | 0 .../type/SqlServerColumnTypeEnum.java | 337 ++++++++++++++++++ .../type/SqlServerIndexTypeEnum.java | 119 +++++++ .../api/param/UpdateSelectResultParam.java | 60 ++++ .../domain/core/impl/TableServiceImpl.java | 14 + .../api/controller/rdb/RdbDmlController.java | 16 + .../api/controller/rdb/TableController.java | 2 + .../rdb/converter/RdbWebConverter.java | 20 +- .../request/SelectResultUpdateRequest.java | 44 +++ .../main/java/ai/chat2db/spi/MetaData.java | 26 +- .../ai/chat2db/spi/model/TableColumn.java | 21 +- .../java/ai/chat2db/spi/util/SqlUtils.java | 173 +++++---- 20 files changed, 1004 insertions(+), 207 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java delete mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/item.json create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml b/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml index a73c0c597..c34a9f660 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/pom.xml @@ -15,6 +15,11 @@ ai.chat2db chat2db-spi + + ai.chat2db + chat2db-mysql + 2.0.0-SNAPSHOT + chat2db-mariadb diff --git a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java index b2c488052..ccfca0793 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java +++ b/chat2db-server/chat2db-plugins/chat2db-mariadb/src/main/java/ai/chat2db/plugin/mariadb/MariaDBMetaData.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; +import ai.chat2db.plugin.mysql.MysqlMetaData; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Function; @@ -13,109 +14,109 @@ import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; -public class MariaDBMetaData extends DefaultMetaService implements MetaData { - - - private static String ROUTINES_SQL - = - "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " - + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " - + "routine_name = '%s';"; - - @Override - public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, - String functionName) { - - String sql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Function function = new Function(); - function.setDatabaseName(databaseName); - function.setSchemaName(schemaName); - function.setFunctionName(functionName); - if (resultSet.next()) { - function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - function.setRemarks(resultSet.getString("ROUTINE_COMMENT")); - function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); - } - return function; - }); - - } - - private static String TRIGGER_SQL - = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " - + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; - - private static String TRIGGER_SQL_LIST - = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; - - @Override - public List triggers(Connection connection, String databaseName, String schemaName) { - List triggers = new ArrayList<>(); - String sql = String.format(TRIGGER_SQL_LIST, databaseName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - while (resultSet.next()) { - Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); - trigger.setSchemaName(schemaName); - trigger.setDatabaseName(databaseName); - triggers.add(trigger); - } - return triggers; - }); - } - - @Override - public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, - String triggerName) { - - String sql = String.format(TRIGGER_SQL, databaseName, triggerName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Trigger trigger = new Trigger(); - trigger.setDatabaseName(databaseName); - trigger.setSchemaName(schemaName); - trigger.setTriggerName(triggerName); - if (resultSet.next()) { - trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); - } - return trigger; - }); - } - - @Override - public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, - String procedureName) { - String sql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Procedure procedure = new Procedure(); - procedure.setDatabaseName(databaseName); - procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); - if (resultSet.next()) { - procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); - procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); - procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); - } - return procedure; - }); - } - - private static String VIEW_SQL - = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " - + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; - - @Override - public Table view(Connection connection, String databaseName, String schemaName, String viewName) { - String sql = String.format(VIEW_SQL, databaseName, viewName); - return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Table table = new Table(); - table.setDatabaseName(databaseName); - table.setSchemaName(schemaName); - table.setName(viewName); - if (resultSet.next()) { - table.setDdl(resultSet.getString("definition")); - } - return table; - }); - } +public class MariaDBMetaData extends MysqlMetaData implements MetaData { + + +// private static String ROUTINES_SQL +// = +// "SELECT SPECIFIC_NAME, ROUTINE_COMMENT, ROUTINE_DEFINITION FROM information_schema.routines WHERE " +// + "routine_type = '%s' AND ROUTINE_SCHEMA ='%s' AND " +// + "routine_name = '%s';"; +// +// @Override +// public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, +// String functionName) { +// +// String sql = String.format(ROUTINES_SQL, "FUNCTION", databaseName, functionName); +// return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { +// Function function = new Function(); +// function.setDatabaseName(databaseName); +// function.setSchemaName(schemaName); +// function.setFunctionName(functionName); +// if (resultSet.next()) { +// function.setSpecificName(resultSet.getString("SPECIFIC_NAME")); +// function.setRemarks(resultSet.getString("ROUTINE_COMMENT")); +// function.setFunctionBody(resultSet.getString("ROUTINE_DEFINITION")); +// } +// return function; +// }); +// +// } +// +// private static String TRIGGER_SQL +// = "SELECT TRIGGER_NAME,EVENT_MANIPULATION, ACTION_STATEMENT FROM INFORMATION_SCHEMA.TRIGGERS where " +// + "TRIGGER_SCHEMA = '%s' AND TRIGGER_NAME = '%s';"; +// +// private static String TRIGGER_SQL_LIST +// = "SELECT TRIGGER_NAME FROM INFORMATION_SCHEMA.TRIGGERS where TRIGGER_SCHEMA = '%s';"; +// +// @Override +// public List triggers(Connection connection, String databaseName, String schemaName) { +// List triggers = new ArrayList<>(); +// String sql = String.format(TRIGGER_SQL_LIST, databaseName); +// return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { +// while (resultSet.next()) { +// Trigger trigger = new Trigger(); +// trigger.setTriggerName(resultSet.getString("TRIGGER_NAME")); +// trigger.setSchemaName(schemaName); +// trigger.setDatabaseName(databaseName); +// triggers.add(trigger); +// } +// return triggers; +// }); +// } +// +// @Override +// public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, +// String triggerName) { +// +// String sql = String.format(TRIGGER_SQL, databaseName, triggerName); +// return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { +// Trigger trigger = new Trigger(); +// trigger.setDatabaseName(databaseName); +// trigger.setSchemaName(schemaName); +// trigger.setTriggerName(triggerName); +// if (resultSet.next()) { +// trigger.setTriggerBody(resultSet.getString("ACTION_STATEMENT")); +// } +// return trigger; +// }); +// } +// +// @Override +// public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, +// String procedureName) { +// String sql = String.format(ROUTINES_SQL, "PROCEDURE", databaseName, procedureName); +// return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { +// Procedure procedure = new Procedure(); +// procedure.setDatabaseName(databaseName); +// procedure.setSchemaName(schemaName); +// procedure.setProcedureName(procedureName); +// if (resultSet.next()) { +// procedure.setSpecificName(resultSet.getString("SPECIFIC_NAME")); +// procedure.setRemarks(resultSet.getString("ROUTINE_COMMENT")); +// procedure.setProcedureBody(resultSet.getString("ROUTINE_DEFINITION")); +// } +// return procedure; +// }); +// } +// +// private static String VIEW_SQL +// = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " +// + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; +// +// @Override +// public Table view(Connection connection, String databaseName, String schemaName, String viewName) { +// String sql = String.format(VIEW_SQL, databaseName, viewName); +// return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { +// Table table = new Table(); +// table.setDatabaseName(databaseName); +// table.setSchemaName(schemaName); +// table.setName(viewName); +// if (resultSet.next()) { +// table.setDdl(resultSet.getString("definition")); +// } +// return table; +// }); +// } } 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 35e469950..a7c39c74c 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 @@ -125,7 +125,7 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, @Override public List columns(Connection connection, String databaseName, String schemaName, String tableName) { - String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName); + String sql = String.format(SELECT_TABLE_COLUMNS, databaseName, tableName); List tableColumns = new ArrayList<>(); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { @@ -146,20 +146,20 @@ public List columns(Connection connection, String databaseName, Str column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE")); column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); column.setCollationName(resultSet.getString("COLLATION_NAME")); - setColumnSize(column,resultSet.getString("COLUMN_TYPE")); + setColumnSize(column, resultSet.getString("COLUMN_TYPE")); tableColumns.add(column); } return tableColumns; }); } - private void setColumnSize(TableColumn column,String columnType){ + private void setColumnSize(TableColumn column, String columnType) { try { if (columnType.contains("(")) { String size = columnType.substring(columnType.indexOf("(") + 1, columnType.indexOf(")")); - if("SET".equalsIgnoreCase(column.getColumnType())|| "ENUM".equalsIgnoreCase(column.getColumnType())){ + if ("SET".equalsIgnoreCase(column.getColumnType()) || "ENUM".equalsIgnoreCase(column.getColumnType())) { column.setValue(size); - }else { + } else { if (size.contains(",")) { String[] sizes = size.split(","); if (StringUtils.isNotBlank(sizes[0])) { @@ -173,12 +173,11 @@ private void setColumnSize(TableColumn column,String columnType){ } } } - }catch (Exception e){ + } catch (Exception e) { } } - private static String VIEW_SQL = "SELECT TABLE_SCHEMA AS DatabaseName, TABLE_NAME AS ViewName, VIEW_DEFINITION AS definition, CHECK_OPTION, " + "IS_UPDATABLE FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' AND TABLE_NAME = '%s';"; @@ -271,4 +270,9 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .collations(MysqlCollationEnum.getCollations()) .build(); } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).map(name -> "`" + name + "`").collect(Collectors.joining(".")); + } } 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 7204d0d81..b9ffa432f 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 @@ -281,4 +281,9 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .collations(Lists.newArrayList()) .build(); } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java index 2a5664150..1a7eb5895 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -79,6 +79,9 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(tableColumn.getColumnType()); script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); + if(StringUtils.isNotBlank(tableColumn.getComment())){ + script.append("\n").append(buildComment(tableColumn)).append(";\n"); + } } } 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 872e63fa2..ccda182aa 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 @@ -2,7 +2,9 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; @@ -35,4 +37,9 @@ public List databases(Connection connection) { public List schemas(Connection connection,String databaseName) { return Lists.newArrayList(); } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml b/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml index 4f9b8edad..d040a7fe4 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/pom.xml @@ -17,6 +17,12 @@ ai.chat2db chat2db-spi + + com.microsoft.sqlserver + mssql-jdbc + 11.2.1.jre17 + test + \ No newline at end of file diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java new file mode 100644 index 000000000..5101f8d08 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java @@ -0,0 +1,124 @@ +package ai.chat2db.plugin.sqlserver.builder; + +import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; +import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import org.apache.commons.lang3.StringUtils; + +public class SqlServerSqlBuilder implements SqlBuilder { + @Override + public String buildCreateTableSql(Table table) { + StringBuilder script = new StringBuilder(); + + script.append("CREATE TABLE ").append("[").append(table.getSchemaName()).append("].[").append(table.getName()).append("] (").append("\n"); + + for (TableColumn column : table.getColumnList()) { + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + continue; + } + SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(column.getColumnType()); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append("\n)\ngo\n"); + + for (TableIndex tableIndex : table.getIndexList()) { + if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { + continue; + } + SqlServerIndexTypeEnum sqlServerIndexTypeEnum = SqlServerIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\n").append(sqlServerIndexTypeEnum.buildIndexScript(tableIndex)); + if (StringUtils.isNotBlank(tableIndex.getComment())) { + script.append("\n").append(buildIndexComment(tableIndex)); + } + } + + for (TableColumn column : table.getColumnList()) { + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { + continue; + } + script.append("\n").append(buildColumnComment(column)); + } + + if (StringUtils.isNotBlank(table.getComment())) { + script.append("\n").append(buildTableComment(table)); + } + + + return script.toString(); + } + + private static String INDEX_COMMENT_SCRIPT = "exec sp_addextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s','INDEX','%s' \ngo"; + + + private String buildIndexComment(TableIndex tableIndex) { + return String.format(INDEX_COMMENT_SCRIPT, tableIndex.getComment(), tableIndex.getSchemaName(), tableIndex.getTableName(), tableIndex.getName()); + } + + private static String TABLE_COMMENT_SCRIPT = "exec sp_addextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s' \ngo"; + + + private String buildTableComment(Table table) { + return String.format(TABLE_COMMENT_SCRIPT, table.getComment(), table.getSchemaName(), table.getName()); + } + + private static String COLUMN_COMMENT_SCRIPT = "exec sp_addextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s','COLUMN','%s' \ngo"; + + private String buildColumnComment(TableColumn column) { + return String.format(COLUMN_COMMENT_SCRIPT, column.getComment(), column.getSchemaName(), column.getTableName(), column.getName()); + } + + @Override + public String buildModifyTaleSql(Table oldTable, Table newTable) { + StringBuilder script = new StringBuilder(); + + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append(buildRenameTable(oldTable, newTable)); + } + if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { + if(oldTable.getComment() == null){ + script.append("\n").append(buildTableComment(newTable)); + }else { + script.append("\n").append(buildUpdateTableComment(newTable)); + } + } + + + // append modify column + for (TableColumn tableColumn : newTable.getColumnList()) { + if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { + SqlServerColumnTypeEnum typeEnum = SqlServerColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append(typeEnum.buildModifyColumn(tableColumn)).append("\n"); + } + } + + // append modify index + for (TableIndex tableIndex : newTable.getIndexList()) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { + SqlServerIndexTypeEnum mysqlIndexTypeEnum = SqlServerIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append("\n"); + if (StringUtils.isNotBlank(tableIndex.getComment())) { + script.append("\n").append(buildIndexComment(tableIndex)).append("\ngo"); + } + } + } + + return script.toString(); + } + + + private static String UPDATE_TABLE_COMMENT_SCRIPT = "exec sp_updateextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s' \ngo"; + private String buildUpdateTableComment(Table newTable) { + return String.format(UPDATE_TABLE_COMMENT_SCRIPT, newTable.getComment(), newTable.getSchemaName(), newTable.getName()); + } + + private static String RENAME_TABLE_SCRIPT = "exec sp_rename '%s','%s','OBJECT' \ngo"; + + private String buildRenameTable(Table oldTable, Table newTable) { + return String.format(RENAME_TABLE_SCRIPT, oldTable.getName(), newTable.getName()); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/item.json b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/item.json deleted file mode 100644 index e69de29bb..000000000 diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java new file mode 100644 index 000000000..9aecd681b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java @@ -0,0 +1,337 @@ +package ai.chat2db.plugin.sqlserver.type; + +import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum SqlServerColumnTypeEnum implements ColumnBuilder { + //JSON("JSON", false, false, true, false, false, false, true, false, false, false) + + BIGINT("BIGINT", false, false, true, false, false, false, true, true), + + BINARY("BINARY", false, false, true, false, false, false, true, true), + + BIT("BIT", false, false, true, false, false, false, true, true), + + CHAR("CHAR", true, false, true, false, false, true, true, true), + + DATE("DATE", false, false, true, false, false, false, true, true), + + DATETIME("DATETIME", false, false, true, false, false, false, true, true), + + DATETIME2("DATETIME2", true, false, true, false, false, false, true, true), + + + DATETIMEOFFSET("DATETIMEOFFSET", true, false, true, false, false, false, true, true), + + + DECIMAL("DECIMAL", true, true, true, false, false, false, true, true), + + + FLOAT("FLOAT", true, false, true, false, false, false, true, true), + + + GEOGRAPHY("GEOGRAPHY", false, false, true, false, false, false, true, true), + + GEOMETRY("GEOMETRY", false, false, true, false, false, false, true, true), + + HIERARCHYID("HIERARCHYID", false, false, true, false, false, false, true, true), + + IMAGE("IMAGE", false, false, true, false, false, false, true, true), + + INT("INT", false, false, true, false, false, false, true, true), + + + MONEY("MONEY", false, false, true, false, false, false, true, true), + + NCHAR("NCHAR", true, false, true, false, false, true, true, true), + + NTEXT("NTEXT", false, false, true, false, false, false, true, true), + + NUMERIC("NUMERIC", true, true, true, false, false, false, true, true), + + NVARCHAR("NVARCHAR", true, false, true, false, false, true, true, true), + + NVARCHAR_MAX("NVARCHAR(MAX)", false, false, true, false, false, true, true, true), + + + REAL("REAL", false, false, true, false, false, false, true, true), + + SMALLDATETIME("SMALLDATETIME", false, false, true, false, false, false, true, true), + + SMALLINT("SMALLINT", false, false, true, false, false, false, true, true), + + SMALLMONEY("SMALLMONEY", false, false, true, false, false, false, true, true), + + SQL_VARIANT("SQL_VARIANT", false, false, true, false, false, false, true, true), + + SYSNAME("SYSNAME", false, false, true, false, false, false, true, true), + + TEXT("TEXT", false, false, true, false, false, true, true, true), + + TIME("TIME", true, false, true, false, false, false, true, true), + + TIMESTAMP("TIMESTAMP", false, false, true, false, false, false, true, true), + + + TINYINT("TINYINT", false, false, true, false, false, false, true, true), + + UNIQUEIDENTIFIER("UNIQUEIDENTIFIER", false, false, true, false, false, false, true, true), + + + VARBINARY("VARBINARY", true, false, true, false, false, false, true, true), + + VARBINARY_MAX("VARBINARY(MAX)", false, false, true, false, false, false, true, true), + + VARCHAR("VARCHAR", true, false, true, false, false, true, true, true), + + VARCHAR_MAX("VARCHAR(MAX)", false, false, true, false, false, true, true, true), + + XML("XML", false, false, true, false, false, false, true, true), + + + ; + private ColumnType columnType; + + public static SqlServerColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (SqlServerColumnTypeEnum value : SqlServerColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); + } + } + + public ColumnType getColumnType() { + return columnType; + } + + + SqlServerColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, false, false, false); + } + + @Override + public String buildCreateColumnSql(TableColumn column) { + SqlServerColumnTypeEnum type = this; + StringBuilder script = new StringBuilder(); + + script.append("[").append(column.getName()).append("]").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + script.append(buildSparse(column, type)).append(" "); + + script.append(buildDefaultValue(column, type)).append(" "); + + script.append(buildNullable(column, type)).append(" "); + + script.append(buildCollation(column, type)).append(" "); + + return script.toString(); + } + + public String buildUpdateColumnSql(TableColumn column) { + SqlServerColumnTypeEnum type = this; + + StringBuilder script = new StringBuilder(); + + script.append("[").append(column.getName()).append("]").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + script.append(buildNullable(column, type)).append(" \ngo\n"); + + if (StringUtils.isNotBlank(column.getDefaultValue()) && column.getOldColumn().getDefaultValue() != null && !StringUtils.equalsIgnoreCase(column.getDefaultValue(), column.getOldColumn().getDefaultValue())) { + script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); + script.append(" ").append("DROP CONSTRAINT ").append("[").append(column.getDefaultConstraintName()).append("]"); + + script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); + script.append(" ").append("ADD ").append(buildDefaultValue(column, type)).append(" for ").append(column.getName()).append(" \ngo\n"); + } + + if (StringUtils.isNotBlank(column.getDefaultValue()) && column.getOldColumn().getDefaultValue() == null) { + script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); + script.append(" ").append("ADD ").append(buildDefaultValue(column, type)).append(" for ").append(column.getName()).append(" \ngo\n"); + } + + + if (column.getSparse() != null && column.getSparse()) { + script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); + script.append(" ").append("ALTER COLUMN ").append("[").append(column.getName()).append("]").append(" add ").append("SPARSE").append(" \ngo\n"); + } + + if (StringUtils.isNotBlank(column.getCollationName())) { + script.append("ALTER TABLE ").append("[").append(column.getSchemaName()).append("].[").append(column.getTableName()).append("]"); + script.append(" ").append("ALTER COLUMN ").append("[").append(column.getName()).append("]").append(" ").append("COLLATE ").append(column.getCollationName()).append(" \ngo\n"); + } + return script.toString(); + } + + private String buildSparse(TableColumn column, SqlServerColumnTypeEnum type) { + if (Boolean.TRUE.equals(column.getSparse())) { + return "SPARSE"; + } else { + return ""; + } + } + + private String buildCollation(TableColumn column, SqlServerColumnTypeEnum type) { + if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { + return ""; + } + return StringUtils.join("COLLATE ", column.getCollationName()); + } + + + private String buildNullable(TableColumn column, SqlServerColumnTypeEnum type) { + if (!type.getColumnType().isSupportNullable()) { + return ""; + } + if (column.getNullable() != null && 1 == column.getNullable()) { + return "NULL"; + } else { + return "NOT NULL"; + } + } + + private String buildDefaultValue(TableColumn column, SqlServerColumnTypeEnum type) { + if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { + return ""; + } + + if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { + return StringUtils.join("DEFAULT ''"); + } + + if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { + return StringUtils.join("DEFAULT NULL"); + } + + return StringUtils.join("DEFAULT ", column.getDefaultValue()); + } + + private String buildDataType(TableColumn column, SqlServerColumnTypeEnum type) { + String columnType = type.columnType.getTypeName(); + if (Arrays.asList(CHAR, NCHAR, NVARCHAR, VARBINARY, VARCHAR).contains(type)) { + StringBuilder script = new StringBuilder(); + script.append(columnType); + if (column.getColumnSize() != null) { + script.append("(").append(column.getColumnSize()).append(")"); + } + + return script.toString(); + } + + if (Arrays.asList(DECIMAL, FLOAT, TIMESTAMP, TIME, DATETIME2, DATETIMEOFFSET, FLOAT, NUMERIC).contains(type)) { + StringBuilder script = new StringBuilder(); + script.append(columnType); + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + script.append("(").append(column.getColumnSize()).append(")"); + } else if (column.getColumnSize() != null && column.getDecimalDigits() != null) { + script.append("(").append(column.getColumnSize()).append(",").append(column.getDecimalDigits()).append(")"); + } + return script.toString(); + } + + if (Arrays.asList().contains(type)) { + StringBuilder script = new StringBuilder(); + if (column.getColumnSize() == null) { + script.append(columnType); + } else { + String[] split = columnType.split("TIMESTAMP"); + script.append("TIMESTAMP").append("(").append(column.getColumnSize()).append(")").append(split[1]); + } + return script.toString(); + } + + + return columnType; + } + + private static String RENAME_COLUMN_SCRIPT = "exec sp_rename '%s.%s','%s','COLUMN' \ngo"; + + private String renameColumn(TableColumn tableColumn) { + return String.format(RENAME_COLUMN_SCRIPT, tableColumn.getTableName(), tableColumn.getOldName(), tableColumn.getName()); + } + + + @Override + public String buildModifyColumn(TableColumn tableColumn) { + + if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { + StringBuilder script = new StringBuilder(); + if (StringUtils.isNotBlank(tableColumn.getDefaultConstraintName())) { + script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); + script.append(" ").append("DROP CONSTRAINT ").append("[").append(tableColumn.getDefaultConstraintName()).append("]"); + script.append("\ngo\n"); + } + script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); + script.append(" ").append("DROP COLUMN ").append("[").append(tableColumn.getName()).append("]"); + script.append("\ngo\n"); + return script.toString(); + } + if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); + script.append(" ").append("ADD ").append(buildCreateColumnSql(tableColumn)).append(" \ngo\n"); + return script.toString(); + } + if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { + StringBuilder script = new StringBuilder(); + + if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { + script.append(renameColumn(tableColumn)); + script.append("\n"); + } + script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); + script.append(" ").append("ALTER COLUMN ").append(buildUpdateColumnSql(tableColumn)).append(" \n"); + + if (StringUtils.isNotBlank(tableColumn.getComment())) { + script.append("\n").append(buildModifyColumnComment(tableColumn)); + } + + return script.toString(); + + } + return ""; + } + + private static String COLUMN_MODIFY_COMMENT_SCRIPT = "IF ((SELECT COUNT(*) FROM ::fn_listextendedproperty('MS_Description',\n" + + "'SCHEMA', N'%s',\n" + + "'TABLE', N'%s',\n" + + "'COLUMN', N'%s')) > 0)\n" + + " EXEC sp_updateextendedproperty\n" + + "'MS_Description', N'%s',\n" + + "'SCHEMA', N'%s',\n" + + "'TABLE', N'%s',\n" + + "'COLUMN', N'%s'\n" + + "ELSE\n" + + " EXEC sp_addextendedproperty\n" + + "'MS_Description', N'%s',\n" + + "'SCHEMA', N'%s',\n" + + "'TABLE', N'%s',\n" + + "'COLUMN', N'%s'\n go"; + + private String buildModifyColumnComment(TableColumn tableColumn) { + return String.format(COLUMN_MODIFY_COMMENT_SCRIPT, tableColumn.getSchemaName(), tableColumn.getTableName(), + tableColumn.getName(), tableColumn.getComment(), tableColumn.getSchemaName(), tableColumn.getTableName(), tableColumn.getName(), + tableColumn.getComment(), tableColumn.getSchemaName(), tableColumn.getTableName(), tableColumn.getName()); + } + + public static List getTypes() { + return Arrays.stream(SqlServerColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java new file mode 100644 index 000000000..74e8f93ac --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java @@ -0,0 +1,119 @@ +package ai.chat2db.plugin.sqlserver.type; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import org.apache.commons.lang3.StringUtils; + +public enum SqlServerIndexTypeEnum { + + PRIMARY_KEY("Primary", "PRIMARY KEY"), + +// NORMAL("Normal", "INDEX"), +// +// UNIQUE("Unique", "UNIQUE INDEX"), + + + UNIQUE_CLUSTERED("UNIQUE CLUSTERED", "UNIQUE CLUSTERED INDEX"), + + CLUSTERED("CLUSTERED", "CLUSTERED INDEX"), + + + NONCLUSTERED("NONCLUSTERED", "NONCLUSTERED INDEX"), + + UNIQUE_NONCLUSTERED("UNIQUE NONCLUSTERED", "UNIQUE NONCLUSTERED INDEX"), + + SPATIAL("SPATIAL", "SPATIAL INDEX"), + + XML("XML", "XML INDEX"); + + + public String getName() { + return name; + } + + private String name; + + + public String getKeyword() { + return keyword; + } + + private String keyword; + + SqlServerIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + } + + + public static SqlServerIndexTypeEnum getByType(String type) { + for (SqlServerIndexTypeEnum value : SqlServerIndexTypeEnum.values()) { + if (value.name.equalsIgnoreCase(type)) { + return value; + } + } + return null; + } + + //ALTER TABLE [dbo].[Employees] ADD CONSTRAINT [PK__Employee__7AD04FF164ABF7C7] PRIMARY KEY CLUSTERED ([FirstName]) + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + if (PRIMARY_KEY.equals(this)) { + script.append("ALTER TABLE [").append(tableIndex.getSchemaName()).append("].[").append(tableIndex.getTableName()).append("] ADD CONSTRAINT ").append(buildIndexName(tableIndex)).append(" ").append(keyword).append(" ").append(buildIndexColumn(tableIndex)); + } else { + script.append("CREATE ").append(keyword).append(" "); + script.append(buildIndexName(tableIndex)).append("\n ON [").append(tableIndex.getSchemaName()).append("].[").append(tableIndex.getTableName()).append("] ").append(buildIndexColumn(tableIndex)); + } + script.append("\ngo"); + return script.toString(); + } + + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + if (StringUtils.isNotBlank(column.getColumnName())) { + script.append("[").append(column.getColumnName()).append("]"); + if (!StringUtils.isBlank(column.getAscOrDesc())) { + script.append(" ").append(column.getAscOrDesc()); + } + script.append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + return "[" + tableIndex.getName() + "]"; + } + + public String buildModifyIndex(TableIndex tableIndex) { + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex), "\n", buildIndexScript(tableIndex)); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (SqlServerIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { + return StringUtils.join("ALTER TABLE [", tableIndex.getSchemaName(), "].[", tableIndex.getTableName(), "] DROP CONSTRAINT ", buildIndexName(tableIndex),"\ngo"); + } + StringBuilder script = new StringBuilder(); + script.append("DROP INDEX "); + script.append(buildIndexName(tableIndex)); + script.append(" ON [").append(tableIndex.getSchemaName()).append("].[").append(tableIndex.getTableName()).append("] \ngo"); + + return script.toString(); + } +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java new file mode 100644 index 000000000..68725bc6b --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java @@ -0,0 +1,60 @@ +package ai.chat2db.server.domain.api.param; + +import ai.chat2db.spi.model.Header; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class UpdateSelectResultParam { + /** + * 控制台id + */ + @NotNull + private Long consoleId; + + /** + * 数据源id + */ + @NotNull + private Long dataSourceId; + + /** + * DB名称 + */ + private String databaseName; + + + /** + * schema名称 + */ + private String schemaName; + + + /** + * 展示头的列表 + */ + @NotEmpty + private List
    headerList; + + + /** + * 修改后数据的列表 + */ + @NotEmpty + private List> dataList; + + /** + * 数据的列表 + */ + @NotEmpty + private List> oldDataList; + + /** + * 表名 + */ + @NotEmpty + private String tableName; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index d5ba425e3..b4858c8ce 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -86,6 +86,7 @@ public DataResult
    query(TableQueryParam param, TableSelector selector) { @Override public ListResult buildSql(Table oldTable, Table newTable) { + initOldTable(oldTable, newTable); SqlBuilder sqlBuilder = Chat2DBContext.getSqlBuilder(); List sqls = new ArrayList<>(); if (oldTable == null) { @@ -96,6 +97,19 @@ public ListResult buildSql(Table oldTable, Table newTable) { return ListResult.of(sqls); } + private void initOldTable(Table oldTable, Table newTable) { + if (oldTable == null) { + return; + } + Map columnMap = oldTable.getColumnList().stream().collect(Collectors.toMap(TableColumn::getName, Function.identity())); + for (TableColumn newColumn : newTable.getColumnList()) { + TableColumn oldColumn = columnMap.get(newColumn.getName()); + if (oldColumn != null) { + newColumn.setOldColumn(oldColumn); + } + } + } + @Override public PageResult
    pageQuery(TablePageQueryParam param, TableSelector selector) { MetaData metaSchema = Chat2DBContext.getMetaData(); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 154a19386..2c7a0cc87 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -4,6 +4,7 @@ import java.util.List; import ai.chat2db.server.domain.api.param.DlExecuteParam; +import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -11,6 +12,7 @@ import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; +import ai.chat2db.server.web.api.controller.rdb.request.SelectResultUpdateRequest; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.sql.Chat2DBContext; @@ -54,6 +56,20 @@ public ListResult manage(@RequestBody DmlRequest request) { } + /** + * update 查询结果 + * + * @param request + * @return + */ + @RequestMapping(value = "/execute_update", method = {RequestMethod.POST, RequestMethod.PUT}) + public DataResult executeUpdate(@RequestBody SelectResultUpdateRequest request) { + UpdateSelectResultParam param = rdbWebConverter.request2param(request); + return dlTemplateService.updateSelectResult(param); + + } + + /** * 增删改查等数据运维 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index bcbcca69a..080743440 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -174,11 +174,13 @@ public ListResult modifySql(@Valid @RequestBody TableModifySqlRequest req tableIndex.setTableName(table.getName()); tableIndex.setDatabaseName(request.getDatabaseName()); } + return tableService.buildSql(rdbWebConverter.tableRequest2param(request.getOldTable()),table) .map(rdbWebConverter::dto2vo); } + /** * 数据库支持的数据类型 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index 872e03fb5..9dd847fdb 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -2,22 +2,9 @@ import java.util.List; -import ai.chat2db.server.domain.api.param.DlCountParam; -import ai.chat2db.server.domain.api.param.DlExecuteParam; -import ai.chat2db.server.domain.api.param.DropParam; -import ai.chat2db.server.domain.api.param.ShowCreateTableParam; -import ai.chat2db.server.domain.api.param.TablePageQueryParam; -import ai.chat2db.server.domain.api.param.TableQueryParam; +import ai.chat2db.server.domain.api.param.*; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; -import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; -import ai.chat2db.server.web.api.controller.rdb.request.DdlCountRequest; -import ai.chat2db.server.web.api.controller.rdb.request.DdlExportRequest; -import ai.chat2db.server.web.api.controller.rdb.request.DdlRequest; -import ai.chat2db.server.web.api.controller.rdb.request.DmlRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableDeleteRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableDetailQueryRequest; -import ai.chat2db.server.web.api.controller.rdb.request.TableRequest; +import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; import ai.chat2db.server.web.api.controller.rdb.vo.ExecuteResultVO; import ai.chat2db.server.web.api.controller.rdb.vo.IndexVO; @@ -234,4 +221,7 @@ public abstract class RdbWebConverter { public abstract List databaseDto2vo(List dto); public abstract MetaSchemaVO metaSchemaDto2vo(MetaSchema data); + + + public abstract UpdateSelectResultParam request2param(SelectResultUpdateRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java new file mode 100644 index 000000000..164a0ce4c --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java @@ -0,0 +1,44 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; +import ai.chat2db.server.web.api.controller.rdb.vo.HeaderVO; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Data +public class SelectResultUpdateRequest extends DataSourceBaseRequest implements DataSourceConsoleRequestInfo { + + /** + * 展示头的列表 + */ + private List headerList; + + /** + * 修改后数据的列表 + */ + private List> dataList; + + /** + * 数据的列表 + */ + private List> oldDataList; + + /** + * 表名 + */ + private String tableName; + + /** + * 控制台id + */ + @NotNull + private Long consoleId; + @Override + public Long getConsoleId() { + return consoleId; + } + +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index abba4689a..9aa1c09b7 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -39,7 +39,7 @@ public interface MetaData { * @return */ String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, - @NotEmpty String tableName); + @NotEmpty String tableName); /** * Querying all table under a schema. @@ -114,7 +114,7 @@ String tableDDL(Connection connection, @NotEmpty String databaseName, String sch * @return */ List columns(Connection connection, @NotEmpty String databaseName, String schemaName, - @NotEmpty String tableName); + @NotEmpty String tableName); /** * Querying all columns under a table. @@ -127,7 +127,7 @@ List columns(Connection connection, @NotEmpty String databaseName, * @return */ List columns(Connection connection, @NotEmpty String databaseName, String schemaName, String tableName, - String columnName); + String columnName); /** * Querying all indexes under a table. @@ -138,7 +138,7 @@ List columns(Connection connection, @NotEmpty String databaseName, * @return */ List indexes(Connection connection, @NotEmpty String databaseName, String schemaName, - @NotEmpty String tableName); + @NotEmpty String tableName); /** * Querying function detail under a schema. @@ -171,11 +171,10 @@ List indexes(Connection connection, @NotEmpty String databaseName, S * @param procedureName * @return */ - Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName,String procedureName); + Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, String procedureName); /** - * * @param connection * @return */ @@ -184,17 +183,28 @@ List indexes(Connection connection, @NotEmpty String databaseName, S /** * Get sql builder. + * * @return */ SqlBuilder getSqlBuilder(); /** - * * @param databaseName * @param schemaName * @param tableName * @return */ - TableMeta getTableMeta( String databaseName, String schemaName, String tableName); + TableMeta getTableMeta(String databaseName, String schemaName, String tableName); + + + /** + * Get meta data name. + * + * @param names + * @return + */ + String getMetaDataName(String ...names); + + } \ No newline at end of file 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 91ede3413..053b2427c 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 @@ -16,6 +16,11 @@ @NoArgsConstructor @AllArgsConstructor public class TableColumn { + + /** + * Old column, when modifying a column, you need this parameter + */ + private TableColumn oldColumn; /** * 旧的列名,在修改列的时候需要这个参数 * 在返回的时候oldName=name @@ -58,6 +63,8 @@ public class TableColumn { @JsonAlias({"COLUMN_DEF"}) private String defaultValue; + + /** * 是否自增 * 为空 代表没有值 数据库的实际语义是 false @@ -88,7 +95,7 @@ public class TableColumn { private String databaseName; /** - * Data source dependent type name, for a UDT the type name is fully qualified + * Data source dependent type name, for a UDT the type name is fully qualified */ private String typeName; @@ -151,8 +158,8 @@ public class TableColumn { /** * String => Indicates whether this is a generated column - * * YES --- if this a generated column - * * NO --- if this not a generated column + * * YES --- if this a generated column + * * NO --- if this not a generated column */ private Boolean generatedColumn; @@ -166,9 +173,15 @@ public class TableColumn { private String collationName; + //Mysql private String value; - + //ORACLE private String unit; + // sqlserver + private Boolean sparse; + + // sqlserver + private String defaultConstraintName; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index 851dc75b6..f40311fe2 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import ai.chat2db.server.tools.common.util.EasyIntegerUtils; +import ai.chat2db.spi.enums.DataTypeEnum; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; import com.alibaba.druid.sql.ast.SQLDataTypeImpl; @@ -57,6 +58,10 @@ import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; +import net.sf.jsqlparser.statement.select.PlainSelect; +import net.sf.jsqlparser.statement.select.Select; +import net.sf.jsqlparser.statement.select.SelectExpressionItem; +import net.sf.jsqlparser.statement.select.SelectItem; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; @@ -69,6 +74,31 @@ public class SqlUtils { public static final String DEFAULT_TABLE_NAME = "table1"; + + public static boolean canEdit(String sql) { + try { + Statement statement = CCJSqlParserUtil.parse(sql); + if (statement instanceof Select) { + Select select = (Select) statement; + PlainSelect plainSelect = (PlainSelect) select.getSelectBody(); + if (plainSelect.getJoins() == null && plainSelect.getFromItem() != null) { + for (SelectItem item : plainSelect.getSelectItems()) { + if (item instanceof SelectExpressionItem) { + SelectExpressionItem expressionItem = (SelectExpressionItem) item; + if (expressionItem.getAlias() != null) { + return false; // 找到了一个别名 + } + } + } + return true; + } + } + } catch (Exception e) { + return false; + } + return false; + } + public static List buildSql(Table oldTable, Table newTable) { List sqlList = new ArrayList<>(); // 创建表 @@ -86,7 +116,7 @@ public static List buildSql(Table oldTable, Table newTable) { mySqlCreateTableStatement.addColumn(sqlColumnDefinition); sqlColumnDefinition.setName(tableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(tableColumn.getColumnType())); - if (tableColumn.getNullable()==1) { + if (tableColumn.getNullable() == 1) { sqlColumnDefinition.addConstraint(new SQLNullConstraint()); } else { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); @@ -130,7 +160,7 @@ public static List buildSql(Table oldTable, Table newTable) { SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); + tableIndexColumn.getCollation()); if (collation != null) { sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); } @@ -147,7 +177,7 @@ public static List buildSql(Table oldTable, Table newTable) { SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); + tableIndexColumn.getCollation()); if (collation != null) { sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); } @@ -193,19 +223,19 @@ public static List buildSql(Table oldTable, Table newTable) { private static void modifyColumn(List sqlList, Table oldTable, Table newTable) { Map oldColumnMap = EasyCollectionUtils.toIdentityMap(oldTable.getColumnList(), - tableColumn -> { - if (tableColumn.getOldName() != null) { - return tableColumn.getOldName(); - } - return tableColumn.getName(); - }); + tableColumn -> { + if (tableColumn.getOldName() != null) { + return tableColumn.getOldName(); + } + return tableColumn.getName(); + }); Map newColumnMap = EasyCollectionUtils.toIdentityMap(newTable.getColumnList(), - tableColumn -> { - if (tableColumn.getOldName() != null) { - return tableColumn.getOldName(); - } - return tableColumn.getName(); - }); + tableColumn -> { + if (tableColumn.getOldName() != null) { + return tableColumn.getOldName(); + } + return tableColumn.getName(); + }); SQLAlterTableStatement sqlAlterTableStatement = new SQLAlterTableStatement(); sqlAlterTableStatement.setDbType(DbType.mysql); @@ -222,7 +252,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab sqlAlterTableAddColumn.addColumn(sqlColumnDefinition); sqlColumnDefinition.setName(newTableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (newTableColumn.getNullable()!=1) { + if (newTableColumn.getNullable() != 1) { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); } if (!Objects.isNull(newTableColumn.getDefaultValue())) { @@ -236,12 +266,12 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab } // 代表可能修改字段 或者没变 boolean hasChange = !StringUtils.equals(oldTableColumn.getName(), newTableColumn.getName()) - || !StringUtils.equals(oldTableColumn.getColumnType(), newTableColumn.getColumnType()) - || !EasyIntegerUtils.equals(oldTableColumn.getNullable(), newTableColumn.getNullable(), 1) - || !StringUtils.equals(oldTableColumn.getDefaultValue(), newTableColumn.getDefaultValue()) - || !EasyBooleanUtils.equals(oldTableColumn.getAutoIncrement(), newTableColumn.getAutoIncrement(), - Boolean.FALSE) - || !StringUtils.equals(oldTableColumn.getComment(), newTableColumn.getComment()); + || !StringUtils.equals(oldTableColumn.getColumnType(), newTableColumn.getColumnType()) + || !EasyIntegerUtils.equals(oldTableColumn.getNullable(), newTableColumn.getNullable(), 1) + || !StringUtils.equals(oldTableColumn.getDefaultValue(), newTableColumn.getDefaultValue()) + || !EasyBooleanUtils.equals(oldTableColumn.getAutoIncrement(), newTableColumn.getAutoIncrement(), + Boolean.FALSE) + || !StringUtils.equals(oldTableColumn.getComment(), newTableColumn.getComment()); // 没有修改字段 if (!hasChange) { @@ -257,7 +287,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab mySqlAlterTableChangeColumn.setNewColumnDefinition(sqlColumnDefinition); sqlColumnDefinition.setName(newTableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (newTableColumn.getNullable()!=1) { + if (newTableColumn.getNullable() != 1) { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); } if (!Objects.isNull(newTableColumn.getDefaultValue())) { @@ -275,7 +305,7 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab mySqlAlterTableModifyColumn.setNewColumnDefinition(sqlColumnDefinition); sqlColumnDefinition.setName(newTableColumn.getName()); sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (newTableColumn.getNullable()!=1) { + if (newTableColumn.getNullable() != 1) { sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); } if (!Objects.isNull(newTableColumn.getDefaultValue())) { @@ -301,17 +331,17 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab // 比较主键是否有修改 // 主键 Set oldPrimaryKeySet = EasyCollectionUtils.stream(oldTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .collect(Collectors.toSet()); + .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) + .map(TableColumn::getName) + .collect(Collectors.toSet()); Set newPrimaryKeySet = EasyCollectionUtils.stream(newTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .collect(Collectors.toSet()); + .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) + .map(TableColumn::getName) + .collect(Collectors.toSet()); boolean primaryKeyChange = oldPrimaryKeySet.stream() - .anyMatch(oldPrimaryKey -> !newPrimaryKeySet.contains(oldPrimaryKey)) - || newPrimaryKeySet.stream() - .anyMatch(newPrimaryKey -> !oldPrimaryKeySet.contains(newPrimaryKey)); + .anyMatch(oldPrimaryKey -> !newPrimaryKeySet.contains(oldPrimaryKey)) + || newPrimaryKeySet.stream() + .anyMatch(newPrimaryKey -> !oldPrimaryKeySet.contains(newPrimaryKey)); if (primaryKeyChange) { sqlAlterTableStatement.addItem(new SQLAlterTableDropPrimaryKey()); SQLAlterTableAddConstraint sqlAlterTableAddConstraint = new SQLAlterTableAddConstraint(); @@ -321,10 +351,10 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab mySqlPrimaryKey.setIndexType("PRIMARY"); // 排序 EasyCollectionUtils.stream(newTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .forEach(tableColumnName -> mySqlPrimaryKey.addColumn( - new SQLSelectOrderByItem(new SQLIdentifierExpr(tableColumnName)))); + .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) + .map(TableColumn::getName) + .forEach(tableColumnName -> mySqlPrimaryKey.addColumn( + new SQLSelectOrderByItem(new SQLIdentifierExpr(tableColumnName)))); } if (CollectionUtils.isNotEmpty(sqlAlterTableStatement.getItems())) { @@ -334,9 +364,9 @@ private static void modifyColumn(List sqlList, Table oldTable, Table newTab private static void modifyIndex(List sqlList, Table oldTable, Table newTable) { Map oldIndexMap = EasyCollectionUtils.toIdentityMap(oldTable.getIndexList(), - TableIndex::getName); + TableIndex::getName); Map newIndexMap = EasyCollectionUtils.toIdentityMap(newTable.getIndexList(), - TableIndex::getName); + TableIndex::getName); newIndexMap.forEach((newTableIndexName, newTableIndex) -> { TableIndex oldTableIndex = oldIndexMap.get(newTableIndexName); // 代表新增索引 @@ -352,7 +382,7 @@ private static void modifyIndex(List sqlList, Table oldTable, Table newTabl SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); + tableIndexColumn.getCollation()); if (collation != null) { sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); } @@ -364,34 +394,34 @@ private static void modifyIndex(List sqlList, Table oldTable, Table newTabl } // 代表可能修改索引 或者没变 boolean hasChange = !StringUtils.equals(oldTableIndex.getName(), newTableIndex.getName()) - || !StringUtils.equals(oldTableIndex.getComment(), newTableIndex.getComment()) - || !Objects.equals(oldTableIndex.getUnique(), newTableIndex.getUnique()); + || !StringUtils.equals(oldTableIndex.getComment(), newTableIndex.getComment()) + || !Objects.equals(oldTableIndex.getUnique(), newTableIndex.getUnique()); if (!hasChange) { Map oldTableIndexColumnMap = EasyCollectionUtils.toIdentityMap( - oldTableIndex.getColumnList(), TableIndexColumn::getColumnName); + oldTableIndex.getColumnList(), TableIndexColumn::getColumnName); Map newTableIndexColumnMap = EasyCollectionUtils.toIdentityMap( - newTableIndex.getColumnList(), TableIndexColumn::getColumnName); + newTableIndex.getColumnList(), TableIndexColumn::getColumnName); hasChange = oldTableIndexColumnMap.entrySet() - .stream() - .anyMatch(oldTableIndexColumnEntry -> { - TableIndexColumn newTableIndexColumn = newTableIndexColumnMap.get( - oldTableIndexColumnEntry.getKey()); - if (newTableIndexColumn == null) { - return true; - } - TableIndexColumn oldTableIndexColumn = oldTableIndexColumnEntry.getValue(); - return !StringUtils.equals(oldTableIndexColumn.getColumnName(), - newTableIndexColumn.getColumnName()) - || !CollationEnum.equals(oldTableIndexColumn.getCollation(), - newTableIndexColumn.getCollation()); - }) - || newTableIndexColumnMap.entrySet() - .stream() - .anyMatch(newTableIndexColumnEntry -> { - TableIndexColumn oldTableIndexColumn = oldTableIndexColumnMap.get( - newTableIndexColumnEntry.getKey()); - return oldTableIndexColumn == null; - }); + .stream() + .anyMatch(oldTableIndexColumnEntry -> { + TableIndexColumn newTableIndexColumn = newTableIndexColumnMap.get( + oldTableIndexColumnEntry.getKey()); + if (newTableIndexColumn == null) { + return true; + } + TableIndexColumn oldTableIndexColumn = oldTableIndexColumnEntry.getValue(); + return !StringUtils.equals(oldTableIndexColumn.getColumnName(), + newTableIndexColumn.getColumnName()) + || !CollationEnum.equals(oldTableIndexColumn.getCollation(), + newTableIndexColumn.getCollation()); + }) + || newTableIndexColumnMap.entrySet() + .stream() + .anyMatch(newTableIndexColumnEntry -> { + TableIndexColumn oldTableIndexColumn = oldTableIndexColumnMap.get( + newTableIndexColumnEntry.getKey()); + return oldTableIndexColumn == null; + }); } // 没有修改索引 @@ -417,7 +447,7 @@ private static void modifyIndex(List sqlList, Table oldTable, Table newTabl SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); + tableIndexColumn.getCollation()); if (collation != null) { sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); } @@ -449,8 +479,8 @@ public static String getTableName(String sql, DbType dbType) { if (!(sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { throw new BusinessException("dataSource.sqlAnalysisError"); } - SQLExprTableSource sqlExprTableSource = (SQLExprTableSource)getSQLExprTableSource( - sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( + sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); if (sqlExprTableSource == null) { return DEFAULT_TABLE_NAME; } @@ -466,7 +496,7 @@ private static SQLTableSource getSQLExprTableSource(SQLTableSource sqlTableSourc return null; } - public static List parse(String sql,DbType dbType) { + public static List parse(String sql, DbType dbType) { List list = new ArrayList<>(); try { Statements statements = CCJSqlParserUtil.parseStatements(sql); @@ -480,4 +510,11 @@ public static List parse(String sql,DbType dbType) { return list; } + public static String getSqlValue(String value, String dataType) { + if(value == null){ + return null; + } + DataTypeEnum dataTypeEnum = DataTypeEnum.getByCode(dataType); + return dataTypeEnum.getSqlValue(value); + } } \ No newline at end of file From 84c4111538277e606c6de50d82adfa44b828f4e6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 8 Oct 2023 09:13:23 +0800 Subject: [PATCH 0836/1069] init edit table data --- chat2db-client/.eslintrc.js | 2 +- .../src/blocks/DatabaseTableEditor/index.less | 1 + .../src/blocks/DatabaseTableEditor/index.tsx | 5 +- .../src/blocks/EditTableData/index.less | 4 + .../src/blocks/EditTableData/index.tsx | 18 + .../src/blocks/SQLExecute/index.tsx | 20 +- .../ConnectionEdit/config/dataSource.ts | 9 +- .../components/ConnectionEdit/config/enum.ts | 11 - .../src/components/Iconfont/index.tsx | 6 +- .../SearchResult/TableBox/index.less | 7 +- .../SearchResult/TableBox/index.tsx | 46 +- .../src/components/SearchResult/index.less | 54 +- .../src/components/SearchResult/index.tsx | 131 ++-- .../src/components/TabsNew/index.less | 1 + .../src/components/TabsNew/index.tsx | 79 +-- chat2db-client/src/constants/tree.ts | 15 +- chat2db-client/src/constants/workspace.ts | 16 +- chat2db-client/src/i18n/en-us/workspace.ts | 1 + chat2db-client/src/i18n/zh-cn/workspace.ts | 3 +- .../workspace/components/TableList/index.tsx | 2 +- .../Tree/TreeNodeRightClick/index.tsx | 29 +- .../main/workspace/components/Tree/index.tsx | 234 ++++--- .../workspace/components/Tree/treeConfig.tsx | 585 +++++++++--------- .../components/WorkspaceRightNew/index.tsx | 31 +- .../src/pages/main/workspace/index.tsx | 31 +- chat2db-client/src/typings/workspace.ts | 1 + 26 files changed, 683 insertions(+), 659 deletions(-) create mode 100644 chat2db-client/src/blocks/EditTableData/index.less create mode 100644 chat2db-client/src/blocks/EditTableData/index.tsx diff --git a/chat2db-client/.eslintrc.js b/chat2db-client/.eslintrc.js index 4e44d483e..4b0e38a63 100644 --- a/chat2db-client/.eslintrc.js +++ b/chat2db-client/.eslintrc.js @@ -58,7 +58,7 @@ module.exports = { // ], 'no-duplicate-imports': [2], // 禁止重复 import 'newline-per-chained-call': 2, // 链式调用必须换行 - 'no-underscore-dangle': 2, // 禁止标识符中有悬空下划线 + // 'no-underscore-dangle': 2, // 禁止标识符中有悬空下划线 'eol-last': 2, // 文件以单一的换行符结束 'no-useless-rename': 2, // 禁止无用的重命名 'no-undef': 0, // 禁止使用未定义的变量 diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.less b/chat2db-client/src/blocks/DatabaseTableEditor/index.less index 617560a52..e5b720e26 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.less +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.less @@ -50,6 +50,7 @@ line-height: 26px; border-radius: 4px; cursor: pointer; + font-size: 13px; &:hover { color: var(--color-primary); } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 77a44ad43..6e4cb55f7 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -8,7 +8,7 @@ import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams, IExecuteSqlParams } from '@/service/sql'; import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; import { IEditTableInfo, IWorkspaceTab } from '@/typings'; -import { DatabaseTypeCode } from '@/constants'; +import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; import Iconfont from '@/components/Iconfont'; @@ -155,7 +155,8 @@ export default memo((props: IProps) => { if (!tableName) { changeTabDetails({ ...tabDetails, - title: `edit-${newTableName}`, + title: `${newTableName}`, + type: WorkspaceTabType.EditTable, uniqueData: { ...(tabDetails.uniqueData || {}), tableName: newTableName, diff --git a/chat2db-client/src/blocks/EditTableData/index.less b/chat2db-client/src/blocks/EditTableData/index.less new file mode 100644 index 000000000..2e57988d1 --- /dev/null +++ b/chat2db-client/src/blocks/EditTableData/index.less @@ -0,0 +1,4 @@ +@import '../../styles/var.less'; + +.box { +} diff --git a/chat2db-client/src/blocks/EditTableData/index.tsx b/chat2db-client/src/blocks/EditTableData/index.tsx new file mode 100644 index 000000000..4ddd0d5ee --- /dev/null +++ b/chat2db-client/src/blocks/EditTableData/index.tsx @@ -0,0 +1,18 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { DatabaseTypeCode } from '@/constants'; + +interface IProps { + className?: string; + dataSourceId: number; + databaseName: string; + schemaName: string | undefined; + tableName?: string; + databaseType: DatabaseTypeCode; +} + +export default memo((props) => { + const { className } = props; + return
    EditTableData
    ; +}); diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 36288a194..35adb8e49 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -10,11 +10,9 @@ import { IManageResultData, IResultConfig } from '@/typings'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import historyServer, { ISaveBasicInfo } from '@/service/history'; import { IAIState } from '@/models/ai'; -import sqlServer, { IExecuteSqlParams, IExportParams } from '@/service/sql'; +import sqlServer, { IExecuteSqlParams } from '@/service/sql'; import { v4 as uuidV4 } from 'uuid'; import { isNumber } from 'lodash'; -import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; -import { downloadFile } from '@/utils/common'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { className?: string; @@ -125,7 +123,6 @@ const SQLExecute = memo((props) => { const sqlResult = await sqlServer.executeSql(param); resultData[index] = { ...resultData[index], ...sqlResult[0] }; setResultData([...resultData]); - resultConfig[index] = { ...config, total: isNumber(resultConfig[index].total) ? resultConfig[index].total : sqlResult[0].fuzzyTotal, @@ -146,16 +143,6 @@ const SQLExecute = memo((props) => { return total; }; - const handleExportSQLResult = async ( - sql: string, - originalSql: string, - exportType: ExportTypeEnum, - exportSize: ExportSizeEnum, - ) => { - const params: IExportParams = { ...data, sql, originalSql, exportType, exportSize }; - downloadFile(window._BaseURL + '/api/rdb/dml/export', params); - }; - const handleResultTabEdit = (type: 'add' | 'remove', uuid?: string | number) => { if (type === 'remove') { const tabIndex = resultData.findIndex((d) => d.uuid === uuid); @@ -205,10 +192,9 @@ const SQLExecute = memo((props) => { onTabEdit={handleResultTabEdit} onExecute={handleExecuteSQLbyConfigChanged} onSearchTotal={handleSearchTotal} - onExport={handleExportSQLResult} - manageResultDataList={resultData} + executeSqlParams={data} resultConfig={resultConfig} - isLoading={tableLoading} + manageResultDataList={resultData} /> } diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index bbe45b307..af7536848 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -1,6 +1,6 @@ -import { DatabaseTypeCode } from '@/constants'; +import { DatabaseTypeCode, OperationColumn } from '@/constants'; import { IConnectionConfig } from './types'; -import { InputType, AuthenticationType, SSHAuthenticationType, OperationColumn } from './enum'; +import { InputType, AuthenticationType } from './enum'; export const sshConfig: IConnectionConfig['ssh'] = { items: [ @@ -360,6 +360,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:postgresql:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:postgresql://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] }, ssh: sshConfig, }, @@ -611,6 +612,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:h2:tcp://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] }, ssh: sshConfig, }, @@ -1013,7 +1015,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:clickhouse:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:clickhouse://{host}:{port}/{database}', - excludes: [OperationColumn.ExportDDL, OperationColumn.CreateTable] //排除掉导出ddl 和 创建表功能 支持的功能见 ./enum.ts => OperationColumn + excludes: [OperationColumn.ExportDDL, OperationColumn.CreateTable] + //排除掉导出ddl 和 创建表功能 支持的功能见 ./enum.ts => OperationColumn }, ssh: sshConfig, }, diff --git a/chat2db-client/src/components/ConnectionEdit/config/enum.ts b/chat2db-client/src/components/ConnectionEdit/config/enum.ts index d6b8499d3..6c0e425cf 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/enum.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/enum.ts @@ -14,14 +14,3 @@ export enum SSHAuthenticationType { KEYPAIR = 2, OPENSSH = 3 } - -// 树右键支持的功能 -export enum OperationColumn { - ShiftOut = 'shiftOut', // 移出数据源 - Refresh = 'refresh', // 刷新各级菜单 - CreateTable = 'createTable', //创建表 - CreateConsole = 'createConsole', // 新建console - DeleteTable = 'deleteTable', // 删除表 - ExportDDL = 'exportDDL', // 导出ddl - EditSource = 'editSource', // 编辑数据源 -} diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index ff64a0aab..f1016c699 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_rlj1v1fcjnk.woff2?t=1695716733221') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_rlj1v1fcjnk.woff?t=1695716733221') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_rlj1v1fcjnk.ttf?t=1695716733221') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_dom9o9rlupe.woff2?t=1696660819240') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_dom9o9rlupe.woff?t=1696660819240') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_dom9o9rlupe.ttf?t=1696660819240') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 563a346e2..2be1f27a8 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -6,13 +6,10 @@ left: 0; right: 0; bottom: 0; - display: none; overflow: auto; + display: flex; + flex-direction: column; :global { - // .hGEOgo td { - // border-bottom: 0; - // // height: 400px !important; - // } .hGEOgo { --header-bgcolor: #fafafa; --header-color: #000; diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 312d8f851..5be0c62b3 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -4,20 +4,19 @@ import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali- import styled from 'styled-components'; import classnames from 'classnames'; import i18n from '@/i18n'; -import { ThemeType } from '@/constants'; import { TableDataType } from '@/constants/table'; -import { useTheme } from '@/hooks/useTheme'; import { IManageResultData, IResultConfig } from '@/typings/database'; import { compareStrings } from '@/utils/sort'; -import { DownOutlined, UserOutlined } from '@ant-design/icons'; +import { DownOutlined } from '@ant-design/icons'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; -import { formatDate } from '@/utils/date'; import { copy } from '@/utils'; import Iconfont from '../../Iconfont'; import StateIndicator from '../../StateIndicator'; import MonacoEditor from '../../Console/MonacoEditor'; import MyPagination from '../Pagination'; import styles from './index.less'; +import { IExportParams } from '@/service/sql'; +import { downloadFile } from '@/utils/common'; interface ITableProps { className?: string; @@ -25,7 +24,7 @@ interface ITableProps { config: IResultConfig; onConfigChange: (config: IResultConfig) => void; onSearchTotal: () => Promise; - onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => void; + executeSqlParams: any; } interface IViewTableCellData { @@ -33,6 +32,13 @@ interface IViewTableCellData { value: any; } +// const defaultResultConfig: IResultConfig = { +// pageNo: 1, +// pageSize: 200, +// total: 0, +// hasNextPage: true, +// }; + const SupportBaseTable: any = styled(BaseTable)` &.supportBaseTable { --bgcolor: var(--color-bg-base); @@ -51,15 +57,21 @@ const SupportBaseTable: any = styled(BaseTable)` const preCode = '$$chat2db_'; export default function TableBox(props: ITableProps) { - const { className, data, config, onConfigChange, onSearchTotal } = props; - const { headerList, dataList, duration, description, sqlType } = data || {}; + const { className, data, config, onConfigChange } = props; + const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); - const [appTheme] = useTheme(); - const isDarkTheme = useMemo(() => appTheme.backgroundColor === ThemeType.Dark, [appTheme]); const [messageApi, contextHolder] = message.useMessage(); + // const [pageConfig, setPageConfig] = useState(defaultResultConfig); - const handleExport = (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { - props.onExport && props.onExport(data.sql, data.originalSql, exportType, exportSize); + const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { + const params: IExportParams = { + ...(props.executeSqlParams || {}), + sql: data.sql, + originalSql: data.originalSql, + exportType, + exportSize, + }; + downloadFile(window._BaseURL + '/api/rdb/dml/export', params); }; const items: MenuProps['items'] = useMemo( @@ -69,7 +81,7 @@ export default function TableBox(props: ITableProps) { key: '1', // icon: , onClick: () => { - handleExport(ExportTypeEnum.CSV, ExportSizeEnum.ALL); + handleExportSQLResult(ExportTypeEnum.CSV, ExportSizeEnum.ALL); }, }, { @@ -77,7 +89,7 @@ export default function TableBox(props: ITableProps) { key: '2', // icon: , onClick: () => { - handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); + handleExportSQLResult(ExportTypeEnum.INSERT, ExportSizeEnum.ALL); }, }, { @@ -85,7 +97,7 @@ export default function TableBox(props: ITableProps) { key: '3', // icon: , onClick: () => { - handleExport(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); + handleExportSQLResult(ExportTypeEnum.CSV, ExportSizeEnum.CURRENT_PAGE); }, }, { @@ -93,7 +105,7 @@ export default function TableBox(props: ITableProps) { key: '4', // icon: , onClick: () => { - handleExport(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); + handleExportSQLResult(ExportTypeEnum.INSERT, ExportSizeEnum.CURRENT_PAGE); }, }, ], @@ -150,7 +162,7 @@ export default function TableBox(props: ITableProps) { name: name, key: name, width: 120, - render: (value: any, row: any, rowIndex: number) => { + render: (value: any) => { return (
    {value} @@ -172,7 +184,7 @@ export default function TableBox(props: ITableProps) { if (!columns?.length) { return []; } else { - return (dataList || []).map((item, rowIndex) => { + return (dataList || []).map((item) => { const rowData: any = {}; item.map((i: string | null, colIndex: number) => { const name = `${preCode}${colIndex}${columns[colIndex].name}`; diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 6007a1a3f..867914be9 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -1,13 +1,13 @@ @import '../../styles/var.less'; -.box { +.searchResult { height: 100%; display: flex; flex-direction: column; } .tabs { - border-bottom: 1px solid var(--color-border); + height: 100%; } .recordIcon { @@ -15,49 +15,17 @@ margin-right: 4px; } -.resultHeader { - flex-shrink: 0; - overflow-x: scroll; - background-color: var(--color-bg-base); - - &::-webkit-scrollbar { - display: none; - } - - .statusIcon { - margin-right: 6px; - font-size: 12px; - } - - .successIcon { - color: var(--color-primary); - } - - .failIcon { - color: var(--color-primary); - } +.statusIcon { + margin-right: 6px; + font-size: 12px; } -.resultContentWrapper { - flex: 1; - - :global { - .ant-spin-container { - height: 100%; - width: 100%; - position: relative; - overflow: auto; - } - } +.successIcon { + color: var(--color-primary); } -.cursorTableBox { - // display: block !important; - // height: 100%; - display: flex !important; - flex-direction: column; - justify-content: space-between; - // background-color: var(--bgcolor); +.failIcon { + color: var(--color-primary); } .tableIndex { @@ -81,10 +49,6 @@ font-size: 12px; } -.stateIndicator { - display: none; -} - .cursorStateIndicator { margin: 0 auto; max-width: 80%; diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index b9baf9639..909c48ff5 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,14 +1,10 @@ -import React, { memo, useEffect, useState, useMemo, Fragment, useCallback } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import classnames from 'classnames'; -import Tabs, { IOption } from '@/components/Tabs'; +import TabsNew from '@/components/TabsNew'; import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; -import { Spin, Popover } from 'antd'; import { IManageResultData, IResultConfig } from '@/typings'; -import i18n from '@/i18n'; import TableBox from './TableBox'; -import EmptyImg from '@/assets/img/empty.svg'; -import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import styles from './index.less'; interface IProps { @@ -16,40 +12,16 @@ interface IProps { manageResultDataList?: IManageResultData[]; resultConfig: IResultConfig[]; onExecute: (sql: string, config: IResultConfig, index: number) => void; - onExport: (sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => Promise; onTabEdit: (type: 'add' | 'remove', value?: number | string) => void; onSearchTotal: (index: number) => Promise; - isLoading?: boolean; + executeSqlParams: any; } -interface DataType { - [key: string]: any; -} - -const handleTabs = (result: IManageResultData[]) => { - return (result || []).map((item, index) => { - return { - label: ( - - - {`${i18n('common.text.executionResult')}-${index + 1}`} - - ), - value: item.uuid!, - }; - }); -}; - -export default memo(function SearchResult(props) { - const { className, manageResultDataList = [], isLoading, onExecute, onExport } = props; +export default memo((props) => { + const { className, manageResultDataList = [], onExecute } = props; const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState([]); const [resultConfig, setResultConfig] = useState([]); - const [tabs, setTabs] = useState([]); useEffect(() => { setResultConfig(props.resultConfig); @@ -65,45 +37,24 @@ export default memo(function SearchResult(props) { } setResultDataList([...manageResultDataList]); - setTabs(handleTabs(manageResultDataList)); }, [manageResultDataList]); - function onChange(uuid: string | number) { + const onChange = useCallback((uuid: string | number) => { setCurrentTab(uuid); - } + }, []); - function onEdit(type: 'add' | 'remove', value?: number | string) { + const onEdit = useCallback((type: 'add' | 'remove', value?: number | string) => { props.onTabEdit && props.onTabEdit(type, value); - } - - const renderEmpty = () => { - return ( -
    - -

    {i18n('common.text.noData')}

    -
    - ); - }; - - const renderTable = () => { - if (!tabs || !tabs.length) { - return renderEmpty(); - } - if (!resultDataList || !resultDataList.length) { - return renderEmpty(); - } - - return renderTableContent; - }; + }, []); - const renderTableContent = (resultDataList || []).map((item, index: number) => { + const renderTable = (item, index) => { if (item.success) { return ( { onExecute && onExecute(item.originalSql, config, index); }} @@ -112,41 +63,43 @@ export default memo(function SearchResult(props) { return await props.onSearchTotal(index); } }} - onExport={(sql: string, originalSql: string, exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { - onExport && onExport(sql, originalSql, exportType, exportSize); - }} /> ); } else { - return ( - - ); + return ; } - }); + }; - return ( -
    - {tabs.length ? ( -
    - { + return resultDataList.map((item, index) => { + return { + prefixIcon: ( + -
    + ), + popover: item.originalSql, + label: item.originalSql, + key: item.uuid!, + children: renderTable(item, index), + }; + }); + }, [resultDataList]); + + return ( +
    + {tabsList.length ? ( + ) : null} - - {renderTable()} -
    ); }); diff --git a/chat2db-client/src/components/TabsNew/index.less b/chat2db-client/src/components/TabsNew/index.less index 681d80e52..aee12e966 100644 --- a/chat2db-client/src/components/TabsNew/index.less +++ b/chat2db-client/src/components/TabsNew/index.less @@ -39,6 +39,7 @@ height: 100%; width: 100%; display: none; + position: relative; } .tabsContentItemActive { display: block; diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 37593f7fb..23d96a890 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -2,12 +2,15 @@ import React, { memo, useEffect, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; import styles from './index.less'; +import { Popover } from 'antd'; export interface ITabItem { - prefixIcon?: string; + prefixIcon?: string | React.ReactNode; label: React.ReactNode; key: number | string; + popover?: string | React.ReactNode; children?: React.ReactNode; + editableName?: boolean | undefined; } export interface IOnchangeProps { @@ -23,12 +26,11 @@ interface IProps { onEdit?: (action: 'add' | 'remove', data?: ITabItem) => void; hideAdd?: boolean; type?: 'line'; - editableName?: boolean; editableNameOnBlur?: (option: ITabItem) => void; } export default memo((props) => { - const { className, items, onChange, onEdit, activeKey, hideAdd, type, editableName, editableNameOnBlur } = props; + const { className, items, onChange, onEdit, activeKey, hideAdd, type, editableNameOnBlur } = props; const [internalTabs, setInternalTabs] = useState([]); const [internalActiveTab, setInternalActiveTab] = useState(); const [editingTab, setEditingTab] = useState(); @@ -76,7 +78,7 @@ export default memo((props) => { } function onDoubleClick(t: ITabItem) { - if (editableName) { + if (t.editableName) { setEditingTab(t.key); } } @@ -93,39 +95,46 @@ export default memo((props) => { } return ( -
    { - onDoubleClick(t); - }} - key={t.key} - className={classnames( - { [styles.tabItem]: type !== 'line' }, - { [styles.tabItemLine]: type === 'line' }, - { [styles.activeTabLine]: t.key === internalActiveTab && type === 'line' }, - { [styles.activeTab]: t.key === internalActiveTab && type !== 'line' }, - )} - > - {t.key === editingTab ? ( - { - inputOnChange(e.target.value); - }} - className={styles.input} - autoFocus - onBlur={onBlur} - type="text" - /> - ) : ( -
    - {t.prefixIcon && } -
    {t.label}
    + +
    { + onDoubleClick(t); + }} + key={t.key} + className={classnames( + { [styles.tabItem]: type !== 'line' }, + { [styles.tabItemLine]: type === 'line' }, + { [styles.activeTabLine]: t.key === internalActiveTab && type === 'line' }, + { [styles.activeTab]: t.key === internalActiveTab && type !== 'line' }, + )} + > + {t.key === editingTab ? ( + { + inputOnChange(e.target.value); + }} + className={styles.input} + autoFocus + onBlur={onBlur} + type="text" + /> + ) : ( +
    + {t.prefixIcon && + (typeof t.prefixIcon == 'string' ? ( + + ) : ( + t.prefixIcon + ))} +
    {t.label}
    +
    + )} +
    +
    - )} -
    -
    -
    +
    ); } diff --git a/chat2db-client/src/constants/tree.ts b/chat2db-client/src/constants/tree.ts index 648f32d5b..7e294356f 100644 --- a/chat2db-client/src/constants/tree.ts +++ b/chat2db-client/src/constants/tree.ts @@ -21,5 +21,18 @@ export enum TreeNodeType { PROCEDURE = 'procedure', // procedure TRIGGERS = 'triggers', // trigger组 TRIGGER = 'trigger', // trigger +} -} \ No newline at end of file +// 树右键支持的功能 +export enum OperationColumn { + ShiftOut = 'shiftOut', // 移出数据源 + Refresh = 'refresh', // 刷新各级菜单 + CreateTable = 'createTable', //创建表 + CreateConsole = 'createConsole', // 新建console + DeleteTable = 'deleteTable', // 删除表 + ExportDDL = 'exportDDL', // 导出ddl + EditSource = 'editSource', // 编辑数据源 + Top = 'top', // 置顶 + EditTable = 'editTable', // 编辑表 + EditTableData = 'editTableData', // 编辑表数据 +} diff --git a/chat2db-client/src/constants/workspace.ts b/chat2db-client/src/constants/workspace.ts index a12c119ce..60f26f83a 100644 --- a/chat2db-client/src/constants/workspace.ts +++ b/chat2db-client/src/constants/workspace.ts @@ -1,18 +1,19 @@ export enum CreateTabIntroType { - EditorTable = 'EditorTable', - OpenTable = 'openTable', + EditorTable = 'editorTable', + EditTableData = 'editTableData', } // 工作台Tab的类型 export enum WorkspaceTabType { - CONSOLE = 'console', + CONSOLE = 'table', FUNCTION = 'function', PROCEDURE = 'procedure', VIEW = 'view', TRIGGER = 'trigger', EditTable = 'editTable', - OpenTable = 'openTable', + CreateTable = 'createTable', + EditTableData = 'editTableData', } // 工作台Tab的类型对应的一些配置 @@ -37,10 +38,13 @@ export const workspaceTabConfig: { icon: '\ue64a' }, [WorkspaceTabType.EditTable]: { + icon: '\ue6f3' + }, + [WorkspaceTabType.CreateTable]: { icon: '\ue6b6' }, - [WorkspaceTabType.OpenTable]: { - icon: '\ue618' + [WorkspaceTabType.EditTableData]: { + icon: '\ue7b5' } } diff --git a/chat2db-client/src/i18n/en-us/workspace.ts b/chat2db-client/src/i18n/en-us/workspace.ts index 6f05a02cc..c252f5254 100644 --- a/chat2db-client/src/i18n/en-us/workspace.ts +++ b/chat2db-client/src/i18n/en-us/workspace.ts @@ -7,6 +7,7 @@ export default { 'workspace.menu.editTable': 'Edit Table', 'workspace.menu.pin': 'Pin', 'workspace.menu.unPin': 'Unpin', + 'workspace.menu.editTableData': 'Edit Table Data', '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/workspace.ts b/chat2db-client/src/i18n/zh-cn/workspace.ts index 5d1f3d56b..67a94d14d 100644 --- a/chat2db-client/src/i18n/zh-cn/workspace.ts +++ b/chat2db-client/src/i18n/zh-cn/workspace.ts @@ -4,9 +4,10 @@ export default { 'workspace.title.saved': '保存记录', 'workspace.menu.exportDDL': '导出DDL', 'workspace.menu.deleteTable': '删除表', - 'workspace.menu.editTable': '编辑表', + 'workspace.menu.editTable': '修改表', 'workspace.menu.pin': '置顶', 'workspace.menu.unPin': '取消置顶', + 'workspace.menu.editTableData': '编辑表数据', 'workspace.menu.deleteTablePlaceHolder': '请输入你要删除的表名', 'workspace.tips.affirmDeleteTable': '输入的表名与要删除的表名不一致,请再次确认', 'workspace.table.total': '总数', 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 aa8394436..017bc847d 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -75,7 +75,7 @@ const TableList = dvaModel((props: any) => { type: 'workspace/setCreateConsoleIntro', payload: { id: uuidV4(), - type: WorkspaceTabType.EditTable, + type: WorkspaceTabType.CreateTable, title: 'create-table', uniqueData: {}, }, diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 0e0c53a55..9504ad94b 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -3,12 +3,11 @@ import i18n from '@/i18n'; import styles from './index.less'; import Iconfont from '@/components/Iconfont'; import { message, Modal, Input, Dropdown, notification } from 'antd'; -import { TreeNodeType, CreateTabIntroType, WorkspaceTabType } from '@/constants'; +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'; -import { OperationColumn } from '../treeConfig'; import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; import { IWorkspaceModelType } from '@/models/workspace'; @@ -52,6 +51,15 @@ function TreeNodeRightClick(props: IProps) { }, }; }, + [OperationColumn.EditTableData]: (data) => { + return { + text: i18n('workspace.menu.editTableData'), + icon: '\ue7b5', + handle: () => { + openEditTableData(); + }, + }; + }, [OperationColumn.ExportDDL]: (data) => { return { text: i18n('workspace.menu.exportDDL'), @@ -180,6 +188,17 @@ function TreeNodeRightClick(props: IProps) { }); } + function openEditTableData() { + dispatch({ + type: 'workspace/setCreateTabIntro', + payload: { + type: CreateTabIntroType.EditTableData, + workspaceTabType: WorkspaceTabType.EditTableData, + treeNodeData: data, + }, + }); + } + function handleOk() { if (verifyTableName === data.key) { let p: any = { @@ -258,7 +277,7 @@ function TreeNodeRightClick(props: IProps) { }} >
    - +
    )} @@ -278,7 +297,7 @@ function TreeNodeRightClick(props: IProps) { onChange={(e) => { setVerifyTableName(e.target.value); }} - > + /> {/* 这里后续肯定是要提出去的 */} {monacoVerifyDialog && ( @@ -299,7 +318,7 @@ function TreeNodeRightClick(props: IProps) { text: monacoDefaultValue, range: 'reset', }} - > + />
    )} diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index 6e42d3827..6d9aaba18 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -6,7 +6,7 @@ import Iconfont from '@/components/Iconfont'; import { Tooltip } from 'antd'; import { ITreeNode } from '@/typings'; import { callVar, approximateTreeNode } from '@/utils'; -import { TreeNodeType, databaseMap } from '@/constants'; +import { TreeNodeType, databaseMap, CreateTabIntroType, WorkspaceTabType } from '@/constants'; import TreeNodeRightClick from './TreeNodeRightClick'; import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; import { IWorkspaceModelType } from '@/models/workspace'; @@ -27,8 +27,8 @@ interface TreeNodeIProps { } const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace -})) + workspaceModel: workspace, +})); function Tree(props: IProps) { const { className, initialData } = props; @@ -37,75 +37,63 @@ function Tree(props: IProps) { useEffect(() => { setTreeData(initialData); - }, [initialData]) + }, [initialData]); function filtrationDataTree(keywords: string) { if (!keywords) { - setSearchedTreeData(null) + setSearchedTreeData(null); } else if (treeData?.length && keywords) { setSearchedTreeData(approximateTreeNode(treeData, keywords)); } } - return
    - { - (searchedTreeData || treeData)?.map((item, index) => { - return - }) - } -
    + return ( +
    + {(searchedTreeData || treeData)?.map((item, index) => { + return ; + })} +
    + ); } const TreeNode = dvaModel((props: TreeNodeIProps) => { - const { - setTreeData, - data, - level, - show = false, - showAllChildrenPenetrate = false, - dispatch, - workspaceModel - } = props; + const { setTreeData, data, level, show = false, showAllChildrenPenetrate = false, dispatch, workspaceModel } = props; const [showChildren, setShowChildren] = useState(false); const [isLoading, setIsLoading] = useState(false); const indentArr = new Array(level).fill('indent'); function loadData(data: ITreeNode) { const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; - treeNodeConfig.getChildren?.({ - ...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); + treeNodeConfig + .getChildren?.({ + ...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((error) => { setIsLoading(false); - // } - } - }).catch(error => { - setIsLoading(false); - }); + }); } useEffect(() => { setShowChildren(showAllChildrenPenetrate); - }, [showAllChildrenPenetrate]) + }, [showAllChildrenPenetrate]); //展开-收起 const handleClick = (data: ITreeNode) => { @@ -114,7 +102,7 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { } if (treeConfig[data.treeNodeType] && !data.children) { - loadData(data) + loadData(data); } else { setShowChildren(!showChildren); } @@ -122,25 +110,25 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { const recognizeIcon = (treeNodeType: TreeNodeType) => { if (treeNodeType === TreeNodeType.DATA_SOURCE) { - return databaseMap[data.extraParams?.databaseType!]?.icon + return databaseMap[data.extraParams?.databaseType!]?.icon; } else { - return switchIcon[treeNodeType]?.[showChildren ? 'unfoldIcon' : 'icon'] || switchIcon[treeNodeType]?.icon + return switchIcon[treeNodeType]?.[showChildren ? 'unfoldIcon' : 'icon'] || switchIcon[treeNodeType]?.icon; } - } + }; function renderTitle(data: ITreeNode) { - return <> - {data.name} - { - data.columnType && data.treeNodeType === TreeNodeType.COLUMN && - ({data.columnType}) - } - + return ( + <> + {data.name} + {data.columnType && data.treeNodeType === TreeNodeType.COLUMN && ( + ({data.columnType}) + )} + + ); } function nodeDoubleClick() { if ( - data.treeNodeType === TreeNodeType.TABLE || data.treeNodeType === TreeNodeType.FUNCTION || data.treeNodeType === TreeNodeType.TRIGGER || data.treeNodeType === TreeNodeType.VIEW || @@ -148,73 +136,83 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { ) { dispatch({ type: 'workspace/setDoubleClickTreeNodeData', - payload: data + payload: data, + }); + } else if (data.treeNodeType === TreeNodeType.TABLE) { + dispatch({ + type: 'workspace/setCreateTabIntro', + payload: { + type: CreateTabIntroType.EditTableData, + workspaceTabType: WorkspaceTabType.EditTableData, + treeNodeData: data, + }, }); } else { handleClick(data); } } - return show ? <> - -
    -
    - { - indentArr.map((item, i) => { - return
    - }) - } -
    -
    - { - !data.isLeaf && -
    - { - isLoading - ? + return show ? ( + <> + +
    +
    + {indentArr.map((item, i) => { + return
    ; + })} +
    +
    + {!data.isLeaf && ( +
    + {isLoading ? (
    - +
    - : - - } -
    - } -
    -
    - + ) : ( + + )} +
    + )} +
    +
    + +
    +
    +
    + {data.treeNodeType === TreeNodeType.COLUMN &&
    {data.columnType}
    } +
    -
    -
    - {data.treeNodeType === TreeNodeType.COLUMN &&
    {data.columnType}
    } +
    +
    -
    - -
    -
    - - { - data.children?.map((item: any, i: number) => { - return - }) - } - : <> -}) + + {data.children?.map((item: any, i: number) => { + return ( + + ); + })} + + ) : ( + <> + ); +}); export default Tree; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index b4a180adf..eb728d78a 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -1,70 +1,69 @@ import { ITreeNode } from '@/typings'; -import { TreeNodeType } from '@/constants'; +import { TreeNodeType, OperationColumn } from '@/constants'; import connectionService from '@/service/connection'; -import mysqlServer, { ISchemaParams, IGetListParams, ITableParams } from '@/service/sql'; - +import mysqlServer, { ISchemaParams, ITableParams } from '@/service/sql'; export type ITreeConfig = Partial<{ [key in TreeNodeType]: ITreeConfigItem }>; -export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string, unfoldIcon?: string } }> = { +export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string; unfoldIcon?: string } }> = { [TreeNodeType.DATABASE]: { icon: '\ue62c', }, [TreeNodeType.SCHEMAS]: { - icon: '\ue696' + icon: '\ue696', }, [TreeNodeType.TABLE]: { - icon: '\ue63e' + icon: '\ue63e', }, [TreeNodeType.TABLES]: { - icon: '\ueac5' + icon: '\ueac5', }, [TreeNodeType.COLUMNS]: { icon: '\ueabe', - unfoldIcon: '\ueabf' + unfoldIcon: '\ueabf', }, [TreeNodeType.COLUMN]: { - icon: '\ue611' + icon: '\ue611', }, [TreeNodeType.KEYS]: { icon: '\ueabe', - unfoldIcon: '\ueabf' + unfoldIcon: '\ueabf', }, [TreeNodeType.KEY]: { icon: '\ue775', }, [TreeNodeType.INDEXES]: { icon: '\ueabe', - unfoldIcon: '\ueabf' + unfoldIcon: '\ueabf', }, [TreeNodeType.INDEX]: { - icon: '\ue65b' + icon: '\ue65b', }, [TreeNodeType.VIEW]: { - icon: '\ue70c' + icon: '\ue70c', }, [TreeNodeType.FUNCTION]: { - icon: '\ue76a' + icon: '\ue76a', }, [TreeNodeType.PROCEDURE]: { - icon: '\ue73c' + icon: '\ue73c', }, [TreeNodeType.TRIGGER]: { - icon: '\ue64a' + icon: '\ue64a', }, -} +}; -export enum OperationColumn { - Refresh = 'refresh', - ShiftOut = 'shiftOut', - CreateTable = 'createTable', - CreateConsole = 'createConsole', - DeleteTable = 'deleteTable', - ExportDDL = 'exportDDL', - EditSource = 'editSource', - Top = 'top', - EditTable = 'editTable', -} +// export enum OperationColumn { +// Refresh = 'refresh', +// ShiftOut = 'shiftOut', +// CreateTable = 'createTable', +// CreateConsole = 'createConsole', +// DeleteTable = 'deleteTable', +// ExportDDL = 'exportDDL', +// EditSource = 'editSource', +// Top = 'top', +// EditTable = 'editTable', +// } export interface ITreeConfigItem { icon?: string; @@ -79,88 +78,93 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { return new Promise((r: (value: ITreeNode[]) => void, j) => { let p = { pageNo: 1, - pageSize: 999 - } - connectionService.getList(p).then(res => { - const data: ITreeNode[] = res.data.map(t => { - return { - key: t.id!, - name: t.alias, - treeNodeType: TreeNodeType.DATA_SOURCE, - extraParams: { - databaseType: t.type, - dataSourceId: t.id, - dataSourceName: t.name, - } - } + pageSize: 999, + }; + connectionService + .getList(p) + .then((res) => { + const data: ITreeNode[] = res.data.map((t) => { + return { + key: t.id!, + name: t.alias, + treeNodeType: TreeNodeType.DATA_SOURCE, + extraParams: { + databaseType: t.type, + dataSourceId: t.id, + dataSourceName: t.name, + }, + }; + }); + r(data); }) - r(data); - }).catch(error => { - j() - }) - }) + .catch((error) => { + j(); + }); + }); }, }, [TreeNodeType.DATA_SOURCE]: { getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - connectionService.getDBList(params).then(res => { - const data: ITreeNode[] = res.map((t: any) => { - return { - key: t.name, - name: t.name, - treeNodeType: TreeNodeType.DATABASE, - extraParams: { - ...params.extraParams, - databaseName: t.name - } - } + connectionService + .getDBList(params) + .then((res) => { + const data: ITreeNode[] = res.map((t: any) => { + return { + key: t.name, + name: t.name, + treeNodeType: TreeNodeType.DATABASE, + extraParams: { + ...params.extraParams, + databaseName: t.name, + }, + }; + }); + r(data); }) - r(data); - }).catch(error => { - j() - }) - }) + .catch((error) => { + j(); + }); + }); }, - operationColumn: [ - OperationColumn.EditSource, OperationColumn.Refresh, OperationColumn.ShiftOut - ], - next: TreeNodeType.DATABASE + operationColumn: [OperationColumn.EditSource, OperationColumn.Refresh, OperationColumn.ShiftOut], + next: TreeNodeType.DATABASE, }, [TreeNodeType.DATABASE]: { icon: '\ue62c', getChildren: (params: ISchemaParams) => { return new Promise((r: (value: ITreeNode[], b?: any) => void, j) => { - connectionService.getSchemaList(params).then(res => { - const data: ITreeNode[] = res.map((t: any) => { - return { - key: t.name, - name: t.name, - treeNodeType: TreeNodeType.SCHEMAS, - schemaName: t.name, - } + connectionService + .getSchemaList(params) + .then((res) => { + const data: ITreeNode[] = res.map((t: any) => { + return { + key: t.name, + name: t.name, + treeNodeType: TreeNodeType.SCHEMAS, + schemaName: t.name, + }; + }); + r(data); + // if (data.length) { + // } else { + // let data = [ + // { + // key: params.databaseName + 'tables', + // name: 'tables', + // treeNodeType: TreeNodeType.TABLES, + // } + // ] + // r(data, 'custom'); + // } }) - r(data); - // if (data.length) { - // } else { - // let data = [ - // { - // key: params.databaseName + 'tables', - // name: 'tables', - // treeNodeType: TreeNodeType.TABLES, - // } - // ] - // r(data, 'custom'); - // } - }).catch(error => { - j() - }) - }) + .catch((error) => { + j(); + }); + }); }, - operationColumn: [ - OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh - ], - next: TreeNodeType.SCHEMAS + operationColumn: [OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh], + next: TreeNodeType.SCHEMAS, }, [TreeNodeType.SCHEMAS]: { icon: '\ue696', @@ -171,43 +175,42 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { key: parentData.name + 'tables', name: 'tables', treeNodeType: TreeNodeType.TABLES, - } - ] + }, + ]; r(data); - }) + }); }, - operationColumn: [ - OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh - ], + operationColumn: [OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh], }, [TreeNodeType.TABLES]: { icon: '\ueac5', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - mysqlServer.getList(params).then(res => { - const tableList: ITreeNode[] = res.data?.map((t: any) => { - return { - name: t.name, - treeNodeType: TreeNodeType.TABLE, - key: t.name, - pinned: t.pinned, - comment: t.comment, - extraParams: { - ...params.extraParams, - tableName: t.name - } - } + mysqlServer + .getList(params) + .then((res) => { + const tableList: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.name, + treeNodeType: TreeNodeType.TABLE, + key: t.name, + pinned: t.pinned, + comment: t.comment, + extraParams: { + ...params.extraParams, + tableName: t.name, + }, + }; + }); + r(tableList); }) - r(tableList); - }).catch(error => { - j() - }) - }) + .catch((error) => { + j(); + }); + }); }, - operationColumn: [ - OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh - ], + operationColumn: [OperationColumn.CreateConsole, OperationColumn.CreateTable, OperationColumn.Refresh], }, [TreeNodeType.TABLE]: { @@ -219,27 +222,31 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { name: 'columns', treeNodeType: TreeNodeType.COLUMNS, key: 'columns', - extraParams: params.extraParams + extraParams: params.extraParams, }, { name: 'keys', treeNodeType: TreeNodeType.KEYS, key: 'keys', - extraParams: params.extraParams + extraParams: params.extraParams, }, { name: 'indexs', treeNodeType: TreeNodeType.INDEXES, key: 'indexs', - extraParams: params.extraParams + extraParams: params.extraParams, }, - ] + ]; r(list); - }) + }); }, operationColumn: [ - OperationColumn.Top, OperationColumn.ExportDDL, OperationColumn.EditTable, OperationColumn.DeleteTable, + OperationColumn.EditTableData, + OperationColumn.Top, + OperationColumn.ExportDDL, + OperationColumn.EditTable, + OperationColumn.DeleteTable, ], }, @@ -247,25 +254,28 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ue70c', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - mysqlServer.getViewList(params).then(res => { - const viewList: ITreeNode[] = res.data?.map((t: any) => { - return { - name: t.name, - treeNodeType: TreeNodeType.VIEW, - key: t.name, - pinned: t.pinned, - comment: t.comment, - extraParams: { - ...params.extraParams, - tableName: t.name - } - } + mysqlServer + .getViewList(params) + .then((res) => { + const viewList: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.name, + treeNodeType: TreeNodeType.VIEW, + key: t.name, + pinned: t.pinned, + comment: t.comment, + extraParams: { + ...params.extraParams, + tableName: t.name, + }, + }; + }); + r(viewList); }) - r(viewList); - }).catch(error => { - j(error) - }) - }) + .catch((error) => { + j(error); + }); + }); }, }, @@ -273,26 +283,29 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ue76a', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - mysqlServer.getFunctionList(params).then(res => { - const list: ITreeNode[] = res.data?.map((t: any) => { - return { - name: t.functionName, - treeNodeType: TreeNodeType.FUNCTION, - key: t.name, - pinned: t.pinned, - comment: t.comment, - isLeaf: true, - extraParams: { - ...params.extraParams, - functionName: t.functionName - } - } + mysqlServer + .getFunctionList(params) + .then((res) => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.functionName, + treeNodeType: TreeNodeType.FUNCTION, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + functionName: t.functionName, + }, + }; + }); + r(list); }) - r(list); - }).catch(error => { - j(error) - }) - }) + .catch((error) => { + j(error); + }); + }); }, }, @@ -304,62 +317,68 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ue73c', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - mysqlServer.getProcedureList(params).then(res => { - const list: ITreeNode[] = res.data?.map((t: any) => { - return { - name: t.procedureName, - treeNodeType: TreeNodeType.PROCEDURE, - key: t.name, - pinned: t.pinned, - comment: t.comment, - isLeaf: true, - extraParams: { - ...params.extraParams, - procedureName: t.procedureName - } - } + mysqlServer + .getProcedureList(params) + .then((res) => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.procedureName, + treeNodeType: TreeNodeType.PROCEDURE, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + procedureName: t.procedureName, + }, + }; + }); + r(list); }) - r(list); - }).catch(error => { - j(error) - }) - }) + .catch((error) => { + j(error); + }); + }); }, }, [TreeNodeType.PROCEDURE]: { - icon: '\ue73c' + icon: '\ue73c', }, [TreeNodeType.TRIGGERS]: { icon: '\ue64a', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - mysqlServer.getTriggerList(params).then(res => { - const list: ITreeNode[] = res.data?.map((t: any) => { - return { - name: t.triggerName, - treeNodeType: TreeNodeType.TRIGGER, - key: t.name, - pinned: t.pinned, - comment: t.comment, - isLeaf: true, - extraParams: { - ...params.extraParams, - triggerName: t.triggerName - } - } + mysqlServer + .getTriggerList(params) + .then((res) => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.triggerName, + treeNodeType: TreeNodeType.TRIGGER, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + triggerName: t.triggerName, + }, + }; + }); + r(list); }) - r(list); - }).catch(error => { - j(error) - }) - }) + .catch((error) => { + j(error); + }); + }); }, }, [TreeNodeType.TRIGGER]: { - icon: '\ue64a' + icon: '\ue64a', }, [TreeNodeType.VIEW]: { @@ -371,11 +390,11 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { name: 'columns', treeNodeType: TreeNodeType.COLUMNS, key: 'columns', - extraParams: params.extraParams + extraParams: params.extraParams, }, - ] + ]; r(list); - }) + }); }, }, @@ -383,25 +402,28 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ue647', getChildren: (params) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - mysqlServer.getViewColumnList(params).then(res => { - const list: ITreeNode[] = res.data?.map((t: any) => { - return { - name: t.name, - treeNodeType: TreeNodeType.VIEWCOLUMN, - key: t.name, - pinned: t.pinned, - comment: t.comment, - isLeaf: true, - extraParams: { - ...params.extraParams, - } - } + mysqlServer + .getViewColumnList(params) + .then((res) => { + const list: ITreeNode[] = res.data?.map((t: any) => { + return { + name: t.name, + treeNodeType: TreeNodeType.VIEWCOLUMN, + key: t.name, + pinned: t.pinned, + comment: t.comment, + isLeaf: true, + extraParams: { + ...params.extraParams, + }, + }; + }); + r(list); }) - r(list); - }).catch(error => { - j(error) - }) - }) + .catch((error) => { + j(error); + }); + }); }, }, [TreeNodeType.VIEWCOLUMN]: { @@ -412,76 +434,81 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { icon: '\ueac5', getChildren: (params: ITableParams) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - - mysqlServer.getColumnList(params).then(res => { - const tableList: ITreeNode[] = res?.map(item => { - return { - name: item.name, - treeNodeType: TreeNodeType.COLUMN, - key: item.name, - isLeaf: true, - columnType: item.columnType, - comment: item.comment, - } + mysqlServer + .getColumnList(params) + .then((res) => { + const tableList: ITreeNode[] = res?.map((item) => { + return { + name: item.name, + treeNodeType: TreeNodeType.COLUMN, + key: item.name, + isLeaf: true, + columnType: item.columnType, + comment: item.comment, + }; + }); + r(tableList); }) - r(tableList); - }).catch(error => { - j() - }) - }) + .catch((error) => { + j(); + }); + }); }, - operationColumn: [OperationColumn.Refresh] + operationColumn: [OperationColumn.Refresh], }, [TreeNodeType.COLUMN]: { - icon: '\ue611' + icon: '\ue611', }, [TreeNodeType.KEYS]: { icon: '\ueac5', getChildren: (params: ITableParams) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - - mysqlServer.getKeyList(params).then(res => { - const tableList: ITreeNode[] = res?.map(item => { - return { - name: item.name, - treeNodeType: TreeNodeType.KEY, - key: item.name, - isLeaf: true, - } + mysqlServer + .getKeyList(params) + .then((res) => { + const tableList: ITreeNode[] = res?.map((item) => { + return { + name: item.name, + treeNodeType: TreeNodeType.KEY, + key: item.name, + isLeaf: true, + }; + }); + r(tableList); }) - r(tableList); - }).catch(error => { - j() - }) - }) - } - + .catch((error) => { + j(); + }); + }); + }, }, [TreeNodeType.KEY]: { - icon: '\ue775' + icon: '\ue775', }, [TreeNodeType.INDEXES]: { icon: '\ueac5', getChildren: (params: ITableParams) => { return new Promise((r: (value: ITreeNode[]) => void, j) => { - - mysqlServer.getIndexList(params).then(res => { - const tableList: ITreeNode[] = res?.map(item => { - return { - name: item.name, - treeNodeType: TreeNodeType.INDEX, - key: item.name, - isLeaf: true, - } + mysqlServer + .getIndexList(params) + .then((res) => { + const tableList: ITreeNode[] = res?.map((item) => { + return { + name: item.name, + treeNodeType: TreeNodeType.INDEX, + key: item.name, + isLeaf: true, + }; + }); + r(tableList); }) - r(tableList); - }).catch(error => { - j() - }) - }) - } + .catch((error) => { + j(); + }); + }); + }, }, [TreeNodeType.INDEX]: { - icon: '\ue65b' - } -} \ No newline at end of file + icon: '\ue65b', + }, +}; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index 775710add..e2455edb9 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -9,6 +9,7 @@ import TabsNew, { ITabItem } from '@/components/TabsNew'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; import DatabaseTableEditor from '@/blocks/DatabaseTableEditor'; +import EditTableData from '@/blocks/EditTableData'; import SQLExecute from '@/blocks/SQLExecute'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IAIState } from '@/models/ai'; @@ -40,6 +41,7 @@ const WorkspaceRight = memo((props: IProps) => { id: t.id, title: t.name, type: t.operationType, + editableName: true, uniqueData: t, }; }); @@ -63,7 +65,10 @@ const WorkspaceRight = memo((props: IProps) => { useEffect(() => { if (createTabIntro) { // 如果已经打开了这个表的编辑页面,那么就切换到这个页面 - const flag = workspaceTabList?.find((t) => t.uniqueData?.tableName === createTabIntro.treeNodeData.name); + const flag = workspaceTabList?.find( + (t) => + t.uniqueData?.tableName === createTabIntro.treeNodeData.name && t.type === createTabIntro.workspaceTabType, + ); if (flag) { setActiveConsoleId(flag.id); return; @@ -73,7 +78,7 @@ const WorkspaceRight = memo((props: IProps) => { const newData = { id, type: createTabIntro.workspaceTabType, - title: `edit-${createTabIntro.treeNodeData.name}`, + title: `${createTabIntro.treeNodeData.name}`, uniqueData: { tableName: createTabIntro.treeNodeData.name, }, @@ -388,7 +393,13 @@ const WorkspaceRight = memo((props: IProps) => { if (action === 'remove') { setWorkspaceTabList(workspaceTabList.filter((t) => t.id !== data.key)); const editData = workspaceTabList?.find((t) => t.id === data.key); - if (editData?.type !== WorkspaceTabType.EditTable) { + if ( + editData?.type === WorkspaceTabType.CONSOLE || + editData?.type === WorkspaceTabType.FUNCTION || + editData?.type === WorkspaceTabType.PROCEDURE || + editData?.type === WorkspaceTabType.TRIGGER || + editData?.type === WorkspaceTabType.VIEW + ) { closeWindowTab(data.key as number); } } @@ -491,7 +502,7 @@ const WorkspaceRight = memo((props: IProps) => { prefixIcon: workspaceTabConfig[t.type]?.icon, label: t.title, key: t.id, - // 这里还缺一个参数 是否可编辑tab名称, 编辑表不可编辑名称 TODO: + editableName: t.editableName, children: ( <> {[ @@ -514,7 +525,7 @@ const WorkspaceRight = memo((props: IProps) => { }} /> )} - {t.type === WorkspaceTabType.EditTable && ( + {(t.type === WorkspaceTabType.EditTable || t.type === WorkspaceTabType.CreateTable) && ( ((props: IProps) => { tableName={uniqueData.tableName} /> )} + {t.type === WorkspaceTabType.EditTableData && ( + + )} ), }; @@ -539,7 +559,6 @@ const WorkspaceRight = memo((props: IProps) => { className={styles.tabs} onChange={onTabChange} onEdit={onEdit as any} - editableName={true} activeKey={activeConsoleId} editableNameOnBlur={editableNameOnBlur} items={tabsList} diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index 5ba1a0695..a58cc193e 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useRef, useEffect, useMemo, useState } from 'react'; +import React, { memo, useRef, useEffect } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; @@ -9,7 +9,6 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import LoadingContent from '@/components/Loading/LoadingContent'; import { ConsoleOpenedStatus } from '@/constants'; -import Iconfont from '@/components/Iconfont'; interface IProps { className?: string; @@ -31,7 +30,11 @@ const workspacePage = memo((props) => { const { workspaceModel, connectionModel, dispatch } = props; const { curConnection } = connectionModel; const { curWorkspaceParams } = workspaceModel; - const isReady = curWorkspaceParams?.dataSourceId && ((curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName) || (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null)) + const isReady = + curWorkspaceParams?.dataSourceId && + (curWorkspaceParams?.databaseName || + curWorkspaceParams?.schemaName || + (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null)); useEffect(() => { clearData(); @@ -44,22 +47,22 @@ const workspacePage = memo((props) => { }, [curWorkspaceParams]); function clearData() { - dispatch(({ + dispatch({ type: 'workspace/setOpenConsoleList', payload: [], - })) - dispatch(({ + }); + dispatch({ type: 'workspace/setConsoleList', payload: [], - })) - dispatch(({ + }); + dispatch({ type: 'workspace/setDatabaseAndSchema', payload: undefined, - })) - dispatch(({ + }); + dispatch({ type: 'workspace/setCurTableList', payload: [], - })) + }); } function getConsoleList() { @@ -84,7 +87,7 @@ const workspacePage = memo((props) => { return (
    - +
    @@ -94,9 +97,9 @@ const workspacePage = memo((props) => {
    -
    +
    ); }); -export default dvaModel(workspacePage) \ No newline at end of file +export default dvaModel(workspacePage); diff --git a/chat2db-client/src/typings/workspace.ts b/chat2db-client/src/typings/workspace.ts index fb06436df..9d3972432 100644 --- a/chat2db-client/src/typings/workspace.ts +++ b/chat2db-client/src/typings/workspace.ts @@ -12,6 +12,7 @@ export interface IWorkspaceTab { id: number | string; // Tab的id type: WorkspaceTabType; // 工作区tab的类型 title: string; // 工作区tab的名称 + editableName?: boolean; // 可以编辑名称 uniqueData?: any; } From d0dd050f2d0aba588e7e7c067007f10dc7b2f002 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 8 Oct 2023 16:27:08 +0800 Subject: [PATCH 0837/1069] add data update --- .../domain/api/param/SelectResultOperation.java | 15 +++++++++++++++ .../domain/api/param/UpdateSelectResultParam.java | 7 +------ .../domain/core/impl/DlTemplateServiceImpl.java | 6 +++--- .../rdb/request/SelectResultUpdateRequest.java | 10 ++++------ 4 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SelectResultOperation.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SelectResultOperation.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SelectResultOperation.java new file mode 100644 index 000000000..500bb4558 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/SelectResultOperation.java @@ -0,0 +1,15 @@ +package ai.chat2db.server.domain.api.param; + +import lombok.Data; + +import java.util.List; + +@Data +public class SelectResultOperation { + + private String type; + + private List dataList; + + private List oldDataList; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java index 68725bc6b..6d37d6f96 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/UpdateSelectResultParam.java @@ -44,13 +44,8 @@ public class UpdateSelectResultParam { * 修改后数据的列表 */ @NotEmpty - private List> dataList; + private List operations; - /** - * 数据的列表 - */ - @NotEmpty - private List> oldDataList; /** * 表名 diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 2e94e3528..dc7821615 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -198,12 +198,12 @@ public DataResult count(DlCountParam param) { public DataResult updateSelectResult(UpdateSelectResultParam param) { StringBuilder stringBuilder = new StringBuilder(); MetaData metaSchema = Chat2DBContext.getMetaData(); - for (int i = 0; i < param.getDataList().size(); i++) { - List row = param.getDataList().get(i); + for (int i = 0; i < param.getOperations().size(); i++) { + List row = param.getOperations().get(i).getDataList(); if (CollectionUtils.isEmpty(row)) { continue; } - List odlRow = param.getOldDataList().get(i); + List odlRow = param.getOperations().get(i).getOldDataList(); String sql = getUpdateSql(param, row, odlRow, metaSchema); stringBuilder.append(sql+";\n"); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java index 164a0ce4c..0fb02315b 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SelectResultUpdateRequest.java @@ -1,8 +1,10 @@ package ai.chat2db.server.web.api.controller.rdb.request; +import ai.chat2db.server.domain.api.param.SelectResultOperation; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceConsoleRequestInfo; import ai.chat2db.server.web.api.controller.rdb.vo.HeaderVO; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -19,12 +21,8 @@ public class SelectResultUpdateRequest extends DataSourceBaseRequest implements /** * 修改后数据的列表 */ - private List> dataList; - - /** - * 数据的列表 - */ - private List> oldDataList; + @NotEmpty + private List operations; /** * 表名 From 358ed15bde2185576f81caa51a7a5c9821079f04 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 8 Oct 2023 17:04:42 +0800 Subject: [PATCH 0838/1069] edit data --- .../src/components/Iconfont/index.tsx | 6 +- .../components/RefreshLoadingButton/index.tsx | 16 +- .../SearchResult/TableBox/index.less | 76 ++++- .../SearchResult/TableBox/index.tsx | 291 ++++++++++++++---- .../src/components/TabsNew/index.tsx | 4 +- chat2db-client/src/constants/common.ts | 8 + chat2db-client/src/service/sql.ts | 6 +- 7 files changed, 317 insertions(+), 90 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index f1016c699..bdad320b7 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_dom9o9rlupe.woff2?t=1696660819240') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_dom9o9rlupe.woff?t=1696660819240') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_dom9o9rlupe.ttf?t=1696660819240') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_lzls56t3018.woff2?t=1696748012976') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_lzls56t3018.woff?t=1696748012976') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_lzls56t3018.ttf?t=1696748012976') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/RefreshLoadingButton/index.tsx b/chat2db-client/src/components/RefreshLoadingButton/index.tsx index 11f4f4e52..73f92b94e 100644 --- a/chat2db-client/src/components/RefreshLoadingButton/index.tsx +++ b/chat2db-client/src/components/RefreshLoadingButton/index.tsx @@ -9,10 +9,12 @@ interface IProps extends React.DetailedHTMLProps(function RefreshLoadingButton(props) { - const { className, loading, ...res } = props - return
    - {loading && } - {!loading && } -
    -}) +export default memo((props) => { + const { className, loading, ...res } = props; + return ( +
    + {loading && } + {!loading && } +
    + ); +}); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 2be1f27a8..185500f90 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -46,7 +46,7 @@ align-items: center; border-bottom: 1px solid var(--color-border-secondary); background-color: var(--color-bg-subtle); - padding: 0 16px; + padding: 0px 4px 0px 0px; height: 30px; } .toolBarItem { @@ -60,6 +60,50 @@ } } +.toolBarRight { + flex: 1; + display: flex; + justify-content: end; + .exportBar { + cursor: pointer; + } +} + +.editTableDataBar { + padding: 0px 4px; + i { + color: var(--color-text-disabled); + } + .editTableDataBarItem { + width: 24px; + box-sizing: border-box; + padding: 4px 6px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; + &:hover i { + color: var(--color-primary); + } + } + .disableBar { + cursor: not-allowed; + &:hover i { + color: var(--color-text-disabled); + } + } + .deleteDataBar { + i { + font-size: 19px; + } + } + .viewSqlBar { + i { + font-size: 15px; + } + } +} + .table { flex: 1; } @@ -84,12 +128,12 @@ .tableItem { width: 100%; height: 100%; - // cursor: pointer; - // display: flex; - // align-items: center; - // justify-content: space-between; + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + position: relative; - // height: 100%; max-height: 120px; overflow-y: auto; @@ -98,23 +142,27 @@ // } &:hover .tableHoverBox { - // display: flex !important; display: block; } + + input { + background: none; + } +} + +.tableItemNo { + width: 100%; + text-align: center; } .tableHoverBox { position: absolute; - top: 0px; + top: 50%; right: 0px; width: 40px; display: none; align-items: center; - // position: absolute; - background-color: var(--color-bg-subtle); - // top: 0; - // right: 0; - // bottom: 0; + transform: translateY(-50%); cursor: pointer; i { @@ -123,7 +171,7 @@ } i:hover { - color: var(--custom-primary-color); + color: var(--color-primary); } } diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 5be0c62b3..7e2b42f28 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -1,22 +1,24 @@ -import React, { useMemo, useState } from 'react'; -import { Button, Dropdown, MenuProps, message, Modal, Space } from 'antd'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Button, Dropdown, Input, MenuProps, message, Modal, Space } from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; import styled from 'styled-components'; import classnames from 'classnames'; import i18n from '@/i18n'; +import { CRUD } from '@/constants'; import { TableDataType } from '@/constants/table'; import { IManageResultData, IResultConfig } from '@/typings/database'; +import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import { compareStrings } from '@/utils/sort'; import { DownOutlined } from '@ant-design/icons'; -import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import { copy } from '@/utils'; import Iconfont from '../../Iconfont'; import StateIndicator from '../../StateIndicator'; import MonacoEditor from '../../Console/MonacoEditor'; import MyPagination from '../Pagination'; import styles from './index.less'; -import { IExportParams } from '@/service/sql'; +import sqlService, { IExportParams } from '@/service/sql'; import { downloadFile } from '@/utils/common'; +import lodash from 'lodash'; interface ITableProps { className?: string; @@ -32,6 +34,13 @@ interface IViewTableCellData { value: any; } +interface IUpdateData { + oldDataList?: string[]; + dataList?: string[]; + type: CRUD; + index: number; +} + // const defaultResultConfig: IResultConfig = { // pageNo: 1, // pageSize: 200, @@ -51,6 +60,8 @@ const SupportBaseTable: any = styled(BaseTable)` --header-color: var(--color-text); --lock-shadow: rgb(37 37 37 / 0.5) 0 0 6px 2px; --border-color: var(--color-border-secondary); + --cell-padding: 0px 4px; + --row-height: 32px; } `; @@ -61,7 +72,11 @@ export default function TableBox(props: ITableProps) { const { headerList, dataList, duration, description } = data || {}; const [viewTableCellData, setViewTableCellData] = useState(null); const [messageApi, contextHolder] = message.useMessage(); - // const [pageConfig, setPageConfig] = useState(defaultResultConfig); + const [tableData, setTableData] = useState([]); + const [editingCell, setEditingCell] = useState<[number, number] | null>(null); + const [editingData, setEditingData] = useState(''); + const [curOperationRowIndex, setCurOperationRowIndex] = useState(-1); + const [updateData, setUpdateData] = useState([]); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -121,12 +136,12 @@ export default function TableBox(props: ITableProps) { [headerList], ); - function viewTableCell(data: IViewTableCellData) { - setViewTableCellData(data); + function viewTableCell(cellData: IViewTableCellData) { + setViewTableCellData(cellData); } - function copyTableCell(data: IViewTableCellData) { - copy(data?.value || viewTableCellData?.value); + function copyTableCell(cellData: IViewTableCellData) { + copy(cellData?.value || viewTableCellData?.value); messageApi.success(i18n('common.button.copySuccessfully')); } @@ -134,70 +149,122 @@ export default function TableBox(props: ITableProps) { setViewTableCellData(null); } - const columns: ArtColumn[] = useMemo( - () => - (headerList || []).map((item, colIndex) => { - const { dataType, name } = item; - const isNumber = dataType === TableDataType.NUMERIC; - const isNumericalOrder = dataType === TableDataType.CHAT2DB_ROW_NUMBER; - if (isNumericalOrder) { - return { - code: `${preCode}${colIndex}No.`, - name: 'No.', - key: name, - lock: true, - width: 48, - features: { sortable: compareStrings }, - render: (value: any) => { - return ( -
    -
    {value}
    -
    - ); - }, - }; - } + const handleDoubleClickTableItem = (colIndex, rowIndex, value) => { + setEditingData(value); + setEditingCell([colIndex, rowIndex]); + }; + + // 编辑数据失焦 + const editDataOnBlur = () => { + setEditingCell(null); + setEditingData(''); + const [colIndex, rowIndex] = editingCell!; + const newTableData = lodash.cloneDeep(tableData); + newTableData[rowIndex][`${preCode}${colIndex}${columns[colIndex].name}`] = editingData; + setTableData(newTableData); + // 如果已经存在该行的更新数据,则更新,否则新增 + const index = updateData.findIndex((item) => item.index === rowIndex); + if (index === -1) { + setUpdateData([ + ...updateData, + { + type: CRUD.UPDATE, + oldDataList: dataList[rowIndex], + dataList: Object.keys(newTableData[rowIndex]).map((item) => newTableData[rowIndex][item]), + index: rowIndex, + }, + ]); + } else { + updateData[index] = { + ...updateData[index], + dataList: Object.keys(newTableData[rowIndex]).map((item) => newTableData[rowIndex][item]), + }; + setUpdateData([...updateData]); + } + }; + + const renderTableCellValue = (value) => { + if (value === null) { + return ''; + } else { + return value; + } + }; + + const columns: ArtColumn[] = useMemo(() => { + return (headerList || []).map((item, colIndex) => { + const { dataType, name } = item; + const isNumber = dataType === TableDataType.NUMERIC; + const isNumericalOrder = dataType === TableDataType.CHAT2DB_ROW_NUMBER; + if (isNumericalOrder) { return { - code: `${preCode}${colIndex}${name}`, - name: name, + code: `${preCode}${colIndex}No.`, + name: 'No.', key: name, - width: 120, + lock: true, + width: 48, + features: { sortable: compareStrings }, render: (value: any) => { return (
    - {value} -
    - - -
    +
    {value}
    ); }, - // 如果是数字类型,因为后端返回的都是字符串,所以需要调用字符串对比函数来判断 - features: { sortable: isNumber ? compareStrings : true }, }; - }), - [headerList], - ); + } + return { + code: `${preCode}${colIndex}${name}`, + name: name, + key: name, + width: 120, + render: (value: any, a, rowIndex) => { + return ( +
    + {editingCell?.join(',') === `${colIndex},${rowIndex}` ? ( + { + setEditingData(e.target.value); + }} + onBlur={editDataOnBlur} + /> + ) : ( + <> + {renderTableCellValue(value)} +
    + + +
    + + )} +
    + ); + }, + // 如果是数字类型,因为后端返回的都是字符串,所以需要调用字符串对比函数来判断 + features: { sortable: isNumber ? compareStrings : true }, + }; + }); + }, [headerList, editingCell, editingData]); - const tableData = useMemo(() => { + useEffect(() => { if (!columns?.length) { - return []; + setTableData([]); } else { - return (dataList || []).map((item) => { + const newData = (dataList || []).map((item) => { const rowData: any = {}; item.map((i: string | null, colIndex: number) => { const name = `${preCode}${colIndex}${columns[colIndex].name}`; - if (i === null) { - rowData[name] = ''; - } else { - rowData[name] = i; - } + rowData[name] = i; }); return rowData; }); + setTableData(newData); } - }, [dataList, columns]); + }, [dataList]); const pipeline = useTablePipeline() .input({ dataSource: tableData, columns }) @@ -215,9 +282,6 @@ export default function TableBox(props: ITableProps) { fallbackSize: 120, minSize: 60, maxSize: 1080, - // handleBackground: '#ddd', - // handleHoverBackground: '#aaa', - // handleActiveBackground: '#89bff7', }), ); @@ -235,6 +299,61 @@ export default function TableBox(props: ITableProps) { } }; + const handelCreateData = () => { + const newTableData = lodash.cloneDeep(tableData); + const newData = {}; + columns.forEach((item, index) => { + if (item.name === 'No.') { + newData[`${preCode}${index}${item.name}`] = newTableData.length + 1; + } else { + newData[`${preCode}${index}${item.name}`] = null; + } + }); + newTableData.push(newData); + setTableData(newTableData); + setUpdateData([ + ...updateData, + { + type: CRUD.CREATE, + dataList: Object.keys(newData).map((item) => newData[item]), + index: newTableData.length, + }, + ]); + }; + + useEffect(() => { + console.log('updateData', updateData); + }, [updateData]); + + const handelDeleteData = () => { + const newTableData = lodash.cloneDeep(tableData); + newTableData.splice(curOperationRowIndex, 1); + setTableData(newTableData); + setCurOperationRowIndex(-1); + }; + + // 查看更新数据的sql + const handelViewSql = () => { + if (!updateData.length) { + return; + } + console.log('handelViewSql'); + }; + + // 更新数据的sql + const handelUpdateSubmit = () => { + if (!updateData.length) { + return; + } + const params = { + ...props.executeSqlParams, + operations: updateData, + }; + sqlService.getExecuteUpdateSql(params).then((res) => { + console.log('res', res); + }); + }; + const renderContent = () => { const bottomStatus = (
    @@ -263,21 +382,67 @@ export default function TableBox(props: ITableProps) { onClickTotalBtn={onClickTotalBtn} />
    - - - - + +

    {i18n('common.text.noData')}

    }} isStickyHead stickyTop={31} + getRowProps={(record, rowIndex) => { + return { + style: + rowIndex === curOperationRowIndex + ? { + '--hover-bgcolor': 'transparent', + '--bgcolor': 'transparent', + background: 'linear-gradient(140deg, #ff000038, #009cff3d)', + } + : { + // 覆盖 website 中自带的 style,实际使用时可以忽略 + backgroundColor: 'transparent', + }, + onClick() { + setCurOperationRowIndex(rowIndex); + }, + }; + }} {...pipeline.getProps()} /> {bottomStatus} diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 23d96a890..a79631019 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -23,7 +23,7 @@ interface IProps { items: ITabItem[] | undefined; activeKey?: number | string; onChange?: (key: string | number | undefined) => void; - onEdit?: (action: 'add' | 'remove', data?: ITabItem) => void; + onEdit?: (action: 'add' | 'remove', data?: ITabItem, list?: ITabItem[]) => void; hideAdd?: boolean; type?: 'line'; editableNameOnBlur?: (option: ITabItem) => void; @@ -66,7 +66,7 @@ export default memo((props) => { } changeTab(activeKeyTemp); setInternalTabs(newInternalTabs); - onEdit?.('remove', data); + onEdit?.('remove', data, newInternalTabs); } function changeTab(key: string | number | undefined) { diff --git a/chat2db-client/src/constants/common.ts b/chat2db-client/src/constants/common.ts index 808efea9e..1b9815982 100644 --- a/chat2db-client/src/constants/common.ts +++ b/chat2db-client/src/constants/common.ts @@ -37,3 +37,11 @@ export enum ConnectionKind { Private = 'PRIVATE', Shared = 'SHARED' } + +// 通用的增删改查枚举 +export enum CRUD { + CREATE = 'CREATE', + READ = 'READ', + UPDATE = 'UPDATE', + DELETE = 'DELETE', +} diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 5f002bbcf..460da87c2 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -208,9 +208,13 @@ export interface IModifyTableSqlParams { const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); /** 执行编辑表的sql, 专为编辑表而生 */ -const executeDDL = createRequest('/api/rdb/dml/execute_ddl', {method:'post'}); +const executeDDL = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); + +/** 获取修改表数据的接口 */ +const getExecuteUpdateSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); export default { + getExecuteUpdateSql, executeDDL, getModifyTableSql, getTableDetails, From 5d521cc22e72ea3f648370cf4346836f249e465f Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 8 Oct 2023 17:57:56 +0800 Subject: [PATCH 0839/1069] add data update --- .../core/impl/DlTemplateServiceImpl.java | 63 ++++++++++++++++--- .../api/controller/rdb/RdbDmlController.java | 2 +- .../controller/rdb/vo/ExecuteResultVO.java | 10 +++ .../ai/chat2db/spi/model/ExecuteResult.java | 5 ++ 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index dc7821615..4fffe5c99 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.Optional; -import ai.chat2db.server.domain.api.param.UpdateSelectResultParam; +import ai.chat2db.server.domain.api.param.*; import ai.chat2db.spi.MetaData; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.PagerUtils; @@ -15,9 +15,6 @@ import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.parser.ParserException; -import ai.chat2db.server.domain.api.param.DlCountParam; -import ai.chat2db.server.domain.api.param.DlExecuteParam; -import ai.chat2db.server.domain.api.param.SqlAnalyseParam; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.tools.base.constant.EasyToolsConstant; import ai.chat2db.server.tools.base.excption.BusinessException; @@ -116,6 +113,7 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar executeResult.setSqlType(sqlType); executeResult.setOriginalSql(originalSql); executeResult.setCanEdit(SqlUtils.canEdit(originalSql)); + executeResult.setTableName(SqlUtils.getTableName(originalSql,dbType)); if (SqlTypeEnum.SELECT.getCode().equals(sqlType)) { executeResult.setPageNo(pageNo); @@ -199,18 +197,65 @@ public DataResult updateSelectResult(UpdateSelectResultParam param) { StringBuilder stringBuilder = new StringBuilder(); MetaData metaSchema = Chat2DBContext.getMetaData(); for (int i = 0; i < param.getOperations().size(); i++) { - List row = param.getOperations().get(i).getDataList(); - if (CollectionUtils.isEmpty(row)) { - continue; + SelectResultOperation operation = param.getOperations().get(i); + + List row = operation.getDataList(); + List odlRow = operation.getOldDataList(); + String sql = ""; + if("UPDATE".equalsIgnoreCase(operation.getType())){ + sql = getUpdateSql(param, row, odlRow, metaSchema); + }else if("CREATE".equalsIgnoreCase(operation.getType())){ + sql = getInsertSql(param, row,metaSchema); + + }else if("DELETE".equalsIgnoreCase(operation.getType())){ + sql= getDeleteSql(param, odlRow,metaSchema); } - List odlRow = param.getOperations().get(i).getOldDataList(); - String sql = getUpdateSql(param, row, odlRow, metaSchema); stringBuilder.append(sql+";\n"); } return DataResult.of(stringBuilder.toString()); } + private String getDeleteSql(UpdateSelectResultParam param, List row, MetaData metaSchema) { + StringBuilder script = new StringBuilder(); + script.append("DELETE FROM ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) + .append(" where "); + for (int i = 0; i < row.size(); i++) { + String newValue = row.get(i); + Header header = param.getHeaderList().get(i); + script.append(metaSchema.getMetaDataName(header.getName())) + .append(" = ") + .append(SqlUtils.getSqlValue(newValue, header.getDataType())) + .append(" and "); + } + script.delete(script.length() - 4, script.length()); + return script.toString(); + } + + private String getInsertSql(UpdateSelectResultParam param, List row, MetaData metaSchema) { + StringBuilder script = new StringBuilder(); + script.append("INSERT INTO ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) + .append(" ("); + for (int i = 0; i < row.size(); i++) { + Header header = param.getHeaderList().get(i); + script.append(metaSchema.getMetaDataName(header.getName())) + .append(","); + } + script.deleteCharAt(script.length() - 1); + script.append(") VALUES ("); + for (int i = 0; i < row.size(); i++) { + String newValue = row.get(i); + Header header = param.getHeaderList().get(i); + script.append(SqlUtils.getSqlValue(newValue, header.getDataType())) + .append(","); + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + + } + + private String getUpdateSql(UpdateSelectResultParam param, List row, List odlRow, MetaData metaSchema) { StringBuilder script = new StringBuilder(); script.append("UPDATE ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 2c7a0cc87..111d56cd4 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -63,7 +63,7 @@ public ListResult manage(@RequestBody DmlRequest request) { * @return */ @RequestMapping(value = "/execute_update", method = {RequestMethod.POST, RequestMethod.PUT}) - public DataResult executeUpdate(@RequestBody SelectResultUpdateRequest request) { + public DataResult executeUpdate(@RequestBody SelectResultUpdateRequest request) { UpdateSelectResultParam param = rdbWebConverter.request2param(request); return dlTemplateService.updateSelectResult(param); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java index 6ca3ad254..1ea6aadbd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/vo/ExecuteResultVO.java @@ -87,4 +87,14 @@ public class ExecuteResultVO { * 执行持续时间 */ private Long duration; + + /** + * 返回结果是否可以编辑 + */ + private boolean canEdit; + + /** + * 表名 + */ + private String tableName; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java index 552bcb8a8..1f28b2e2a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/ExecuteResult.java @@ -100,4 +100,9 @@ public class ExecuteResult { * 返回结果是否可以编辑 */ private boolean canEdit; + + /** + * 表名 + */ + private String tableName; } From f00181be1b9d414b3d7135c9a9ddbecf165774fe Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 8 Oct 2023 22:53:07 +0800 Subject: [PATCH 0840/1069] feat:delete create table data --- .../SearchResult/TableBox/index.tsx | 82 +++++++++++++++---- chat2db-client/src/typings/database.ts | 2 + 2 files changed, 69 insertions(+), 15 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 7e2b42f28..533a615c6 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -150,6 +150,9 @@ export default function TableBox(props: ITableProps) { } const handleDoubleClickTableItem = (colIndex, rowIndex, value) => { + if (!data.canEdit) { + return; + } setEditingData(value); setEditingCell([colIndex, rowIndex]); }; @@ -300,6 +303,14 @@ export default function TableBox(props: ITableProps) { }; const handelCreateData = () => { + // 如果加的这行数据是删除过的,则恢复 + const index = updateData.findIndex((item) => item.index === curOperationRowIndex && item.type === CRUD.DELETE); + if (index !== -1) { + updateData.splice(index, 1); + setUpdateData([...updateData]); + return; + } + // 正常的新增 const newTableData = lodash.cloneDeep(tableData); const newData = {}; columns.forEach((item, index) => { @@ -316,7 +327,7 @@ export default function TableBox(props: ITableProps) { { type: CRUD.CREATE, dataList: Object.keys(newData).map((item) => newData[item]), - index: newTableData.length, + index: newTableData.length - 1, }, ]); }; @@ -326,9 +337,28 @@ export default function TableBox(props: ITableProps) { }, [updateData]); const handelDeleteData = () => { - const newTableData = lodash.cloneDeep(tableData); - newTableData.splice(curOperationRowIndex, 1); - setTableData(newTableData); + if (curOperationRowIndex === -1) { + return; + } + // 如果是新增的行,则直接删除 + const index = updateData.findIndex((item) => item.index === curOperationRowIndex && item.type === CRUD.CREATE); + if (index !== -1) { + updateData.splice(index, 1); + setUpdateData([...updateData]); + setTableData(tableData.filter((item, index) => index !== curOperationRowIndex)); + return; + } + const index2 = updateData.findIndex((item) => item.index === curOperationRowIndex); + if (index2 === -1) { + setUpdateData([ + ...updateData, + { + type: CRUD.DELETE, + oldDataList: dataList[curOperationRowIndex], + index: curOperationRowIndex, + }, + ]); + } setCurOperationRowIndex(-1); }; @@ -347,6 +377,7 @@ export default function TableBox(props: ITableProps) { } const params = { ...props.executeSqlParams, + tableName: data.tableName || 't_user1', // TODO: operations: updateData, }; sqlService.getExecuteUpdateSql(params).then((res) => { @@ -354,6 +385,35 @@ export default function TableBox(props: ITableProps) { }); }; + const tableRowStyle = (rowIndex: number) => { + // 如果是删除过的行 + const index = updateData.findIndex((item) => item.index === rowIndex && item.type === CRUD.DELETE); + if (index !== -1) { + return { + '--hover-bgcolor': 'transparent', + '--bgcolor': 'transparent', + background: 'red', + }; + } + // 如果是新增的行 + const index2 = updateData.findIndex((item) => item.index === rowIndex && item.type === CRUD.CREATE); + if (index2 !== -1) { + return { + '--hover-bgcolor': 'transparent', + '--bgcolor': 'transparent', + background: 'green', + }; + } + if (rowIndex === curOperationRowIndex) { + return { + '--hover-bgcolor': 'transparent', + '--bgcolor': 'transparent', + background: 'linear-gradient(140deg, #ff000038, #009cff3d)', + }; + } + return {}; + }; + const renderContent = () => { const bottomStatus = (
    @@ -382,6 +442,8 @@ export default function TableBox(props: ITableProps) { onClickTotalBtn={onClickTotalBtn} />
    + {/* {data.canEdit && ( + )} */}
    @@ -427,17 +489,7 @@ export default function TableBox(props: ITableProps) { stickyTop={31} getRowProps={(record, rowIndex) => { return { - style: - rowIndex === curOperationRowIndex - ? { - '--hover-bgcolor': 'transparent', - '--bgcolor': 'transparent', - background: 'linear-gradient(140deg, #ff000038, #009cff3d)', - } - : { - // 覆盖 website 中自带的 style,实际使用时可以忽略 - backgroundColor: 'transparent', - }, + style: tableRowStyle(rowIndex), onClick() { setCurOperationRowIndex(rowIndex); }, diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index bf56efd48..c33ed71a1 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -25,6 +25,8 @@ export interface IManageResultData { fuzzyTotal: string; hasNextPage: boolean; sqlType: 'SELECT' | 'UNKNOWN'; + canEdit?: boolean; // 返回的数据是否可以编辑 + tableName?: string; // 如果可以编辑的话。后端会返回表名称。修改需要给后端传递表名 } /** 查询结果 配置属性 */ From c5f96bda14a462b26c5b85b5bd05da20a2c6770d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 08:31:45 +0800 Subject: [PATCH 0841/1069] style:delete data color --- .../SearchResult/TableBox/index.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 533a615c6..c305c805a 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -386,13 +386,21 @@ export default function TableBox(props: ITableProps) { }; const tableRowStyle = (rowIndex: number) => { + // 如果是当前操作的行 + if (rowIndex === curOperationRowIndex) { + return { + '--hover-bgcolor': 'transparent', + '--bgcolor': 'transparent', + background: 'linear-gradient(140deg, #ff000038, #009cff3d)', + }; + } // 如果是删除过的行 const index = updateData.findIndex((item) => item.index === rowIndex && item.type === CRUD.DELETE); if (index !== -1) { return { '--hover-bgcolor': 'transparent', '--bgcolor': 'transparent', - background: 'red', + background: 'var(--color-error-bg)', }; } // 如果是新增的行 @@ -401,14 +409,7 @@ export default function TableBox(props: ITableProps) { return { '--hover-bgcolor': 'transparent', '--bgcolor': 'transparent', - background: 'green', - }; - } - if (rowIndex === curOperationRowIndex) { - return { - '--hover-bgcolor': 'transparent', - '--bgcolor': 'transparent', - background: 'linear-gradient(140deg, #ff000038, #009cff3d)', + background: 'var(--color-success-bg)', }; } return {}; From b4d8df03f07a575cb5aa9b2d2992cd06b0b6809c Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 9 Oct 2023 08:59:26 +0800 Subject: [PATCH 0842/1069] add data update --- .../server/domain/core/impl/DlTemplateServiceImpl.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 4fffe5c99..e454ba132 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -220,7 +220,7 @@ private String getDeleteSql(UpdateSelectResultParam param, List row, Met StringBuilder script = new StringBuilder(); script.append("DELETE FROM ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) .append(" where "); - for (int i = 0; i < row.size(); i++) { + for (int i = 1; i < row.size(); i++) { String newValue = row.get(i); Header header = param.getHeaderList().get(i); script.append(metaSchema.getMetaDataName(header.getName())) @@ -236,14 +236,14 @@ private String getInsertSql(UpdateSelectResultParam param, List row, Met StringBuilder script = new StringBuilder(); script.append("INSERT INTO ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) .append(" ("); - for (int i = 0; i < row.size(); i++) { + for (int i = 1; i < row.size(); i++) { Header header = param.getHeaderList().get(i); script.append(metaSchema.getMetaDataName(header.getName())) .append(","); } script.deleteCharAt(script.length() - 1); script.append(") VALUES ("); - for (int i = 0; i < row.size(); i++) { + for (int i = 1; i < row.size(); i++) { String newValue = row.get(i); Header header = param.getHeaderList().get(i); script.append(SqlUtils.getSqlValue(newValue, header.getDataType())) @@ -260,7 +260,7 @@ private String getUpdateSql(UpdateSelectResultParam param, List row, Lis StringBuilder script = new StringBuilder(); script.append("UPDATE ").append(metaSchema.getMetaDataName(param.getDatabaseName(), param.getSchemaName(), param.getTableName())) .append(" set "); - for (int i = 0; i < row.size(); i++) { + for (int i = 1; i < row.size(); i++) { String newValue = row.get(i); String oldValue = odlRow.get(i); if (StringUtils.equals(newValue, oldValue)) { @@ -275,7 +275,7 @@ private String getUpdateSql(UpdateSelectResultParam param, List row, Lis } script.deleteCharAt(script.length() - 1); script.append(" where "); - for (int i = 0; i < odlRow.size(); i++) { + for (int i = 1; i < odlRow.size(); i++) { String oldValue = odlRow.get(i); if (oldValue == null) { continue; From d0f1ab2c8995b628307ff0225133146643f49f98 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 9 Oct 2023 09:58:08 +0800 Subject: [PATCH 0843/1069] add data update --- .../server/domain/core/impl/DlTemplateServiceImpl.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index e454ba132..397edb047 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -112,8 +112,13 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar ExecuteResult executeResult = execute(originalSql, offset, count); executeResult.setSqlType(sqlType); executeResult.setOriginalSql(originalSql); - executeResult.setCanEdit(SqlUtils.canEdit(originalSql)); - executeResult.setTableName(SqlUtils.getTableName(originalSql,dbType)); + try { + executeResult.setCanEdit(SqlUtils.canEdit(originalSql)); + executeResult.setTableName(SqlUtils.getTableName(originalSql,dbType)); + }catch (Exception e){ + + } + if (SqlTypeEnum.SELECT.getCode().equals(sqlType)) { executeResult.setPageNo(pageNo); From 20a1d651637de8a32da76c7d16ab23a5b6193c39 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 10:42:57 +0800 Subject: [PATCH 0844/1069] edit data --- .../src/blocks/DatabaseTableEditor/index.tsx | 119 ++++---------- .../src/components/ExecuteSQL/index.less | 92 +++++++++++ .../src/components/ExecuteSQL/index.tsx | 114 ++++++++++++++ .../SearchResult/TableBox/index.tsx | 148 +++++++++++++----- chat2db-client/src/service/sql.ts | 1 + 5 files changed, 344 insertions(+), 130 deletions(-) create mode 100644 chat2db-client/src/components/ExecuteSQL/index.less create mode 100644 chat2db-client/src/components/ExecuteSQL/index.tsx diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 6e4cb55f7..c4392cbce 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -5,15 +5,12 @@ import classnames from 'classnames'; import IndexList, { IIndexListRef } from './IndexList'; import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; -import sqlService, { IModifyTableSqlParams, IExecuteSqlParams } from '@/service/sql'; -import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; +import sqlService, { IModifyTableSqlParams } from '@/service/sql'; +import ExecuteSQL from '@/components/ExecuteSQL'; import { IEditTableInfo, IWorkspaceTab } from '@/typings'; import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; -import Iconfont from '@/components/Iconfont'; -import { formatSql } from '@/utils'; - interface IProps { dataSourceId: number; databaseName: string; @@ -48,9 +45,6 @@ export default memo((props: IProps) => { const baseInfoRef = useRef(null); const columnListRef = useRef(null); const indexListRef = useRef(null); - const monacoEditorRef = useRef(null); - const [executeSqlResult, setExecuteSqlResult] = useState(null); - const [executeLoading, setExecuteLoading] = useState(false); const [appendValue, setAppendValue] = useState(''); const tabList = useMemo(() => { return [ @@ -80,12 +74,6 @@ export default memo((props: IProps) => { setCurrentTab(item); } - useEffect(() => { - if (!viewSqlModal) { - setExecuteSqlResult(null); - } - }, [viewSqlModal]); - useEffect(() => { if (tableName) { getTableDetails({}); @@ -136,62 +124,22 @@ export default memo((props: IProps) => { } } - const executeSql = () => { - const executeSQLParams: IExecuteSqlParams = { - sql: monacoEditorRef.current?.getAllContent() || '', - dataSourceId, - databaseName, - schemaName, - }; - setExecuteLoading(true); - sqlService - .executeDDL(executeSQLParams) - .then((res) => { - if (res.success) { - setViewSqlModal(false); - message.success(i18n('common.text.successfulExecution')); - const newTableName = baseInfoRef.current?.getBaseInfo().name; - getTableDetails({ tableNameProps: newTableName }); - if (!tableName) { - changeTabDetails({ - ...tabDetails, - title: `${newTableName}`, - type: WorkspaceTabType.EditTable, - uniqueData: { - ...(tabDetails.uniqueData || {}), - tableName: newTableName, - }, - }); - } - } else { - setExecuteSqlResult(res.message); - } - }) - .finally(() => { - setExecuteLoading(false); + const executeSuccessCallBack = () => { + setViewSqlModal(false); + message.success(i18n('common.text.successfulExecution')); + const newTableName = baseInfoRef.current?.getBaseInfo().name; + getTableDetails({ tableNameProps: newTableName }); + if (!tableName) { + changeTabDetails({ + ...tabDetails, + title: `${newTableName}`, + type: WorkspaceTabType.EditTable, + uniqueData: { + ...(tabDetails.uniqueData || {}), + tableName: newTableName, + }, }); - }; - - // - const renderMonacoEditor = useMemo(() => { - return ( - - ); - }, [appendValue]); - - const handleFormatSql = () => { - const sql = monacoEditorRef.current?.getAllContent() || ''; - formatSql(sql, databaseType).then((res) => { - setAppendValue(res); - }); + } }; return ( @@ -244,30 +192,17 @@ export default memo((props: IProps) => { width="60vw" maskClosable={false} footer={false} + destroyOnClose={true} > -
    -
    -
    -
    - - {i18n('common.button.format')} -
    - -
    - {renderMonacoEditor} -
    - {executeSqlResult && ( -
    -
    {i18n('common.text.errorMessage')}
    -
    -
    {executeSqlResult}
    -
    -
    - )} -
    + ); diff --git a/chat2db-client/src/components/ExecuteSQL/index.less b/chat2db-client/src/components/ExecuteSQL/index.less new file mode 100644 index 000000000..dbb6c1703 --- /dev/null +++ b/chat2db-client/src/components/ExecuteSQL/index.less @@ -0,0 +1,92 @@ +@import '../../styles/var.less'; + +.executeSQL { + .monacoEditorModal { + display: flex; + height: 400px; + border: 1px solid var(--color-border); + border-radius: 4px; + + .monacoEditorHeader { + height: 38px; + padding: 0px 10px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid var(--color-border); + .formatButton { + border-radius: 3px; + background-color: var(--color-fill-quaternary); + height: 24px; + padding: 0px 7px; + cursor: pointer; + i { + margin-right: 6px; + } + &:hover { + color: var(--color-primary); + } + } + .executeButton { + height: 24px; + line-height: 24px; + display: flex; + justify-content: center; + align-items: center; + i { + margin-right: 4px; + } + } + } + .monacoEditorContent { + width: 0px; + flex: 1; + display: flex; + flex-direction: column; + padding: 0px -6px; + border-right: 1px solid var(--color-border); + .monacoEditor { + flex: 1; + margin: 0px 6px; + } + } + .result { + width: 0px; + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + .resultHeader { + width: 100%; + line-height: 38px; + text-align: center; + border-bottom: 1px solid var(--color-border); + } + .resultContent { + flex: 1; + padding: 0px 10px; + overflow-y: auto; + .errorTitle { + display: flex; + align-items: center; + padding: 4px 10px; + // background-color: var(--color-bg-subtle); + border-radius: 8px; + margin-top: 10px; + i { + color: var(--color-error); + margin-right: 4px; + } + } + + .errorMessage { + margin: 10px 0px; + padding: 4px 10px; + background-color: var(--color-bg-subtle); + border-radius: 8px; + } + } + } + } +} diff --git a/chat2db-client/src/components/ExecuteSQL/index.tsx b/chat2db-client/src/components/ExecuteSQL/index.tsx new file mode 100644 index 000000000..6dac0c33b --- /dev/null +++ b/chat2db-client/src/components/ExecuteSQL/index.tsx @@ -0,0 +1,114 @@ +import React, { memo, useRef, useState, useMemo, useEffect } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import Iconfont from '@/components/Iconfont'; +import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; +import i18n from '@/i18n'; +import { Button } from 'antd'; +import { formatSql } from '@/utils'; +import sqlService, { IExecuteSqlParams } from '@/service/sql'; +import { DatabaseTypeCode } from '@/constants'; + +interface IProps { + className?: string; + initSql: string; + databaseType: DatabaseTypeCode; + databaseName: string; + dataSourceId: number; + schemaName: string | undefined; + tableName?: string; + executeSuccessCallBack: () => void; +} + +export default memo((props) => { + const { + className, + initSql, + databaseType, + databaseName, + dataSourceId, + schemaName, + tableName, + executeSuccessCallBack, + } = props; + const monacoEditorRef = useRef(null); + const [executeLoading, setExecuteLoading] = useState(false); + const [appendValue, setAppendValue] = useState(''); + const [executeSqlResult, setExecuteSqlResult] = useState(null); + + useEffect(() => { + setAppendValue(initSql); + }, []); + + const handleFormatSql = () => { + const sql = monacoEditorRef.current?.getAllContent() || ''; + formatSql(sql, databaseType).then((res) => { + setAppendValue(res); + }); + }; + + const executeSql = () => { + const executeSQLParams: IExecuteSqlParams = { + sql: monacoEditorRef.current?.getAllContent() || '', + dataSourceId, + databaseName, + schemaName, + tableName, + }; + setExecuteLoading(true); + sqlService + .executeDDL(executeSQLParams) + .then((res) => { + if (res.success) { + executeSuccessCallBack?.(); + } else { + setExecuteSqlResult(res.message); + } + }) + .finally(() => { + setExecuteLoading(false); + }); + }; + + const renderMonacoEditor = useMemo(() => { + return ( + + ); + }, [appendValue]); + + return ( +
    +
    +
    +
    +
    + + {i18n('common.button.format')} +
    + +
    + {renderMonacoEditor} +
    + {executeSqlResult && ( +
    +
    {i18n('common.text.errorMessage')}
    +
    +
    {executeSqlResult}
    +
    +
    + )} +
    +
    + ); +}); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index c305c805a..3e14bbd05 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -6,6 +6,7 @@ import classnames from 'classnames'; import i18n from '@/i18n'; import { CRUD } from '@/constants'; import { TableDataType } from '@/constants/table'; +import ExecuteSQL from '@/components/ExecuteSQL'; import { IManageResultData, IResultConfig } from '@/typings/database'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; import { compareStrings } from '@/utils/sort'; @@ -16,7 +17,7 @@ import StateIndicator from '../../StateIndicator'; import MonacoEditor from '../../Console/MonacoEditor'; import MyPagination from '../Pagination'; import styles from './index.less'; -import sqlService, { IExportParams } from '@/service/sql'; +import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; import { downloadFile } from '@/utils/common'; import lodash from 'lodash'; @@ -77,6 +78,8 @@ export default function TableBox(props: ITableProps) { const [editingData, setEditingData] = useState(''); const [curOperationRowIndex, setCurOperationRowIndex] = useState(-1); const [updateData, setUpdateData] = useState([]); + const [updateDataSql, setUpdateDataSql] = useState(''); + const [viewUpdateDataSql, setViewUpdateDataSql] = useState(false); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -302,6 +305,7 @@ export default function TableBox(props: ITableProps) { } }; + // 处理创建数据 const handelCreateData = () => { // 如果加的这行数据是删除过的,则恢复 const index = updateData.findIndex((item) => item.index === curOperationRowIndex && item.type === CRUD.DELETE); @@ -336,6 +340,7 @@ export default function TableBox(props: ITableProps) { console.log('updateData', updateData); }, [updateData]); + // 处理删除数据 const handelDeleteData = () => { if (curOperationRowIndex === -1) { return; @@ -367,7 +372,10 @@ export default function TableBox(props: ITableProps) { if (!updateData.length) { return; } - console.log('handelViewSql'); + getExecuteUpdateSql().then((res) => { + setUpdateDataSql(res); + setViewUpdateDataSql(true); + }); }; // 更新数据的sql @@ -375,16 +383,49 @@ export default function TableBox(props: ITableProps) { if (!updateData.length) { return; } - const params = { - ...props.executeSqlParams, - tableName: data.tableName || 't_user1', // TODO: - operations: updateData, + getExecuteUpdateSql().then((res) => { + executeSql(res); + }); + }; + + // 获取更新数据的sql + const getExecuteUpdateSql = () => { + return new Promise((resolve) => { + const params = { + databaseName: props.executeSqlParams?.databaseName, + dataSourceId: props.executeSqlParams?.dataSourceId, + schemaName: props.executeSqlParams?.schemaName, + type: props.executeSqlParams?.databaseType, + tableName: data.tableName, + operations: updateData, + }; + sqlService.getExecuteUpdateSql(params).then((res) => { + resolve(res?.[0].sql); + }); + }); + }; + + // 执行sql + const executeSql = (sql: string) => { + const executeSQLParams: IExecuteSqlParams = { + sql, + dataSourceId: props.executeSqlParams?.dataSourceId, + databaseName: props.executeSqlParams?.databaseName, + schemaName: props.executeSqlParams?.schemaName, + tableName: data.tableName, }; - sqlService.getExecuteUpdateSql(params).then((res) => { - console.log('res', res); + sqlService.executeDDL(executeSQLParams).then((res) => { + if (res.success) { + message.success(i18n('common.text.successfulExecution')); + setUpdateData([]); + } else { + // setExecuteSqlResult(res.message); + // TODO:弹出错误弹窗 + } }); }; + // 不通状态下的表格行样式 const tableRowStyle = (rowIndex: number) => { // 如果是当前操作的行 if (rowIndex === curOperationRowIndex) { @@ -415,6 +456,13 @@ export default function TableBox(props: ITableProps) { return {}; }; + // sql执行成功后的回调 + const executeSuccessCallBack = () => { + setViewUpdateDataSql(false); + message.success(i18n('common.text.successfulExecution')); + setUpdateData([]); + }; + const renderContent = () => { const bottomStatus = (
    @@ -443,37 +491,40 @@ export default function TableBox(props: ITableProps) { onClickTotalBtn={onClickTotalBtn} />
    - {/* {data.canEdit && ( - )} */} -
    -
    - -
    -
    - + {data.canEdit && ( +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    -
    - -
    -
    - -
    -
    + )}
    @@ -536,6 +587,27 @@ export default function TableBox(props: ITableProps) { />
    + { + setViewUpdateDataSql(false); + setUpdateDataSql(''); + }} + > + + {contextHolder}
    ); diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 460da87c2..44a37b21d 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -16,6 +16,7 @@ export interface IExecuteSqlParams { dataSourceId?: number; databaseName?: string; schemaName?: string; + tableName?: string; } export interface IExecuteSqlResponse { From 7d5970ee16825499dc648a113e7357f56340231d Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 10:48:48 +0800 Subject: [PATCH 0845/1069] fix: add headList --- chat2db-client/src/components/SearchResult/TableBox/index.tsx | 3 ++- chat2db-client/src/service/sql.ts | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 3e14bbd05..93e0554f3 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -397,10 +397,11 @@ export default function TableBox(props: ITableProps) { schemaName: props.executeSqlParams?.schemaName, type: props.executeSqlParams?.databaseType, tableName: data.tableName, + headerList, operations: updateData, }; sqlService.getExecuteUpdateSql(params).then((res) => { - resolve(res?.[0].sql); + resolve(res || ''); }); }); }; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 44a37b21d..9545b7363 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -212,7 +212,7 @@ const getModifyTableSql = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); /** 获取修改表数据的接口 */ -const getExecuteUpdateSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); +const getExecuteUpdateSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); export default { getExecuteUpdateSql, From cc9bc22a180cfeda5745b630763d684db8097fb5 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 10:52:19 +0800 Subject: [PATCH 0846/1069] destroyOnClose --- chat2db-client/src/components/SearchResult/TableBox/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 93e0554f3..e35c92cfd 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -594,6 +594,7 @@ export default function TableBox(props: ITableProps) { title={i18n('editTable.title.sqlPreview')} open={viewUpdateDataSql} footer={false} + destroyOnClose={true} onCancel={() => { setViewUpdateDataSql(false); setUpdateDataSql(''); From 6cdbee88e893e2e6aef050436f023ee5cc51f6dc Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 11:44:03 +0800 Subject: [PATCH 0847/1069] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/MyNotification/index.tsx | 47 ++++++++++--------- .../SearchResult/TableBox/index.tsx | 25 ++++++---- .../src/components/TabsNew/index.tsx | 3 +- chat2db-client/src/layouts/index.tsx | 6 +-- .../main/workspace/components/Tree/index.tsx | 27 ++++++----- .../workspace/components/Tree/treeConfig.tsx | 2 +- .../components/WorkspaceRightNew/index.tsx | 6 +-- chat2db-client/src/service/base.ts | 3 ++ 8 files changed, 67 insertions(+), 52 deletions(-) diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index 69670f5e2..bd86e51be 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -1,9 +1,9 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { Button, ConfigProvider, message, Modal, notification, Space, Tooltip } from 'antd'; +import React, { useCallback, useState } from 'react'; +import { Button, message, Modal, notification, Space } from 'antd'; import i18n from '@/i18n'; import { IconType } from 'antd/es/notification/interface'; import Iconfont from '../Iconfont'; -import { copy, getApplicationMessage } from '@/utils' +import { copy, getApplicationMessage } from '@/utils'; import styles from './index.less'; interface IProps { @@ -30,9 +30,9 @@ function MyNotification() { const [open, setOpen] = useState(false); const [props, setProps] = useState(); - window._notificationApi = useCallback((props: IProps) => { - const { errorCode, errorMessage, errorDetail, solutionLink } = props; - setProps(props); + window._notificationApi = useCallback((myProps: IProps) => { + const { errorCode, errorMessage, solutionLink } = myProps; + setProps(myProps); const btn = ( - + {solutionLink && ( + + )} ); const renderDescription = () => { return (
    - {props.errorCode} {props.errorMessage} + {errorCode} {errorMessage}
    ); }; @@ -77,25 +79,26 @@ function MyNotification() { }, []); function renderModalTitle() { - return
    - {`${props?.errorCode}:${props?.errorMessage} `} -
    + return
    {`${props?.errorCode}:${props?.errorMessage} `}
    ; } function copyError() { const errorMessage = { getApplicationMessage: getApplicationMessage(), ...props, - } - copy(JSON.stringify(errorMessage)) - message.success(i18n('common.button.copySuccessfully')) + }; + copy(JSON.stringify(errorMessage)); + message.success(i18n('common.button.copySuccessfully')); } function renderModalFooter() { - return
    - {i18n('common.button.copyError')} - {i18n('common.button.copyErrorTips')} -
    + return ( +
    + + {i18n('common.button.copyError')} + {i18n('common.button.copyErrorTips')} +
    + ); } return ( @@ -111,9 +114,7 @@ function MyNotification() { setOpen(false); }} > -
    - {props?.errorDetail} -
    +
    {props?.errorDetail}
    ); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index e35c92cfd..cc98dc1a1 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -317,11 +317,11 @@ export default function TableBox(props: ITableProps) { // 正常的新增 const newTableData = lodash.cloneDeep(tableData); const newData = {}; - columns.forEach((item, index) => { - if (item.name === 'No.') { - newData[`${preCode}${index}${item.name}`] = newTableData.length + 1; + columns.forEach((t, i) => { + if (t.name === 'No.') { + newData[`${preCode}${i}${t.name}`] = newTableData.length + 1; } else { - newData[`${preCode}${index}${item.name}`] = null; + newData[`${preCode}${i}${t.name}`] = null; } }); newTableData.push(newData); @@ -345,15 +345,18 @@ export default function TableBox(props: ITableProps) { if (curOperationRowIndex === -1) { return; } + // 如果是新增的行,则直接删除 const index = updateData.findIndex((item) => item.index === curOperationRowIndex && item.type === CRUD.CREATE); if (index !== -1) { updateData.splice(index, 1); setUpdateData([...updateData]); - setTableData(tableData.filter((item, index) => index !== curOperationRowIndex)); + setTableData(tableData.filter((item, i) => i !== curOperationRowIndex)); return; } - const index2 = updateData.findIndex((item) => item.index === curOperationRowIndex); + + // 正常的删除数据 + const index2 = updateData.findIndex((t) => t.index === curOperationRowIndex); if (index2 === -1) { setUpdateData([ ...updateData, @@ -420,8 +423,14 @@ export default function TableBox(props: ITableProps) { message.success(i18n('common.text.successfulExecution')); setUpdateData([]); } else { - // setExecuteSqlResult(res.message); - // TODO:弹出错误弹窗 + window._notificationApi({ + requestUrl: eventualUrl, + requestParams: JSON.stringify(params), + errorCode, + errorMessage, + errorDetail, + solutionLink, + }); } }); }; diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index a79631019..999e22c5c 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -95,12 +95,11 @@ export default memo((props) => { } return ( - +
    { onDoubleClick(t); }} - key={t.key} className={classnames( { [styles.tabItem]: type !== 'line' }, { [styles.tabItemLine]: type === 'line' }, diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 1a2643a3f..9b099a8e5 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -65,7 +65,7 @@ export default function Layout() { return ( - + ); } @@ -163,7 +163,7 @@ function AppContainer() { {startSchedule === 1 && ( <>
    - + {/*
    */} @@ -188,7 +188,7 @@ function AppContainer() {
    )} {/* 全局的弹窗 */} - +
    ); } diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index 6d9aaba18..590d56984 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -129,6 +129,7 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { function nodeDoubleClick() { if ( + data.treeNodeType === TreeNodeType.TABLE || data.treeNodeType === TreeNodeType.FUNCTION || data.treeNodeType === TreeNodeType.TRIGGER || data.treeNodeType === TreeNodeType.VIEW || @@ -138,16 +139,18 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { 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 { + } + // else if (data.treeNodeType === TreeNodeType.TABLE) { + // dispatch({ + // type: 'workspace/setCreateTabIntro', + // payload: { + // type: CreateTabIntroType.EditTableData, + // workspaceTabType: WorkspaceTabType.EditTableData, + // treeNodeData: data, + // }, + // }); + // } + else { handleClick(data); } } @@ -178,10 +181,10 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { )}
    - +
    -
    +
    {data.treeNodeType === TreeNodeType.COLUMN &&
    {data.columnType}
    }
    diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index eb728d78a..9cc6c8236 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -242,7 +242,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }); }, operationColumn: [ - OperationColumn.EditTableData, + // OperationColumn.EditTableData, OperationColumn.Top, OperationColumn.ExportDDL, OperationColumn.EditTable, diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx index e2455edb9..7119dd3c6 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useState, useMemo } from 'react'; +import React, { memo, useEffect, useState, useMemo, Fragment } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; @@ -504,7 +504,7 @@ const WorkspaceRight = memo((props: IProps) => { key: t.id, editableName: t.editableName, children: ( - <> + {[ WorkspaceTabType.CONSOLE, WorkspaceTabType.FUNCTION, @@ -545,7 +545,7 @@ const WorkspaceRight = memo((props: IProps) => { tableName={uniqueData.tableName} /> )} - + ), }; }); diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 11039f392..e18a3ae93 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -162,6 +162,9 @@ export default function createRequest

    (url: string, options?: I case 'put': dataName = 'data'; break; + default: + dataName = 'params'; + break; } let eventualUrl = outside ? `${outsideUrlPrefix}${_url}` : `${_baseURL}${_url}`; From 3c03e53690108c58da44e322770eef12c5cfb342 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 9 Oct 2023 11:47:56 +0800 Subject: [PATCH 0848/1069] add data update --- .../main/java/ai/chat2db/plugin/mysql/MysqlMetaData.java | 2 +- .../main/java/ai/chat2db/plugin/oracle/OracleMetaData.java | 2 +- .../java/ai/chat2db/plugin/sqlserver/SqlServerMetaData.java | 6 ++++++ .../server/domain/core/impl/DlTemplateServiceImpl.java | 3 --- .../main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java | 2 +- 5 files changed, 9 insertions(+), 6 deletions(-) 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 a7c39c74c..1f2cadfea 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 @@ -273,6 +273,6 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).map(name -> "`" + name + "`").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining(".")); } } 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 b9ffa432f..814a4c475 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 @@ -284,6 +284,6 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } } 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 d83b31355..1c94e5b5d 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 @@ -2,6 +2,7 @@ import java.sql.Connection; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -224,4 +225,9 @@ public Table view(Connection connection, String databaseName, String schemaName, return table; }); } + + @Override + public String getMetaDataName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "[" + name + "]").collect(Collectors.joining(".")); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 397edb047..6219e3ece 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -116,10 +116,7 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar executeResult.setCanEdit(SqlUtils.canEdit(originalSql)); executeResult.setTableName(SqlUtils.getTableName(originalSql,dbType)); }catch (Exception e){ - } - - if (SqlTypeEnum.SELECT.getCode().equals(sqlType)) { executeResult.setPageNo(pageNo); executeResult.setPageSize(pageSize); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index 193d0ff3e..ea30a1574 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -110,7 +110,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).collect(Collectors.joining(".")); } From c781f169287932ef2fd870d4f54e1c026a58d3d4 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 14:38:20 +0800 Subject: [PATCH 0849/1069] =?UTF-8?q?feat:=E9=AB=98=E4=BA=AE=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E8=BF=87=E7=9A=84=E5=8D=95=E5=85=83=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SearchResult/TableBox/index.less | 14 ++-- .../SearchResult/TableBox/index.tsx | 81 ++++++++++++------- 2 files changed, 61 insertions(+), 34 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 185500f90..f202d94f7 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -126,20 +126,18 @@ } .tableItem { - width: 100%; + width: calc(100% + 8px); height: 100%; cursor: pointer; display: flex; align-items: center; justify-content: space-between; position: relative; - + padding: 0px 4px; + margin: 0px -4px; max-height: 120px; overflow-y: auto; - - // &::-webkit-scrollbar { - // display: none; - // } + user-select: none; &:hover .tableHoverBox { display: block; @@ -150,6 +148,10 @@ } } +.tableItemEdit { + background-color: var(--color-success-bg); +} + .tableItemNo { width: 100%; text-align: center; diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index cc98dc1a1..fbfe10714 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -168,25 +168,37 @@ export default function TableBox(props: ITableProps) { const newTableData = lodash.cloneDeep(tableData); newTableData[rowIndex][`${preCode}${colIndex}${columns[colIndex].name}`] = editingData; setTableData(newTableData); - // 如果已经存在该行的更新数据,则更新,否则新增 + // 如果已经存在该行的更新数据则更新,否则新增 const index = updateData.findIndex((item) => item.index === rowIndex); + const oldDataList = dataList[rowIndex]; + const newDataList = Object.keys(newTableData[rowIndex]).map((item) => newTableData[rowIndex][item]); + + // 如果datalist和oldDataList的数据一样,代表用户虽然编辑过,但是又改回去了,则不需要更新 + if (oldDataList?.join(',') === newDataList?.join(',')) { + if (index !== -1) { + setUpdateData(updateData.filter((item) => item.index !== rowIndex)); + } + return; + } + if (index === -1) { setUpdateData([ ...updateData, { type: CRUD.UPDATE, - oldDataList: dataList[rowIndex], - dataList: Object.keys(newTableData[rowIndex]).map((item) => newTableData[rowIndex][item]), + oldDataList: oldDataList, + dataList: newDataList, index: rowIndex, }, ]); - } else { - updateData[index] = { - ...updateData[index], - dataList: Object.keys(newTableData[rowIndex]).map((item) => newTableData[rowIndex][item]), - }; - setUpdateData([...updateData]); + return; } + + updateData[index] = { + ...updateData[index], + dataList: newDataList, + }; + setUpdateData([...updateData]); }; const renderTableCellValue = (value) => { @@ -197,6 +209,18 @@ export default function TableBox(props: ITableProps) { } }; + // 每个单元格的样式 + const tableCellStyle = (value, colIndex, rowIndex) => { + // 单元格的基础样式 + const styleList = [styles.tableItem]; + // 编辑过的单元格的样式 + const oldValue = dataList?.[rowIndex]?.[colIndex]; + if (value !== oldValue) { + styleList.push(styles.tableItemEdit); + } + return classnames(...styleList); + }; + const columns: ArtColumn[] = useMemo(() => { return (headerList || []).map((item, colIndex) => { const { dataType, name } = item; @@ -227,7 +251,7 @@ export default function TableBox(props: ITableProps) { render: (value: any, a, rowIndex) => { return (

    {editingCell?.join(',') === `${colIndex},${rowIndex}` ? ( @@ -356,17 +380,18 @@ export default function TableBox(props: ITableProps) { } // 正常的删除数据 - const index2 = updateData.findIndex((t) => t.index === curOperationRowIndex); - if (index2 === -1) { - setUpdateData([ - ...updateData, - { - type: CRUD.DELETE, - oldDataList: dataList[curOperationRowIndex], - index: curOperationRowIndex, - }, - ]); + const deleteIndex = updateData.findIndex((t) => t.index === curOperationRowIndex); + if (deleteIndex !== -1) { + updateData.splice(deleteIndex, 1); } + setUpdateData([ + ...updateData, + { + type: CRUD.DELETE, + oldDataList: dataList[curOperationRowIndex], + index: curOperationRowIndex, + }, + ]); setCurOperationRowIndex(-1); }; @@ -423,14 +448,14 @@ export default function TableBox(props: ITableProps) { message.success(i18n('common.text.successfulExecution')); setUpdateData([]); } else { - window._notificationApi({ - requestUrl: eventualUrl, - requestParams: JSON.stringify(params), - errorCode, - errorMessage, - errorDetail, - solutionLink, - }); + // window._notificationApi({ + // requestUrl: eventualUrl, + // requestParams: JSON.stringify(params), + // errorCode, + // errorMessage, + // errorDetail, + // solutionLink, + // }); } }); }; From b38d4a8d895b60df84156858209427b4820d6d3a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 15:15:29 +0800 Subject: [PATCH 0850/1069] feat: Locate to the lowest end when adding data to the end --- .../DatabaseTableEditor/ColumnList/index.tsx | 7 ++++++- .../DatabaseTableEditor/IncludeCol/index.tsx | 16 +++++++++++++++- .../DatabaseTableEditor/IndexList/index.tsx | 5 +++++ .../components/SearchResult/TableBox/index.tsx | 10 ++++++++-- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 3489b325f..75560e56e 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; +import React, { useContext, useEffect, useState, useRef, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import styles from './index.less'; import { MenuOutlined } from '@ant-design/icons'; import { DndContext, type DragEndEvent } from '@dnd-kit/core'; @@ -110,6 +110,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const [form] = Form.useForm(); const [editingData, setEditingData] = useState(null); const [editingConfig, setEditingConfig] = useState(null); + const tableRef = useRef(null); const [databaseSupportField, setDatabaseSupportField] = useState<{ columnTypes: IColumnTypesOption[]; charsets: IOption[]; @@ -386,6 +387,9 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) }; setDataSource([...dataSource, newData]); edit(newData); + setTimeout(() => { + tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); + }, 0); }; const deleteData = (record) => { @@ -505,6 +509,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) i.key!)} strategy={verticalListSortingStrategy}>
    ) const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(null); const isEditing = (record: IIndexIncludeColumnItem) => record.key === editingKey; + const tableRef = useRef(null); useEffect(() => { if (includedColumnList.length) { @@ -78,6 +88,9 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) setDataSource([...dataSource, newData]); console.log([...dataSource, newData]); edit(newData); + setTimeout(() => { + tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); + }, 0); }; const deleteData = (record) => { @@ -183,6 +196,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef)
    ) = const [editingData, setEditingData] = useState(null); const [includeColModalOpen, setIncludeColModalOpen] = useState(false); const includeColRef = useRef(null); + const tableRef = useRef(null); const isEditing = (record: IIndexItem) => record.key === editingData?.key; @@ -110,6 +111,9 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = const newData = createInitialData(); setDataSource([...dataSource, newData]); edit(newData); + setTimeout(() => { + tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); + }, 0); }; const deleteData = (record) => { @@ -330,6 +334,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = i.key!)} strategy={verticalListSortingStrategy}>
    ([]); const [updateDataSql, setUpdateDataSql] = useState(''); const [viewUpdateDataSql, setViewUpdateDataSql] = useState(false); + const tableBoxRef = React.useRef(null); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -358,6 +359,11 @@ export default function TableBox(props: ITableProps) { index: newTableData.length - 1, }, ]); + + // 新增一条数据,tableBox需要滚动到最下方 + setTimeout(() => { + tableBoxRef.current?.scrollTo(0, tableBoxRef.current?.scrollHeight + 31); + }, 0); }; useEffect(() => { @@ -460,7 +466,7 @@ export default function TableBox(props: ITableProps) { }); }; - // 不通状态下的表格行样式 + // 不同状态下的表格行样式 const tableRowStyle = (rowIndex: number) => { // 如果是当前操作的行 if (rowIndex === curOperationRowIndex) { @@ -591,7 +597,7 @@ export default function TableBox(props: ITableProps) { }; return ( -
    +
    {renderContent()} Date: Mon, 9 Oct 2023 17:18:37 +0800 Subject: [PATCH 0851/1069] fix:Edit table data sorting bug --- .../SearchResult/TableBox/index.less | 3 + .../SearchResult/TableBox/index.tsx | 118 ++++++++++-------- chat2db-client/src/constants/workspace.ts | 2 +- .../components/WorkspaceRightNew/index.tsx | 8 +- 4 files changed, 75 insertions(+), 56 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index f202d94f7..557c7278e 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -196,5 +196,8 @@ } .monacoEditor { + border: 1px solid var(--color-border); + border-radius: 4px; + overflow: hidden; height: 60vh; } diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index eecca9fbb..c1f5f1fc3 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -39,7 +39,7 @@ interface IUpdateData { oldDataList?: string[]; dataList?: string[]; type: CRUD; - index: number; + rowNo: string; } // const defaultResultConfig: IResultConfig = { @@ -74,9 +74,9 @@ export default function TableBox(props: ITableProps) { const [viewTableCellData, setViewTableCellData] = useState(null); const [messageApi, contextHolder] = message.useMessage(); const [tableData, setTableData] = useState([]); - const [editingCell, setEditingCell] = useState<[number, number] | null>(null); + const [editingCell, setEditingCell] = useState<[string, string] | null>(null); const [editingData, setEditingData] = useState(''); - const [curOperationRowIndex, setCurOperationRowIndex] = useState(-1); + const [curOperationRowNo, setCurOperationRowNo] = useState(null); const [updateData, setUpdateData] = useState([]); const [updateDataSql, setUpdateDataSql] = useState(''); const [viewUpdateDataSql, setViewUpdateDataSql] = useState(false); @@ -153,31 +153,43 @@ export default function TableBox(props: ITableProps) { setViewTableCellData(null); } - const handleDoubleClickTableItem = (colIndex, rowIndex, value) => { + const handleDoubleClickTableItem = (colIndex, rowNo, value) => { if (!data.canEdit) { return; } setEditingData(value); - setEditingCell([colIndex, rowIndex]); + setEditingCell([colIndex, rowNo]); }; // 编辑数据失焦 const editDataOnBlur = () => { setEditingCell(null); setEditingData(''); - const [colIndex, rowIndex] = editingCell!; + const [colIndex, rowNo] = editingCell!; const newTableData = lodash.cloneDeep(tableData); - newTableData[rowIndex][`${preCode}${colIndex}${columns[colIndex].name}`] = editingData; + let oldDataList: string[] = []; + let newDataList: string[] = []; + newTableData.forEach((item) => { + if (item[`${preCode}0No.`] === rowNo) { + item[`${preCode}${colIndex}${columns[colIndex].name}`] = editingData; + newDataList = Object.keys(item).map((i) => item[i]); + } + }); + setTableData(newTableData); - // 如果已经存在该行的更新数据则更新,否则新增 - const index = updateData.findIndex((item) => item.index === rowIndex); - const oldDataList = dataList[rowIndex]; - const newDataList = Object.keys(newTableData[rowIndex]).map((item) => newTableData[rowIndex][item]); + dataList.forEach((item) => { + if (item[0] === rowNo) { + oldDataList = item; + } + }); + + // 如果已经存在该行的更新数据则更新,否则新增 + const index = updateData.findIndex((item) => item.rowNo === rowNo); // 如果datalist和oldDataList的数据一样,代表用户虽然编辑过,但是又改回去了,则不需要更新 if (oldDataList?.join(',') === newDataList?.join(',')) { if (index !== -1) { - setUpdateData(updateData.filter((item) => item.index !== rowIndex)); + setUpdateData(updateData.filter((item) => item.rowNo !== rowNo)); } return; } @@ -189,7 +201,7 @@ export default function TableBox(props: ITableProps) { type: CRUD.UPDATE, oldDataList: oldDataList, dataList: newDataList, - index: rowIndex, + rowNo, }, ]); return; @@ -211,11 +223,16 @@ export default function TableBox(props: ITableProps) { }; // 每个单元格的样式 - const tableCellStyle = (value, colIndex, rowIndex) => { + const tableCellStyle = (value, colIndex, rowNo) => { // 单元格的基础样式 const styleList = [styles.tableItem]; // 编辑过的单元格的样式 - const oldValue = dataList?.[rowIndex]?.[colIndex]; + let oldValue = ''; + dataList.forEach((item) => { + if (item[0] === rowNo) { + oldValue = item[colIndex]; + } + }); if (value !== oldValue) { styleList.push(styles.tableItemEdit); } @@ -249,13 +266,14 @@ export default function TableBox(props: ITableProps) { name: name, key: name, width: 120, - render: (value: any, a, rowIndex) => { + render: (value: any, rowData) => { + const rowNo = rowData[`${preCode}0No.`]; return (
    - {editingCell?.join(',') === `${colIndex},${rowIndex}` ? ( + {editingCell?.join(',') === `${colIndex},${rowNo}` ? ( { @@ -333,7 +351,7 @@ export default function TableBox(props: ITableProps) { // 处理创建数据 const handelCreateData = () => { // 如果加的这行数据是删除过的,则恢复 - const index = updateData.findIndex((item) => item.index === curOperationRowIndex && item.type === CRUD.DELETE); + const index = updateData.findIndex((item) => item.rowNo === curOperationRowNo && item.type === CRUD.DELETE); if (index !== -1) { updateData.splice(index, 1); setUpdateData([...updateData]); @@ -344,7 +362,7 @@ export default function TableBox(props: ITableProps) { const newData = {}; columns.forEach((t, i) => { if (t.name === 'No.') { - newData[`${preCode}${i}${t.name}`] = newTableData.length + 1; + newData[`${preCode}${i}${t.name}`] = (newTableData.length + 1).toString(); } else { newData[`${preCode}${i}${t.name}`] = null; } @@ -356,7 +374,7 @@ export default function TableBox(props: ITableProps) { { type: CRUD.CREATE, dataList: Object.keys(newData).map((item) => newData[item]), - index: newTableData.length - 1, + rowNo: newTableData.length.toString(), }, ]); @@ -366,27 +384,23 @@ export default function TableBox(props: ITableProps) { }, 0); }; - useEffect(() => { - console.log('updateData', updateData); - }, [updateData]); - // 处理删除数据 const handelDeleteData = () => { - if (curOperationRowIndex === -1) { + if (curOperationRowNo === null) { return; } // 如果是新增的行,则直接删除 - const index = updateData.findIndex((item) => item.index === curOperationRowIndex && item.type === CRUD.CREATE); + const index = updateData.findIndex((item) => item.rowNo === curOperationRowNo && item.type === CRUD.CREATE); if (index !== -1) { updateData.splice(index, 1); setUpdateData([...updateData]); - setTableData(tableData.filter((item, i) => i !== curOperationRowIndex)); + setTableData(tableData.filter((item) => item[`${preCode}0No.`] !== curOperationRowNo)); return; } // 正常的删除数据 - const deleteIndex = updateData.findIndex((t) => t.index === curOperationRowIndex); + const deleteIndex = updateData.findIndex((t) => t.rowNo === curOperationRowNo); if (deleteIndex !== -1) { updateData.splice(deleteIndex, 1); } @@ -394,11 +408,11 @@ export default function TableBox(props: ITableProps) { ...updateData, { type: CRUD.DELETE, - oldDataList: dataList[curOperationRowIndex], - index: curOperationRowIndex, + oldDataList: dataList[curOperationRowNo!], + rowNo: curOperationRowNo!, }, ]); - setCurOperationRowIndex(-1); + setCurOperationRowNo(null); }; // 查看更新数据的sql @@ -467,9 +481,9 @@ export default function TableBox(props: ITableProps) { }; // 不同状态下的表格行样式 - const tableRowStyle = (rowIndex: number) => { + const tableRowStyle = (rowNo: string) => { // 如果是当前操作的行 - if (rowIndex === curOperationRowIndex) { + if (rowNo === curOperationRowNo) { return { '--hover-bgcolor': 'transparent', '--bgcolor': 'transparent', @@ -477,7 +491,7 @@ export default function TableBox(props: ITableProps) { }; } // 如果是删除过的行 - const index = updateData.findIndex((item) => item.index === rowIndex && item.type === CRUD.DELETE); + const index = updateData.findIndex((item) => item.rowNo === rowNo && item.type === CRUD.DELETE); if (index !== -1) { return { '--hover-bgcolor': 'transparent', @@ -486,7 +500,9 @@ export default function TableBox(props: ITableProps) { }; } // 如果是新增的行 - const index2 = updateData.findIndex((item) => item.index === rowIndex && item.type === CRUD.CREATE); + const index2 = updateData.findIndex((item) => { + return item.rowNo === rowNo && item.type === CRUD.CREATE; + }); if (index2 !== -1) { return { '--hover-bgcolor': 'transparent', @@ -543,7 +559,7 @@ export default function TableBox(props: ITableProps) {
    @@ -580,11 +596,12 @@ export default function TableBox(props: ITableProps) { components={{ EmptyContent: () =>

    {i18n('common.text.noData')}

    }} isStickyHead stickyTop={31} - getRowProps={(record, rowIndex) => { + getRowProps={(record) => { + const rowNo = record[`${preCode}0No.`]; return { - style: tableRowStyle(rowIndex), + style: tableRowStyle(rowNo), onClick() { - setCurOperationRowIndex(rowIndex); + setCurOperationRowNo(rowNo); }, }; }} @@ -605,15 +622,16 @@ export default function TableBox(props: ITableProps) { onCancel={handleCancel} width="60vw" maskClosable={false} - footer={ - <> - { - - } - - } + footer={false} + // footer={ + // <> + // { + // + // } + // + // } >
    ((props: IProps) => { setWorkspaceTabList(workspaceTabList.filter((t) => t.id !== data.key)); const editData = workspaceTabList?.find((t) => t.id === data.key); if ( - editData?.type === WorkspaceTabType.CONSOLE || - editData?.type === WorkspaceTabType.FUNCTION || - editData?.type === WorkspaceTabType.PROCEDURE || - editData?.type === WorkspaceTabType.TRIGGER || - editData?.type === WorkspaceTabType.VIEW + editData?.type !== WorkspaceTabType.EditTable && + editData?.type !== WorkspaceTabType.CreateTable && + editData?.type !== WorkspaceTabType.EditTableData ) { closeWindowTab(data.key as number); } From 26b9d57f0c63c2a4f031d3994b8ca509de31fea1 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 9 Oct 2023 18:27:24 +0800 Subject: [PATCH 0852/1069] Support table schema edit --- .../ai/chat2db/plugin/db2/DB2MetaData.java | 2 + .../chat2db/plugin/mysql/MysqlMetaData.java | 1 + .../plugin/mysql/builder/MysqlSqlBuilder.java | 2 +- .../plugin/mysql/type/MysqlIndexTypeEnum.java | 18 ++ .../chat2db/plugin/oracle/OracleMetaData.java | 1 + .../oracle/type/OracleIndexTypeEnum.java | 19 ++ .../chat2db/plugin/sqlite/SqliteMetaData.java | 18 +- .../plugin/sqlite/builder/SqliteBuilder.java | 106 ++++++++++ .../plugin/sqlite/type/SqliteCharsetEnum.java | 66 +++++++ .../sqlite/type/SqliteCollationEnum.java | 31 +++ .../sqlite/type/SqliteColumnTypeEnum.java | 187 ++++++++++++++++++ .../sqlite/type/SqliteIndexTypeEnum.java | 127 ++++++++++++ .../plugin/sqlserver/SqlServerMetaData.java | 18 +- .../type/SqlServerIndexTypeEnum.java | 20 +- .../java/ai/chat2db/spi/model/IndexType.java | 14 ++ .../java/ai/chat2db/spi/model/TableMeta.java | 3 + 16 files changed, 626 insertions(+), 7 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCharsetEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCollationEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexType.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 7ee3e8443..07a24e999 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 @@ -59,4 +59,6 @@ public String tableDDL(Connection connection, String databaseName, String schema return null; }); } + + } 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 1f2cadfea..80da4803b 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 @@ -268,6 +268,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .columnTypes(MysqlColumnTypeEnum.getTypes()) .charsets(MysqlCharsetEnum.getCharsets()) .collations(MysqlCollationEnum.getCollations()) + .indexTypes(MysqlIndexTypeEnum.getIndexTypes()) .build(); } 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 3420b8265..4d126de9d 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 @@ -82,7 +82,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { - if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())&& StringUtils.isNotBlank(tableColumn.getName())){ MysqlColumnTypeEnum typeEnum = MysqlColumnTypeEnum.getByType(tableColumn.getColumnType()); script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); } diff --git a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java index b4266434c..de0162239 100644 --- a/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-mysql/src/main/java/ai/chat2db/plugin/mysql/type/MysqlIndexTypeEnum.java @@ -1,10 +1,14 @@ package ai.chat2db.plugin.mysql.type; import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; +import java.util.Arrays; +import java.util.List; + public enum MysqlIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), @@ -30,9 +34,20 @@ public String getKeyword() { private String keyword; + public IndexType getIndexType() { + return indexType; + } + + public void setIndexType(IndexType indexType) { + this.indexType = indexType; + } + + private IndexType indexType; + MysqlIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; + this.indexType = new IndexType(name); } @@ -108,4 +123,7 @@ private String buildDropIndex(TableIndex tableIndex) { } return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); } + public static List getIndexTypes() { + return Arrays.asList(MysqlIndexTypeEnum.values()).stream().map(MysqlIndexTypeEnum::getIndexType).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 814a4c475..74d479291 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 @@ -279,6 +279,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .columnTypes(OracleColumnTypeEnum.getTypes()) .charsets(Lists.newArrayList()) .collations(Lists.newArrayList()) + .indexTypes(OracleIndexTypeEnum.getIndexTypes()) .build(); } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java index de6142005..f408f189a 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/type/OracleIndexTypeEnum.java @@ -1,10 +1,14 @@ package ai.chat2db.plugin.oracle.type; import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; +import java.util.Arrays; +import java.util.List; + public enum OracleIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), @@ -15,6 +19,16 @@ public enum OracleIndexTypeEnum { BITMAP("BITMAP", "BITMAP INDEX"); + public IndexType getIndexType() { + return indexType; + } + + public void setIndexType(IndexType indexType) { + this.indexType = indexType; + } + + private IndexType indexType; + public String getName() { return name; @@ -32,6 +46,7 @@ public String getKeyword() { OracleIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; + this.indexType = new IndexType(name); } @@ -104,4 +119,8 @@ private String buildDropIndex(TableIndex tableIndex) { return script.toString(); } + + public static List getIndexTypes() { + return Arrays.asList(OracleIndexTypeEnum.values()).stream().map(OracleIndexTypeEnum::getIndexType).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 ccda182aa..df1ce6a3f 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 @@ -6,12 +6,17 @@ import java.util.List; import java.util.stream.Collectors; +import ai.chat2db.plugin.sqlite.type.SqliteCollationEnum; +import ai.chat2db.plugin.sqlite.type.SqliteColumnTypeEnum; +import ai.chat2db.plugin.sqlite.type.SqliteIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.TableMeta; import ai.chat2db.spi.sql.SQLExecutor; import com.google.common.collect.Lists; +import org.apache.commons.lang3.StringUtils; public class SqliteMetaData extends DefaultMetaService implements MetaData { @Override @@ -38,8 +43,19 @@ public List schemas(Connection connection,String databaseName) { return Lists.newArrayList(); } + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return TableMeta.builder() + .columnTypes(SqliteColumnTypeEnum.getTypes()) + .charsets(null) + .collations(SqliteCollationEnum.getCollations()) + .indexTypes(SqliteIndexTypeEnum.getIndexTypes()) + .build(); + } + + @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java new file mode 100644 index 000000000..8b204452f --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java @@ -0,0 +1,106 @@ +package ai.chat2db.plugin.sqlite.builder; + +import ai.chat2db.plugin.sqlite.type.SqliteColumnTypeEnum; +import ai.chat2db.plugin.sqlite.type.SqliteIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import org.apache.commons.lang3.StringUtils; + + +public class SqliteBuilder implements SqlBuilder { + @Override + public String buildCreateTableSql(Table table) { + StringBuilder script = new StringBuilder(); + script.append("CREATE TABLE "); + script.append("\"").append(table.getName()).append("\"").append(" (").append("\n"); + + // append column + for (TableColumn column : table.getColumnList()) { + if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType())){ + continue; + } + SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(column.getColumnType()); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + } + + // append primary key and index + for (TableIndex tableIndex : table.getIndexList()) { + if(StringUtils.isBlank(tableIndex.getName())|| StringUtils.isBlank(tableIndex.getType())){ + continue; + } + SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append("").append(sqliteIndexTypeEnum.buildIndexScript(tableIndex)).append(",\n"); + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append("\n)"); + + + if (StringUtils.isNotBlank(table.getEngine())) { + script.append(" ENGINE=").append(table.getEngine()); + } + + if (StringUtils.isNotBlank(table.getCharset())) { + script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); + } + + if (StringUtils.isNotBlank(table.getCollate())) { + script.append(" COLLATE=").append(table.getCollate()); + } + + if (table.getIncrementValue() != null) { + script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); + } + + if (StringUtils.isNotBlank(table.getComment())) { + script.append(" COMMENT='").append(table.getComment()).append("'"); + } + + if (StringUtils.isNotBlank(table.getPartition())) { + script.append(" \n").append(table.getPartition()); + } + script.append(";"); + + return script.toString(); + } + + @Override + public String buildModifyTaleSql(Table oldTable, Table newTable) { + StringBuilder script = new StringBuilder(); + script.append("ALTER TABLE ").append("\"").append(oldTable.getName()).append("\"").append("\n"); + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(",\n"); + } + if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { + script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); + } + if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { + script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); + } + + // append modify column + for (TableColumn tableColumn : newTable.getColumnList()) { + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())&& StringUtils.isNotBlank(tableColumn.getName())){ + SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + } + } + + // append modify index + for (TableIndex tableIndex : newTable.getIndexList()) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { + SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); + script.append("\t").append(sqliteIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); + } + } + + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append(";"); + + return script.toString(); + } + + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCharsetEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCharsetEnum.java new file mode 100644 index 000000000..32973e1da --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCharsetEnum.java @@ -0,0 +1,66 @@ +//package ai.chat2db.plugin.sqlite.type; +// +//import ai.chat2db.spi.model.Charset; +// +//import java.util.Arrays; +//import java.util.List; +// +//public enum SqliteCharsetEnum { +// +// UTF8("utf8", "utf8_general_ci"), +// BIG5("big5", "big5_chinese_ci"), +// DEC8("dec8", "dec8_swedish_ci"), +// CP850("cp850", "cp850_general_ci"), +// HP8("hp8", "hp8_english_ci"), +// KOI8R("koi8r", "koi8r_general_ci"), +// LATIN1("latin1", "latin1_swedish_ci"), +// LATIN2("latin2", "latin2_general_ci"), +// SWE7("swe7", "swe7_swedish_ci"), +// ASCII("ascii", "ascii_general_ci"), +// UJIS("ujis", "ujis_japanese_ci"), +// SJIS("sjis", "sjis_japanese_ci"), +// HEBREW("hebrew", "hebrew_general_ci"), +// TIS620("tis620", "tis620_thai_ci"), +// EUCKR("euckr", "euckr_korean_ci"), +// KOI8U("koi8u", "koi8u_general_ci"), +// GB2312("gb2312", "gb2312_chinese_ci"), +// GREEK("greek", "greek_general_ci"), +// CP1250("cp1250", "cp1250_general_ci"), +// GBK("gbk", "gbk_chinese_ci"), +// LATIN5("latin5", "latin5_turkish_ci"), +// ARMSCII8("armscii8", "armscii8_general_ci"), +// UCS2("ucs2", "ucs2_general_ci"), +// CP866("cp866", "cp866_general_ci"), +// KEYBCS2("keybcs2", "keybcs2_general_ci"), +// MACCE("macce", "macce_general_ci"), +// MACROMAN("macroman", "macroman_general_ci"), +// CP852("cp852", "cp852_general_ci"), +// LATIN7("latin7", "latin7_general_ci"), +// UTF8MB4("utf8mb4", "utf8mb4_general_ci"), +// CP1251("cp1251", "cp1251_general_ci"), +// UTF16("utf16", "utf16_general_ci"), +// UTF16LE("utf16le", "utf16le_general_ci"), +// CP1256("cp1256", "cp1256_general_ci"), +// CP1257("cp1257", "cp1257_general_ci"), +// UTF32("utf32", "utf32_general_ci"), +// BINARY("binary", "binary"), +// GEOSTD8("geostd8", "geostd8_general_ci"), +// CP932("cp932", "cp932_japanese_ci"), +// EUCJPMS("eucjpms", "eucjpms_japanese_ci"), +// GB18030("gb18030", "gb18030_chinese_ci"); +// private Charset charset; +// +// SqliteCharsetEnum(String charsetName, String defaultCollationName) { +// this.charset = new Charset(charsetName, defaultCollationName); +// } +// +// +// public Charset getCharset() { +// return charset; +// } +// +// public static List getCharsets() { +// return Arrays.stream(SqliteCharsetEnum.values()).map(SqliteCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); +// } +// +//} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCollationEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCollationEnum.java new file mode 100644 index 000000000..39740b627 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteCollationEnum.java @@ -0,0 +1,31 @@ +package ai.chat2db.plugin.sqlite.type; + +import ai.chat2db.spi.model.Collation; + +import java.util.Arrays; +import java.util.List; + +public enum SqliteCollationEnum { + + BINARY("BINARY"), + + NOCASE("NOCASE"), + + RTRIM("RTRIM"), + ; + private Collation collation; + + SqliteCollationEnum(String collationName) { + this.collation = new Collation(collationName); + } + + public Collation getCollation() { + return collation; + } + + + public static List getCollations() { + return Arrays.asList(SqliteCollationEnum.values()).stream().map(SqliteCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java new file mode 100644 index 000000000..a59e59623 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java @@ -0,0 +1,187 @@ +package ai.chat2db.plugin.sqlite.type; + +import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum SqliteColumnTypeEnum implements ColumnBuilder { + + + INTEGER("INTEGER", true, false, true, false, false, true, true, false, false, false), + + REAL("REAL", true, false, true, false, false, true, true, false, false, false), + + BLOB("BLOB", true, false, true, false, false, true, true, false, false, false), + + + TEXT("TEXT", true, false, true, false, false, true, true, false, false, false), + + ; + private ColumnType columnType; + + public static SqliteColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + + public ColumnType getColumnType() { + return columnType; + } + + + SqliteColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); + } + + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (SqliteColumnTypeEnum value : SqliteColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); + } + } + + + @Override + public String buildCreateColumnSql(TableColumn column) { + SqliteColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + if (type == null) { + return ""; + } + StringBuilder script = new StringBuilder(); + + script.append("`").append(column.getName()).append("`").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + script.append(buildCharset(column, type)).append(" "); + + script.append(buildCollation(column, type)).append(" "); + + script.append(buildNullable(column, type)).append(" "); + + script.append(buildDefaultValue(column, type)).append(" "); + + script.append(buildExt(column, type)).append(" "); + + script.append(buildAutoIncrement(column, type)).append(" "); + + script.append(buildComment(column, type)).append(" "); + + return script.toString(); + } + + private String buildCharset(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.getColumnType().isSupportCharset() || StringUtils.isEmpty(column.getCharSetName())) { + return ""; + } + return StringUtils.join("CHARACTER SET ", column.getCharSetName()); + } + + private String buildCollation(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { + return ""; + } + return StringUtils.join("COLLATE ", column.getCollationName()); + } + + @Override + public String buildModifyColumn(TableColumn tableColumn) { + + if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { + return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); + } + if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { + return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); + } + if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { + if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { + return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); + } else { + return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); + } + } + return ""; + } + + private String buildAutoIncrement(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.getColumnType().isSupportAutoIncrement()) { + return ""; + } + if (column.getAutoIncrement() != null && column.getAutoIncrement()) { + return "AUTO_INCREMENT"; + } + return ""; + } + + private String buildComment(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())) { + return ""; + } + return StringUtils.join("COMMENT '", column.getComment(), "'"); + } + + private String buildExt(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())) { + return ""; + } + return column.getComment(); + } + + private String buildDefaultValue(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { + return ""; + } + + if ("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())) { + return StringUtils.join("DEFAULT ''"); + } + + if ("NULL".equalsIgnoreCase(column.getDefaultValue().trim())) { + return StringUtils.join("DEFAULT NULL"); + } + + return StringUtils.join("DEFAULT ", column.getDefaultValue()); + } + + private String buildNullable(TableColumn column, SqliteColumnTypeEnum type) { + if (!type.getColumnType().isSupportNullable()) { + return ""; + } + if (column.getNullable() != null && 1 == column.getNullable()) { + return "NULL"; + } else { + return "NOT NULL"; + } + } + + private String buildDataType(TableColumn column, SqliteColumnTypeEnum type) { + String columnType = type.columnType.getTypeName(); + + if (column.getColumnSize() == null || column.getDecimalDigits() == null) { + return columnType; + } + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); + } + if (column.getColumnSize() != null && column.getDecimalDigits() != null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); + } + return columnType; + } + + + public static List getTypes() { + return Arrays.stream(SqliteColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } + + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java new file mode 100644 index 000000000..a33acd36b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java @@ -0,0 +1,127 @@ +package ai.chat2db.plugin.sqlite.type; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.Collation; +import ai.chat2db.spi.model.IndexType; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; + +public enum SqliteIndexTypeEnum { + + PRIMARY_KEY("Primary", "PRIMARY KEY"), + + NORMAL("Normal", "INDEX"), + + UNIQUE("Unique", "UNIQUE INDEX"); + + + public IndexType getIndexType() { + return indexType; + } + + public void setIndexType(IndexType indexType) { + this.indexType = indexType; + } + + private IndexType indexType; + + public String getName() { + return name; + } + + private String name; + + + public String getKeyword() { + return keyword; + } + + private String keyword; + + SqliteIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + this.indexType = new IndexType(name); + } + + + public static SqliteIndexTypeEnum getByType(String type) { + for (SqliteIndexTypeEnum value : SqliteIndexTypeEnum.values()) { + if (value.name.equalsIgnoreCase(type)) { + return value; + } + } + return null; + } + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + + script.append(keyword).append(" "); + + script.append(buildIndexName(tableIndex)).append(" "); + + script.append(buildIndexColumn(tableIndex)).append(" "); + + script.append(buildIndexComment(tableIndex)).append(" "); + + return script.toString(); + } + + private String buildIndexComment(TableIndex tableIndex) { + if(StringUtils.isBlank(tableIndex.getComment())){ + return ""; + }else { + return StringUtils.join("COMMENT '",tableIndex.getComment(),"'"); + } + + } + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + if(StringUtils.isNotBlank(column.getColumnName())) { + script.append("`").append(column.getColumnName()).append("`").append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + if(this.equals(PRIMARY_KEY)){ + return ""; + }else { + return "`"+tableIndex.getName()+"`"; + } + } + + public String buildModifyIndex(TableIndex tableIndex) { + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join("ADD ", buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (SqliteIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { + return StringUtils.join("DROP PRIMARY KEY"); + } + return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); + } + public static List getIndexTypes() { + return Arrays.asList(SqliteIndexTypeEnum.values()).stream().map(SqliteIndexTypeEnum::getIndexType).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 1c94e5b5d..1e77b13e5 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 @@ -6,12 +6,11 @@ import java.util.List; import java.util.stream.Collectors; +import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; +import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; @@ -226,6 +225,17 @@ public Table view(Connection connection, String databaseName, String schemaName, }); } + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return TableMeta.builder() + .columnTypes(SqlServerColumnTypeEnum.getTypes()) + .charsets(null) + .collations(null) + .indexTypes(SqlServerIndexTypeEnum.getIndexTypes()) + .build(); + } + + @Override public String getMetaDataName(String... names) { return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "[" + name + "]").collect(Collectors.joining(".")); diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java index 74e8f93ac..8547f6eaa 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerIndexTypeEnum.java @@ -1,10 +1,14 @@ package ai.chat2db.plugin.sqlserver.type; import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.lang3.StringUtils; +import java.util.Arrays; +import java.util.List; + public enum SqlServerIndexTypeEnum { PRIMARY_KEY("Primary", "PRIMARY KEY"), @@ -27,6 +31,16 @@ public enum SqlServerIndexTypeEnum { XML("XML", "XML INDEX"); + public IndexType getIndexType() { + return indexType; + } + + public void setIndexType(IndexType indexType) { + this.indexType = indexType; + } + + private IndexType indexType; + public String getName() { return name; @@ -44,6 +58,7 @@ public String getKeyword() { SqlServerIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; + this.indexType = new IndexType(name); } @@ -116,4 +131,7 @@ private String buildDropIndex(TableIndex tableIndex) { return script.toString(); } -} + + public static List getIndexTypes() { + return Arrays.asList(SqlServerIndexTypeEnum.values()).stream().map(SqlServerIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); + }} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexType.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexType.java new file mode 100644 index 000000000..a32c47212 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/IndexType.java @@ -0,0 +1,14 @@ +package ai.chat2db.spi.model; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class IndexType { + + /** + * + */ + private String typeName; +} 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 7531c7e78..82e9a5a95 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 @@ -16,4 +16,7 @@ public class TableMeta { private List collations; + + + private List indexTypes; } From d3c62fb73f8fab0afc347d2e4c1deaf5f8a6381c Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 19:44:11 +0800 Subject: [PATCH 0853/1069] fear:Cancel the execution of the sql request --- chat2db-client/.vscode/settings.json | 3 ++- .../src/blocks/SQLExecute/index.less | 23 ++++++++++++++++ .../src/blocks/SQLExecute/index.tsx | 27 +++++++++++++++---- .../src/components/SearchResult/index.tsx | 15 ++++++++++- chat2db-client/src/service/base.ts | 19 ++++++------- 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 759fad4f7..6f942c93b 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -80,7 +80,8 @@ "uuidv", "VARCHAR", "VIEWCOLUMN", - "wireframe" + "wireframe", + "yapi" ], "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" } diff --git a/chat2db-client/src/blocks/SQLExecute/index.less b/chat2db-client/src/blocks/SQLExecute/index.less index c20e0db75..52b2a3a3b 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.less +++ b/chat2db-client/src/blocks/SQLExecute/index.less @@ -17,4 +17,27 @@ border-top: 1px solid var(--color-border); height: 0px; flex: 1; + position: relative; +} + +.tableLoading { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + // height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + // background-color: var(--color-fill); + .stopExecuteSql { + cursor: pointer; + margin-top: 30px; + &:hover { + color: var(--color-primary); + } + } } diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 35adb8e49..e67feeeaf 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -13,6 +13,7 @@ import { IAIState } from '@/models/ai'; import sqlServer, { IExecuteSqlParams } from '@/service/sql'; import { v4 as uuidV4 } from 'uuid'; import { isNumber } from 'lodash'; +import { Spin } from 'antd'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { className?: string; @@ -44,10 +45,9 @@ const SQLExecute = memo((props) => { const [appendValue, setAppendValue] = useState(); const [resultData, setResultData] = useState([]); const [resultConfig, setResultConfig] = useState([]); - const { doubleClickTreeNodeData, curTableList, curWorkspaceParams } = workspaceModel; const [tableLoading, setTableLoading] = useState(false); - + const controllerRef = useRef(); useEffect(() => { if (!doubleClickTreeNodeData) { return; @@ -76,6 +76,7 @@ const SQLExecute = memo((props) => { */ const handleExecuteSQL = async (sql: string) => { setTableLoading(true); + setResultData([]); const executeSQLParams: IExecuteSqlParams = { sql, @@ -83,8 +84,12 @@ const SQLExecute = memo((props) => { ...data, }; + controllerRef.current = new AbortController(); // 获取当前SQL的查询结果 - let sqlResult = await sqlServer.executeSql(executeSQLParams); + let sqlResult = await sqlServer.executeSql(executeSQLParams, { + signal: controllerRef.current.signal, + }); + sqlResult = sqlResult.map((res) => ({ ...res, uuid: uuidV4(), @@ -153,6 +158,11 @@ const SQLExecute = memo((props) => { } }; + const stopExecuteSql = () => { + controllerRef.current && controllerRef.current.abort(); + setTableLoading(false); + }; + return (
    @@ -187,7 +197,14 @@ const SQLExecute = memo((props) => { />
    - { + {tableLoading ? ( +
    + +
    + 取消请求 +
    +
    + ) : ( ((props) => { resultConfig={resultConfig} manageResultDataList={resultData} /> - } + )}
    diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 909c48ff5..336d54720 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -6,6 +6,8 @@ import StateIndicator from '@/components/StateIndicator'; import { IManageResultData, IResultConfig } from '@/typings'; import TableBox from './TableBox'; import styles from './index.less'; +import i18n from '@/i18n'; +import EmptyImg from '@/assets/img/empty.svg'; interface IProps { className?: string; @@ -88,6 +90,15 @@ export default memo((props) => { }); }, [resultDataList]); + const renderEmpty = () => { + return ( +
    + +

    {i18n('common.text.noData')}

    +
    + ); + }; + return (
    {tabsList.length ? ( @@ -99,7 +110,9 @@ export default memo((props) => { activeKey={currentTab} items={tabsList} /> - ) : null} + ) : ( + renderEmpty() + )}
    ); }); diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index e18a3ae93..3237c7885 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,8 +1,5 @@ -import { extend, ResponseError } from 'umi-request'; -import { message, notification } from 'antd'; -import { getLang } from '@/utils/localStorage'; -import ErrorNotification from '@/components/MyNotification'; -const path = require('path'); +import { extend, ResponseError,type RequestOptionsInit } from 'umi-request'; +import { message } from 'antd'; export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; export interface IOptions { @@ -109,7 +106,7 @@ request.interceptors.request.use((url, options) => { }; }); -request.interceptors.response.use(async (response, options) => { +request.interceptors.response.use(async (response) => { const res = await response.clone().json(); if (__ENV__ === 'desktop') { const Chat2db = response.headers.get('Chat2db') || ''; @@ -117,7 +114,7 @@ request.interceptors.response.use(async (response, options) => { localStorage.setItem('Chat2db', Chat2db); } } - const { errorCode, codeMessage } = res; + const { errorCode } = res; if (errorCode === ErrorCode.NEED_LOGGED_IN) { const callback = window.location.hash.substr(1).split('?')[0]; window.location.href = '#/login' + (callback === '/login' ? '' : `?callback=${callback}`); @@ -130,8 +127,8 @@ export default function createRequest

    (url: string, options?: I const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside, isFullPath, dynamicUrl } = options || {}; // 是否需要mock - let _baseURL = (mock ? mockUrl : baseURL) || ''; - return function (params: P) { + const _baseURL = (mock ? mockUrl : baseURL) || ''; + return function (params: P, restParams?: RequestOptionsInit) { // 在url上按照定义规则拼接params const paramsInUrl: string[] = []; @@ -175,7 +172,7 @@ export default function createRequest

    (url: string, options?: I eventualUrl = params as string; } - request[method](eventualUrl, { [dataName]: params }) + request[method](eventualUrl, { [dataName]: params, ...restParams }) .then((res) => { if (!res) return; const { success, errorCode, errorMessage, errorDetail, solutionLink, data } = res; @@ -211,7 +208,7 @@ export default function createRequest

    (url: string, options?: I } // 简单的延时函数 -function delayTimeFn(callback: Function, time: number | true | undefined) { +function delayTimeFn(callback: () => void, time: number | true | undefined) { if (time) { const timer = setTimeout(() => { callback(); From b0818107b47175483b7ceac8308d7affbba919ce Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 20:49:16 +0800 Subject: [PATCH 0854/1069] feat: edit table index add order --- .../DatabaseTableEditor/ColumnList/index.tsx | 64 +-------------- .../DatabaseTableEditor/IncludeCol/index.tsx | 22 ++++++ .../DatabaseTableEditor/IndexList/index.tsx | 12 ++- .../src/blocks/DatabaseTableEditor/index.tsx | 78 ++++++++++++++++++- chat2db-client/src/constants/editTable.ts | 8 -- chat2db-client/src/i18n/en-us/editTable.ts | 1 + chat2db-client/src/i18n/zh-cn/editTable.ts | 1 + chat2db-client/src/typings/database.ts | 6 ++ 8 files changed, 113 insertions(+), 79 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 75560e56e..b4fd2e670 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -7,7 +7,6 @@ import { Table, InputNumber, Input, Form, Select, Checkbox } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { arrayMove, SortableContext, useSortable, verticalListSortingStrategy } from '@dnd-kit/sortable'; import { CSS } from '@dnd-kit/utilities'; -import sqlService from '@/service/sql'; import { Context } from '../index'; import { IColumnItemNew, IColumnTypes } from '@/typings'; import i18n from '@/i18n'; @@ -21,22 +20,11 @@ interface RowProps extends React.HTMLAttributes { interface IProps {} -interface IOption { - label: string; - value: string | number | null; -} - // 编辑配置 interface IEditingConfig extends IColumnTypes { editKey: string; } -// 列字段类型,select组件的options需要的数据结构 -interface IColumnTypesOption extends IColumnTypes { - label: string; - value: string | number | null; -} - // 本组件暴露给父组件的方法 export interface IColumnListRef { getColumnListInfo: () => IColumnItemNew[]; @@ -105,21 +93,12 @@ const createInitialData = () => { }; const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) => { - const { dataSourceId, databaseName, schemaName, tableDetails } = useContext(Context); + const { databaseSupportField, databaseName, schemaName, tableDetails } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingData, setEditingData] = useState(null); const [editingConfig, setEditingConfig] = useState(null); const tableRef = useRef(null); - const [databaseSupportField, setDatabaseSupportField] = useState<{ - columnTypes: IColumnTypesOption[]; - charsets: IOption[]; - collations: IOption[]; - }>({ - columnTypes: [], - charsets: [], - collations: [], - }); const isEditing = (record: IColumnItemNew) => record.key === editingData?.key; @@ -159,47 +138,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } }, [tableDetails]); - useEffect(() => { - // 获取数据库字段类型列表 - sqlService - .getDatabaseFieldTypeList({ - dataSourceId, - databaseName, - }) - .then((res) => { - const columnTypes = - res?.columnTypes?.map((i) => { - return { - ...i, - value: i.typeName, - label: i.typeName, - }; - }) || []; - - const charsets = - res?.charsets?.map((i) => { - return { - value: i.charsetName, - label: i.charsetName, - }; - }) || []; - - const collations = - res?.collations?.map((i) => { - return { - value: i.collationName, - label: i.collationName, - }; - }) || []; - - setDatabaseSupportField({ - columnTypes, - charsets, - collations, - }); - }); - }, []); - const columns = [ { key: 'sort', diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 3302097bb..66036425f 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -124,6 +124,27 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) ); }, }, + { + title: i18n('editTable.label.order'), + dataIndex: 'ascOrDesc', + render: (text: string, record: IIndexIncludeColumnItem) => { + const editable = isEditing(record); + return editable ? ( + + - {indexesTypeList.map((i) => ( - - {i} + {databaseSupportField?.indexTypes?.map((i) => ( + + {i.label} ))} diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index c4392cbce..cb00718bd 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -7,7 +7,7 @@ import ColumnList, { IColumnListRef } from './ColumnList'; import BaseInfo, { IBaseInfoRef } from './BaseInfo'; import sqlService, { IModifyTableSqlParams } from '@/service/sql'; import ExecuteSQL from '@/components/ExecuteSQL'; -import { IEditTableInfo, IWorkspaceTab } from '@/typings'; +import { IEditTableInfo, IWorkspaceTab, IColumnTypes } from '@/typings'; import { DatabaseTypeCode, WorkspaceTabType } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; @@ -33,10 +33,28 @@ interface IContext extends IProps { baseInfoRef: React.RefObject; columnListRef: React.RefObject; indexListRef: React.RefObject; + databaseSupportField: IDatabaseSupportField; } export const Context = createContext({} as any); +interface IOption { + label: string; + value: string | number | null; +} + +// 列字段类型,select组件的options需要的数据结构 +interface IColumnTypesOption extends IColumnTypes { + label: string; + value: string | number | null; +} +export interface IDatabaseSupportField { + columnTypes: IColumnTypesOption[]; + charsets: IOption[]; + collations: IOption[]; + indexTypes: IOption[]; +} + export default memo((props: IProps) => { const { databaseName, dataSourceId, tableName, schemaName, changeTabDetails, tabDetails, databaseType } = props; const [tableDetails, setTableDetails] = useState({} as any); @@ -69,6 +87,12 @@ export default memo((props: IProps) => { ]; }, []); const [currentTab, setCurrentTab] = useState(tabList[0]); + const [databaseSupportField, setDatabaseSupportField] = useState({ + columnTypes: [], + charsets: [], + collations: [], + indexTypes: [], + }); function changeTab(item: ITabItem) { setCurrentTab(item); @@ -78,8 +102,59 @@ export default memo((props: IProps) => { if (tableName) { getTableDetails({}); } + getDatabaseFieldTypeList(); }, []); + // 获取数据库字段类型列表 + const getDatabaseFieldTypeList = () => { + sqlService + .getDatabaseFieldTypeList({ + dataSourceId, + databaseName, + }) + .then((res) => { + const columnTypes = + res?.columnTypes?.map((i) => { + return { + ...i, + value: i.typeName, + label: i.typeName, + }; + }) || []; + + const charsets = + res?.charsets?.map((i) => { + return { + value: i.charsetName, + label: i.charsetName, + }; + }) || []; + + const collations = + res?.collations?.map((i) => { + return { + value: i.collationName, + label: i.collationName, + }; + }) || []; + + const indexTypes = + res?.indexTypes?.map((i) => { + return { + value: i.typeName, + label: i.typeName, + }; + }) || []; + + setDatabaseSupportField({ + columnTypes, + charsets, + collations, + indexTypes, + }); + }); + }; + const getTableDetails = ({ tableNameProps }: { tableNameProps?: string }) => { if (!tableName) return; const params = { @@ -150,6 +225,7 @@ export default memo((props: IProps) => { baseInfoRef, columnListRef, indexListRef, + databaseSupportField, }} >

    diff --git a/chat2db-client/src/constants/editTable.ts b/chat2db-client/src/constants/editTable.ts index 3db8bc639..51f2dc83c 100644 --- a/chat2db-client/src/constants/editTable.ts +++ b/chat2db-client/src/constants/editTable.ts @@ -1,11 +1,3 @@ -// 索引类型 -export enum IndexesType { - Primary = 'Primary', - Normal = 'Normal', - Unique = 'Unique', - Fulltext = 'Fulltext', - Spatial = 'Spatial', -} export enum EditColumnOperationType { // 新增 Add = 'ADD', diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts index 60d4686c9..91fb5cfec 100644 --- a/chat2db-client/src/i18n/en-us/editTable.ts +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -26,6 +26,7 @@ export default { 'editTable.label.autoIncrement': 'Auto increment', 'editTable.label.engine': 'Engine', 'editTable.label.incrementValue': 'Increment value', + 'editTable.label.order': 'Order', 'editTable.title.sqlPreview': 'SQL preview', 'editTable.button.addColumn': 'Add column', }; diff --git a/chat2db-client/src/i18n/zh-cn/editTable.ts b/chat2db-client/src/i18n/zh-cn/editTable.ts index 4ee2ad535..19e318705 100644 --- a/chat2db-client/src/i18n/zh-cn/editTable.ts +++ b/chat2db-client/src/i18n/zh-cn/editTable.ts @@ -27,6 +27,7 @@ export default { 'editTable.label.autoIncrement': '是否自增', 'editTable.label.engine': '引擎', 'editTable.label.incrementValue': '自增值', + 'editTable.label.order': '排序', 'editTable.title.sqlPreview': 'sql预览', 'editTable.button.addColumn': '添加列', }; diff --git a/chat2db-client/src/typings/database.ts b/chat2db-client/src/typings/database.ts index c33ed71a1..2c274163a 100644 --- a/chat2db-client/src/typings/database.ts +++ b/chat2db-client/src/typings/database.ts @@ -42,6 +42,7 @@ export interface IDatabaseSupportField { columnTypes: IColumnTypes[]; charsets: ICharset[]; collations: ICollation[]; + indexTypes: IIndexTypes[]; } /** 字段所对应的 字符集*/ @@ -55,6 +56,11 @@ export interface ICollation { collationName: string; } +/** 索引的类型*/ +export interface IIndexTypes { + typeName: string; +} + /** 不同数据库支持的列字段类型 以及支持调整的选项*/ export interface IColumnTypes { typeName: string; From 5c291871ffdd1f205292409e6a022f2ff77794f0 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 21:27:35 +0800 Subject: [PATCH 0855/1069] sql execution error message --- .../src/blocks/SQLExecute/index.less | 9 ++++++++ .../src/blocks/SQLExecute/index.tsx | 17 ++++++++++++-- .../src/components/ExecuteSQL/index.tsx | 3 +++ .../src/components/MyNotification/index.tsx | 22 +++++++++++-------- .../SearchResult/TableBox/index.less | 4 ++++ .../SearchResult/TableBox/index.tsx | 21 +++++++++--------- .../src/components/SearchResult/index.less | 9 -------- .../src/components/SearchResult/index.tsx | 15 +------------ chat2db-client/src/i18n/en-us/common.ts | 2 ++ chat2db-client/src/i18n/zh-cn/common.ts | 2 ++ chat2db-client/src/service/sql.ts | 2 +- 11 files changed, 60 insertions(+), 46 deletions(-) diff --git a/chat2db-client/src/blocks/SQLExecute/index.less b/chat2db-client/src/blocks/SQLExecute/index.less index 52b2a3a3b..a082b7261 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.less +++ b/chat2db-client/src/blocks/SQLExecute/index.less @@ -41,3 +41,12 @@ } } } + +.noData { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 12px; +} diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index e67feeeaf..538609877 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -15,6 +15,8 @@ import { v4 as uuidV4 } from 'uuid'; import { isNumber } from 'lodash'; import { Spin } from 'antd'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; +import i18n from '@/i18n'; +import EmptyImg from '@/assets/img/empty.svg'; interface IProps { className?: string; isActive: boolean; @@ -163,6 +165,15 @@ const SQLExecute = memo((props) => { setTableLoading(false); }; + const renderEmpty = () => { + return ( +
    + +

    {i18n('common.text.noData')}

    +
    + ); + }; + return (
    @@ -201,10 +212,10 @@ const SQLExecute = memo((props) => {
    - 取消请求 + {i18n('common.button.cancelRequest')}
    - ) : ( + ) : resultData?.length ? ( ((props) => { resultConfig={resultConfig} manageResultDataList={resultData} /> + ) : ( + renderEmpty() )}
    diff --git a/chat2db-client/src/components/ExecuteSQL/index.tsx b/chat2db-client/src/components/ExecuteSQL/index.tsx index 6dac0c33b..1fb1e1f09 100644 --- a/chat2db-client/src/components/ExecuteSQL/index.tsx +++ b/chat2db-client/src/components/ExecuteSQL/index.tsx @@ -12,6 +12,7 @@ import { DatabaseTypeCode } from '@/constants'; interface IProps { className?: string; initSql: string; + initError?: string; databaseType: DatabaseTypeCode; databaseName: string; dataSourceId: number; @@ -24,6 +25,7 @@ export default memo((props) => { const { className, initSql, + initError, databaseType, databaseName, dataSourceId, @@ -38,6 +40,7 @@ export default memo((props) => { useEffect(() => { setAppendValue(initSql); + setExecuteSqlResult(initError || null); }, []); const handleFormatSql = () => { diff --git a/chat2db-client/src/components/MyNotification/index.tsx b/chat2db-client/src/components/MyNotification/index.tsx index bd86e51be..13ac703e3 100644 --- a/chat2db-client/src/components/MyNotification/index.tsx +++ b/chat2db-client/src/components/MyNotification/index.tsx @@ -20,7 +20,7 @@ interface IProps { /** 请求的接口 */ requestUrl: string; /** 请求的参数 */ - requestParams: string; + requestParams?: string; } function MyNotification() { @@ -79,7 +79,8 @@ function MyNotification() { }, []); function renderModalTitle() { - return
    {`${props?.errorCode}:${props?.errorMessage} `}
    ; + const list = [props?.errorCode, props?.errorMessage]; + return
    {list.filter((t) => t).join(':')}
    ; } function copyError() { @@ -92,13 +93,16 @@ function MyNotification() { } function renderModalFooter() { - return ( -
    - - {i18n('common.button.copyError')} - {i18n('common.button.copyErrorTips')} -
    - ); + if (props?.requestParams) { + return ( +
    + + {i18n('common.button.copyError')} + {i18n('common.button.copyErrorTips')} +
    + ); + } + return false; } return ( diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 557c7278e..1a2a44c39 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -201,3 +201,7 @@ overflow: hidden; height: 60vh; } + +.errorDetail { + white-space: normal; +} diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index c1f5f1fc3..957365d9d 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { Button, Dropdown, Input, MenuProps, message, Modal, Space } from 'antd'; +import { Dropdown, Input, MenuProps, message, Modal, Space } from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; import styled from 'styled-components'; import classnames from 'classnames'; @@ -19,7 +19,7 @@ import MyPagination from '../Pagination'; import styles from './index.less'; import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; import { downloadFile } from '@/utils/common'; -import lodash from 'lodash'; +import lodash, { set } from 'lodash'; interface ITableProps { className?: string; @@ -79,6 +79,7 @@ export default function TableBox(props: ITableProps) { const [curOperationRowNo, setCurOperationRowNo] = useState(null); const [updateData, setUpdateData] = useState([]); const [updateDataSql, setUpdateDataSql] = useState(''); + const [initError, setInitError] = useState(''); const [viewUpdateDataSql, setViewUpdateDataSql] = useState(false); const tableBoxRef = React.useRef(null); @@ -467,15 +468,11 @@ export default function TableBox(props: ITableProps) { if (res.success) { message.success(i18n('common.text.successfulExecution')); setUpdateData([]); + // 在执行一遍sql,刷新数据?// TODO: } else { - // window._notificationApi({ - // requestUrl: eventualUrl, - // requestParams: JSON.stringify(params), - // errorCode, - // errorMessage, - // errorDetail, - // solutionLink, - // }); + setUpdateDataSql(res.originalSql); + setViewUpdateDataSql(true); + setInitError(res.message); } }); }; @@ -649,16 +646,18 @@ export default function TableBox(props: ITableProps) { { setViewUpdateDataSql(false); setUpdateDataSql(''); + setInitError(''); }} > ((props) => { }); }, [resultDataList]); - const renderEmpty = () => { - return ( -
    - -

    {i18n('common.text.noData')}

    -
    - ); - }; - return (
    - {tabsList.length ? ( + {tabsList.length && ( ((props) => { activeKey={currentTab} items={tabsList} /> - ) : ( - renderEmpty() )}
    ); diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 4af4fccaf..4f0127050 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -87,5 +87,7 @@ export default { 'common.text.action': 'Action', 'common.button.add': 'Add', 'common.text.errorMessage': 'Error Message', + 'common.button.cancelRequest': 'Cancel Request', + 'common.button.executionError': 'Execution Error', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 88948ddd8..c340dbc0f 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -85,5 +85,7 @@ export default { 'common.text.action': '操作', 'common.button.add': '添加', 'common.text.errorMessage': '错误信息', + 'common.button.cancelRequest': '取消请求', + 'common.button.executionError': '执行错误', }; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 9545b7363..2412f79e8 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -209,7 +209,7 @@ export interface IModifyTableSqlParams { const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); /** 执行编辑表的sql, 专为编辑表而生 */ -const executeDDL = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); +const executeDDL = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); /** 获取修改表数据的接口 */ const getExecuteUpdateSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); From 184933937748d534fd7b6e0880d592985b6a69e7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 9 Oct 2023 21:34:55 +0800 Subject: [PATCH 0856/1069] style --- .../src/components/SearchResult/TableBox/index.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 957365d9d..9bab6f0e7 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -227,6 +227,12 @@ export default function TableBox(props: ITableProps) { const tableCellStyle = (value, colIndex, rowNo) => { // 单元格的基础样式 const styleList = [styles.tableItem]; + // 如果当前单元格所在的行被选中了,则需要把单元格的背景色设置为透明 + console.log(curOperationRowNo, rowNo); + if (curOperationRowNo === rowNo) { + return classnames(...styleList); + } + // 编辑过的单元格的样式 let oldValue = ''; dataList.forEach((item) => { @@ -298,7 +304,7 @@ export default function TableBox(props: ITableProps) { features: { sortable: isNumber ? compareStrings : true }, }; }); - }, [headerList, editingCell, editingData]); + }, [headerList, editingCell, editingData, curOperationRowNo]); useEffect(() => { if (!columns?.length) { From 2dc259a94c4b6d329e641589b38276159f5b1224 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 9 Oct 2023 21:58:00 +0800 Subject: [PATCH 0857/1069] Support sqlserver table schema edit --- .../chat2db/plugin/mysql/MysqlMetaData.java | 2 +- .../chat2db/plugin/oracle/OracleMetaData.java | 2 +- .../chat2db/plugin/sqlite/SqliteMetaData.java | 8 +- .../plugin/sqlite/builder/SqliteBuilder.java | 43 +---- .../sqlite/type/SqliteColumnTypeEnum.java | 42 ++--- .../sqlite/type/SqliteIndexTypeEnum.java | 11 +- .../plugin/sqlserver/SqlServerMetaData.java | 177 ++++++++++++------ .../type/SqlServerColumnTypeEnum.java | 5 + .../chat2db/spi/jdbc/DefaultMetaService.java | 2 +- 9 files changed, 163 insertions(+), 129 deletions(-) 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 80da4803b..398858f22 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 @@ -274,6 +274,6 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "`" + name + "`").collect(Collectors.joining(".")); } } 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 74d479291..acf8552be 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 @@ -285,6 +285,6 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } } 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 df1ce6a3f..401137ae2 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 @@ -6,10 +6,12 @@ import java.util.List; import java.util.stream.Collectors; +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.SqliteIndexTypeEnum; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Schema; @@ -43,6 +45,10 @@ public List schemas(Connection connection,String databaseName) { return Lists.newArrayList(); } + @Override + public SqlBuilder getSqlBuilder() { + return new SqliteBuilder(); + } @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() @@ -56,6 +62,6 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "\"" + name + "\"").collect(Collectors.joining(".")); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java index 8b204452f..379651fdf 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java @@ -14,7 +14,7 @@ public class SqliteBuilder implements SqlBuilder { public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); script.append("CREATE TABLE "); - script.append("\"").append(table.getName()).append("\"").append(" (").append("\n"); + script.append("\"").append(table.getDatabaseName()).append("\".\"").append(table.getName()).append("\"").append(" (").append("\n"); // append column for (TableColumn column : table.getColumnList()) { @@ -37,30 +37,6 @@ public String buildCreateTableSql(Table table) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append("\n)"); - - if (StringUtils.isNotBlank(table.getEngine())) { - script.append(" ENGINE=").append(table.getEngine()); - } - - if (StringUtils.isNotBlank(table.getCharset())) { - script.append(" DEFAULT CHARACTER SET=").append(table.getCharset()); - } - - if (StringUtils.isNotBlank(table.getCollate())) { - script.append(" COLLATE=").append(table.getCollate()); - } - - if (table.getIncrementValue() != null) { - script.append(" AUTO_INCREMENT=").append(table.getIncrementValue()); - } - - if (StringUtils.isNotBlank(table.getComment())) { - script.append(" COMMENT='").append(table.getComment()).append("'"); - } - - if (StringUtils.isNotBlank(table.getPartition())) { - script.append(" \n").append(table.getPartition()); - } script.append(";"); return script.toString(); @@ -69,30 +45,27 @@ public String buildCreateTableSql(Table table) { @Override public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); - script.append("ALTER TABLE ").append("\"").append(oldTable.getName()).append("\"").append("\n"); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { - script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(",\n"); - } - if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { - script.append("\t").append("COMMENT=").append("'").append(newTable.getComment()).append("'").append(",\n"); - } - if (oldTable.getIncrementValue() != newTable.getIncrementValue()) { - script.append("\t").append("AUTO_INCREMENT=").append(newTable.getIncrementValue()).append(",\n"); + script.append("ALTER TABLE ").append("\"").append(oldTable.getDatabaseName()).append("\".\"").append(oldTable.getName()).append("\"").append("\n"); + script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } + // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())&& StringUtils.isNotBlank(tableColumn.getName())){ + script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(tableColumn.getColumnType()); - script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); } } // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { + // script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); - script.append("\t").append(sqliteIndexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); + script.append("\t").append(sqliteIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java index a59e59623..5aded889d 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteColumnTypeEnum.java @@ -14,14 +14,14 @@ public enum SqliteColumnTypeEnum implements ColumnBuilder { - INTEGER("INTEGER", true, false, true, false, false, true, true, false, false, false), + INTEGER("INTEGER", true, false, true, false, false, true, false, false, false, false), - REAL("REAL", true, false, true, false, false, true, true, false, false, false), + REAL("REAL", true, false, true, false, false, true, false, false, false, false), - BLOB("BLOB", true, false, true, false, false, true, true, false, false, false), + BLOB("BLOB", true, false, true, false, false, true, false, false, false, false), - TEXT("TEXT", true, false, true, false, false, true, true, false, false, false), + TEXT("TEXT", true, false, true, false, false, true, false, false, false, false), ; private ColumnType columnType; @@ -56,7 +56,7 @@ public String buildCreateColumnSql(TableColumn column) { } StringBuilder script = new StringBuilder(); - script.append("`").append(column.getName()).append("`").append(" "); + script.append("\"").append(column.getName()).append("\"").append(" "); script.append(buildDataType(column, type)).append(" "); @@ -72,7 +72,7 @@ public String buildCreateColumnSql(TableColumn column) { script.append(buildAutoIncrement(column, type)).append(" "); - script.append(buildComment(column, type)).append(" "); +// script.append(buildComment(column, type)).append(" "); return script.toString(); } @@ -94,19 +94,19 @@ private String buildCollation(TableColumn column, SqliteColumnTypeEnum type) { @Override public String buildModifyColumn(TableColumn tableColumn) { - if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { - return StringUtils.join("DROP COLUMN `", tableColumn.getName() + "`"); - } +// if (EditStatus.DELETE.name().equals(tableColumn.getEditStatus())) { +// return StringUtils.join("DROP COLUMN \"", tableColumn.getName() + "\""); +// } if (EditStatus.ADD.name().equals(tableColumn.getEditStatus())) { - return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(tableColumn)); - } - if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { - if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { - return StringUtils.join("CHANGE COLUMN `", tableColumn.getOldName(), "` ", buildCreateColumnSql(tableColumn)); - } else { - return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); - } - } + return StringUtils.join("ADD ", buildCreateColumnSql(tableColumn)); + } +// if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { +// if (!StringUtils.equalsIgnoreCase(tableColumn.getOldName(), tableColumn.getName())) { +// return StringUtils.join("CHANGE COLUMN \"", tableColumn.getOldName(), "\" ", buildCreateColumnSql(tableColumn)); +// } else { +// return StringUtils.join("MODIFY COLUMN ", buildCreateColumnSql(tableColumn)); +// } +// } return ""; } @@ -120,12 +120,6 @@ private String buildAutoIncrement(TableColumn column, SqliteColumnTypeEnum type) return ""; } - private String buildComment(TableColumn column, SqliteColumnTypeEnum type) { - if (!type.columnType.isSupportComments() || StringUtils.isEmpty(column.getComment())) { - return ""; - } - return StringUtils.join("COMMENT '", column.getComment(), "'"); - } private String buildExt(TableColumn column, SqliteColumnTypeEnum type) { if (!type.columnType.isSupportExtent() || StringUtils.isEmpty(column.getExtent())) { diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java index a33acd36b..ee5c90364 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/type/SqliteIndexTypeEnum.java @@ -63,11 +63,10 @@ public String buildIndexScript(TableIndex tableIndex) { script.append(keyword).append(" "); - script.append(buildIndexName(tableIndex)).append(" "); + script.append(buildIndexName(tableIndex)).append(" ON ").append(tableIndex.getTableName()).append(" "); script.append(buildIndexColumn(tableIndex)).append(" "); - script.append(buildIndexComment(tableIndex)).append(" "); return script.toString(); } @@ -86,7 +85,7 @@ private String buildIndexColumn(TableIndex tableIndex) { script.append("("); for (TableIndexColumn column : tableIndex.getColumnList()) { if(StringUtils.isNotBlank(column.getColumnName())) { - script.append("`").append(column.getColumnName()).append("`").append(","); + script.append("\"").append(column.getColumnName()).append("\"").append(","); } } script.deleteCharAt(script.length() - 1); @@ -98,7 +97,7 @@ private String buildIndexName(TableIndex tableIndex) { if(this.equals(PRIMARY_KEY)){ return ""; }else { - return "`"+tableIndex.getName()+"`"; + return "\""+tableIndex.getName()+"\""; } } @@ -110,7 +109,7 @@ public String buildModifyIndex(TableIndex tableIndex) { return StringUtils.join(buildDropIndex(tableIndex),",\n", "ADD ", buildIndexScript(tableIndex)); } if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { - return StringUtils.join("ADD ", buildIndexScript(tableIndex)); + return StringUtils.join("CREATE ", buildIndexScript(tableIndex)); } return ""; } @@ -119,7 +118,7 @@ private String buildDropIndex(TableIndex tableIndex) { if (SqliteIndexTypeEnum.PRIMARY_KEY.getName().equals(tableIndex.getType())) { return StringUtils.join("DROP PRIMARY KEY"); } - return StringUtils.join("DROP INDEX `", tableIndex.getOldName(),"`"); + return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(),"\""); } public static List getIndexTypes() { return Arrays.asList(SqliteIndexTypeEnum.values()).stream().map(SqliteIndexTypeEnum::getIndexType).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 1e77b13e5..d89361040 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 @@ -6,9 +6,11 @@ import java.util.List; import java.util.stream.Collectors; +import ai.chat2db.plugin.sqlserver.builder.SqlServerSqlBuilder; import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; @@ -17,45 +19,45 @@ public class SqlServerMetaData extends DefaultMetaService implements MetaData { private String functionSQL - = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" - + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " - + "NVARCHAR(MAX) = ''; DECLARE @ColumnDescriptions NVARCHAR(MAX) = N''; SELECT @CreateTableScript = CONCAT( " - + "'CREATE TABLE [', s.name, '].[' , t.name, '] (', STUFF( ( SELECT ', [' + c.name + '] ' + tp.name + CASE " - + "WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + IIF(c.max_length = -1, 'MAX', CAST(c" - + ".max_length AS NVARCHAR(10))) + ')' WHEN tp.name IN ('decimal', 'numeric') THEN '(' + CAST(c.precision AS " - + "NVARCHAR(10)) + ', ' + CAST(c.scale AS NVARCHAR(10)) + ')' ELSE '' END + ' ' + CASE WHEN c.is_nullable = 1" - + " THEN 'NULL' ELSE 'NOT NULL' END FROM sys.columns c JOIN sys.types tp ON c.user_type_id = tp.user_type_id " - + "WHERE c.object_id = t.object_id FOR XML PATH(''), TYPE ).value('/', 'nvarchar(max)'), 1, 1, ''), ');' ) " - + "FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name = @table_name AND s.name = " - + "@schema_name; SELECT @IndexScripts = @IndexScripts + 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' " - + "ELSE '' END + i.type_desc + ' INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' + STUFF( ( " - + "SELECT ', [' + c.name + ']' + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys" - + ".index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic" - + ".object_id = i.object_id AND ic.index_id = i.index_id ORDER BY ic.key_ordinal FOR XML PATH('') ), 1, 1, " - + "'') + ')' + CASE WHEN i.has_filter = 1 THEN ' WHERE ' + i.filter_definition ELSE '' END + ';' + CHAR(13) +" - + " CHAR(10) FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t" - + ".schema_id = s.schema_id WHERE i.type > 0 AND t.name = @table_name AND s.name " - + "= @schema_name; SELECT @ColumnDescriptions += 'EXEC sp_addextendedproperty @name=N''MS_Description'', " - + "@value=N''' + CAST(p.value AS NVARCHAR(MAX)) + ''', @level0type=N''SCHEMA'', @level0name=N''' + " - + "@schema_name + ''', @level1type=N''TABLE'', @level1name=N''' + @table_name + ''', @level2type=N''COLUMN''," - + " @level2name=N''' + c.name + ''';' + CHAR(13) + CHAR(10) FROM sys.extended_properties p JOIN sys.columns c" - + " ON p.major_id = c.object_id AND p.minor_id = c.column_id JOIN sys.tables t ON c.object_id = t.object_id " - + "JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.class = 1 AND t.name = @table_name AND s.name = " - + "@schema_name; SET @CreateTableScript = @CreateTableScript + CHAR(13) + CHAR(10) + @IndexScripts + CHAR(13)" - + " + CHAR(10)+ @ColumnDescriptions+ CHAR(10); RETURN @CreateTableScript; END"; + = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" + + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " + + "NVARCHAR(MAX) = ''; DECLARE @ColumnDescriptions NVARCHAR(MAX) = N''; SELECT @CreateTableScript = CONCAT( " + + "'CREATE TABLE [', s.name, '].[' , t.name, '] (', STUFF( ( SELECT ', [' + c.name + '] ' + tp.name + CASE " + + "WHEN tp.name IN ('varchar', 'nvarchar', 'char', 'nchar') THEN '(' + IIF(c.max_length = -1, 'MAX', CAST(c" + + ".max_length AS NVARCHAR(10))) + ')' WHEN tp.name IN ('decimal', 'numeric') THEN '(' + CAST(c.precision AS " + + "NVARCHAR(10)) + ', ' + CAST(c.scale AS NVARCHAR(10)) + ')' ELSE '' END + ' ' + CASE WHEN c.is_nullable = 1" + + " THEN 'NULL' ELSE 'NOT NULL' END FROM sys.columns c JOIN sys.types tp ON c.user_type_id = tp.user_type_id " + + "WHERE c.object_id = t.object_id FOR XML PATH(''), TYPE ).value('/', 'nvarchar(max)'), 1, 1, ''), ');' ) " + + "FROM sys.tables t JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE t.name = @table_name AND s.name = " + + "@schema_name; SELECT @IndexScripts = @IndexScripts + 'CREATE ' + CASE WHEN i.is_unique = 1 THEN 'UNIQUE ' " + + "ELSE '' END + i.type_desc + ' INDEX [' + i.name + '] ON [' + s.name + '].[' + t.name + '] (' + STUFF( ( " + + "SELECT ', [' + c.name + ']' + CASE WHEN ic.is_descending_key = 1 THEN ' DESC' ELSE ' ASC' END FROM sys" + + ".index_columns ic JOIN sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id WHERE ic" + + ".object_id = i.object_id AND ic.index_id = i.index_id ORDER BY ic.key_ordinal FOR XML PATH('') ), 1, 1, " + + "'') + ')' + CASE WHEN i.has_filter = 1 THEN ' WHERE ' + i.filter_definition ELSE '' END + ';' + CHAR(13) +" + + " CHAR(10) FROM sys.indexes i JOIN sys.tables t ON i.object_id = t.object_id JOIN sys.schemas s ON t" + + ".schema_id = s.schema_id WHERE i.type > 0 AND t.name = @table_name AND s.name " + + "= @schema_name; SELECT @ColumnDescriptions += 'EXEC sp_addextendedproperty @name=N''MS_Description'', " + + "@value=N''' + CAST(p.value AS NVARCHAR(MAX)) + ''', @level0type=N''SCHEMA'', @level0name=N''' + " + + "@schema_name + ''', @level1type=N''TABLE'', @level1name=N''' + @table_name + ''', @level2type=N''COLUMN''," + + " @level2name=N''' + c.name + ''';' + CHAR(13) + CHAR(10) FROM sys.extended_properties p JOIN sys.columns c" + + " ON p.major_id = c.object_id AND p.minor_id = c.column_id JOIN sys.tables t ON c.object_id = t.object_id " + + "JOIN sys.schemas s ON t.schema_id = s.schema_id WHERE p.class = 1 AND t.name = @table_name AND s.name = " + + "@schema_name; SET @CreateTableScript = @CreateTableScript + CHAR(13) + CHAR(10) + @IndexScripts + CHAR(13)" + + " + CHAR(10)+ @ColumnDescriptions+ CHAR(10); RETURN @CreateTableScript; END"; @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { System.out.println(functionSQL); SQLExecutor.getInstance().executeSql(connection, functionSQL.replace("tableSchema", schemaName), - resultSet -> null); + resultSet -> null); } catch (Exception e) { //log.error("创建函数失败", e); } String ddlSql = "SELECT " + schemaName + ".ufn_GetCreateTableScript('" + schemaName + "', '" + tableName - + "') AS sql"; + + "') AS sql"; return SQLExecutor.getInstance().execute(connection, ddlSql, resultSet -> { if (resultSet.next()) { return resultSet.getString("sql"); @@ -64,9 +66,65 @@ public String tableDDL(Connection connection, String databaseName, String schema }); } + private static String SELECT_TABLES_SQL = "SELECT t.name AS TableName, mm.value as comment FROM sys.tables t LEFT JOIN(SELECT * from sys.extended_properties ep where ep.minor_id = 0 AND ep.name = 'MS_Description') mm ON t.object_id = mm.major_id WHERE t.schema_id= SCHEMA_ID('%S') "; + + @Override + public List
    tables(Connection connection, String databaseName, String schemaName, String tableName) { + List
    tables = new ArrayList<>(); + String sql = String.format(SELECT_TABLES_SQL, schemaName); + if (StringUtils.isNotBlank(tableName)) { + sql += " AND t.name = '" + tableName + "'"; + } + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(resultSet.getString("TableName")); + table.setComment(resultSet.getString("comment")); + tables.add(table); + } + return tables; + }); + } + + private static final String SELECT_TABLE_COLUMNS = "SELECT c.name as COLUMN_NAME , c.is_nullable as IS_NULLABLE ,c.column_id as ORDINAL_POSITION,c.max_length as COLUMN_SIZE, c.scale as NUMERIC_SCALE, c.collation_name as COLLATION_NAME, ty.name as DATA_TYPE ,t.name, def.definition as COLUMN_DEFAULT, ep.value as COLUMN_COMMENT from sys.columns c LEFT JOIN sys.tables t on c.object_id=t.object_id LEFT JOIN sys.types ty ON c.user_type_id = ty.user_type_id LEFT JOIN sys.default_constraints def ON c.default_object_id = def.object_id LEFT JOIN sys.extended_properties ep ON t.object_id = ep.major_id AND c.column_id = ep.minor_id WHERE t.name ='%s' and t.schema_id=SCHEMA_ID('%s');"; + + @Override + public List columns(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = String.format(SELECT_TABLE_COLUMNS, tableName, schemaName); + List tableColumns = new ArrayList<>(); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + TableColumn column = new TableColumn(); + column.setDatabaseName(databaseName); + column.setTableName(tableName); + column.setSchemaName(schemaName); + column.setOldName(resultSet.getString("COLUMN_NAME")); + column.setName(resultSet.getString("COLUMN_NAME")); + //column.setColumnType(resultSet.getString("COLUMN_TYPE")); + column.setColumnType(resultSet.getString("DATA_TYPE").toUpperCase()); + //column.setDataType(resultSet.getInt("DATA_TYPE")); + column.setDefaultValue(resultSet.getString("COLUMN_DEFAULT")); + //column.setAutoIncrement(resultSet.getString("EXTRA").contains("auto_increment")); + column.setComment(resultSet.getString("COLUMN_COMMENT")); + // column.setPrimaryKey("PRI".equalsIgnoreCase(resultSet.getString("COLUMN_KEY"))); + column.setNullable(resultSet.getInt("IS_NULLABLE")); + column.setOrdinalPosition(resultSet.getInt("ORDINAL_POSITION")); + column.setDecimalDigits(resultSet.getInt("NUMERIC_SCALE")); + column.setCharSetName(resultSet.getString("CHARACTER_SET_NAME")); + column.setCollationName(resultSet.getString("COLLATION_NAME")); + column.setColumnSize(resultSet.getInt("CHARACTER_MAXIMUM_LENGTH")); + //setColumnSize(column, resultSet.getString("COLUMN_TYPE")); + tableColumns.add(column); + } + return tableColumns; + }); + } + private static String ROUTINES_SQL - = "SELECT type_desc, OBJECT_NAME(object_id) AS FunctionName, OBJECT_DEFINITION(object_id) AS " - + "definition FROM sys.objects WHERE type_desc IN(%s) and name = '%s' ;"; + = "SELECT type_desc, OBJECT_NAME(object_id) AS FunctionName, OBJECT_DEFINITION(object_id) AS " + + "definition FROM sys.objects WHERE type_desc IN(%s) and name = '%s' ;"; private static String OBJECT_SQL @@ -74,7 +132,7 @@ public String tableDDL(Connection connection, String databaseName, String schema @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, - String functionName) { + String functionName) { String sql = String.format(ROUTINES_SQL, "'SQL_SCALAR_FUNCTION', 'SQL_TABLE_VALUED_FUNCTION'", functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -92,17 +150,13 @@ public Function function(Connection connection, @NotEmpty String databaseName, S @Override public List functions(Connection connection, String databaseName, String schemaName) { List functions = new ArrayList<>(); -// List functions = SQLExecutor.getInstance().functions(connection, databaseName, schemaName); -// return functions.stream().map(function -> removeVersion(function)).collect(Collectors.toList()); - String sql = String.format(OBJECT_SQL,"FN", schemaName); + String sql = String.format(OBJECT_SQL, "FN", schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Function function = new Function(); function.setDatabaseName(databaseName); function.setSchemaName(schemaName); - if (resultSet.next()) { - function.setFunctionName(resultSet.getString("name")); - } + function.setFunctionName(resultSet.getString("name")); functions.add(function); } return functions; @@ -122,15 +176,13 @@ private Function removeVersion(Function function) { @Override public List procedures(Connection connection, String databaseName, String schemaName) { List procedures = new ArrayList<>(); - String sql = String.format(OBJECT_SQL,"P", schemaName); + String sql = String.format(OBJECT_SQL, "P", schemaName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { while (resultSet.next()) { Procedure procedure = new Procedure(); procedure.setDatabaseName(databaseName); procedure.setSchemaName(schemaName); - if (resultSet.next()) { - procedure.setProcedureName(resultSet.getString("name")); - } + procedure.setProcedureName(resultSet.getString("name")); procedures.add(procedure); } return procedures; @@ -148,14 +200,14 @@ private Procedure removeVersion(Procedure procedure) { } private static String TRIGGER_SQL - = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " - + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " - + "WHERE xtype = 'TR' and name = '%s';"; + = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " + + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " + + "WHERE xtype = 'TR' and name = '%s';"; private static String TRIGGER_SQL_LIST - = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " - + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " - + "WHERE xtype = 'TR' "; + = "SELECT OBJECT_NAME(parent_obj) AS TableName, name AS triggerName, OBJECT_DEFINITION(id) AS " + + "triggerDefinition, CASE WHEN status & 1 = 1 THEN 'Enabled' ELSE 'Disabled' END AS Status FROM sysobjects " + + "WHERE xtype = 'TR' "; @Override public List triggers(Connection connection, String databaseName, String schemaName) { @@ -174,7 +226,7 @@ public List triggers(Connection connection, String databaseName, String @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, - String triggerName) { + String triggerName) { String sql = String.format(TRIGGER_SQL, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -191,24 +243,24 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, - String procedureName) { + String procedureName) { String sql = String.format(ROUTINES_SQL, "'SQL_STORED_PROCEDURE'", procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Procedure procedure = new Procedure(); - procedure.setDatabaseName(databaseName); - procedure.setSchemaName(schemaName); - procedure.setProcedureName(procedureName); - if (resultSet.next()) { - procedure.setProcedureBody(resultSet.getString("definition")); + Procedure procedure = new Procedure(); + procedure.setDatabaseName(databaseName); + procedure.setSchemaName(schemaName); + procedure.setProcedureName(procedureName); + if (resultSet.next()) { + procedure.setProcedureBody(resultSet.getString("definition")); + } + return procedure; } - return procedure; - } ); } private static String VIEW_SQL - = "SELECT TABLE_SCHEMA, TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' " - + "AND TABLE_NAME = '%s';"; + = "SELECT TABLE_SCHEMA, TABLE_NAME, VIEW_DEFINITION FROM INFORMATION_SCHEMA.VIEWS WHERE TABLE_SCHEMA = '%s' " + + "AND TABLE_NAME = '%s';"; @Override public Table view(Connection connection, String databaseName, String schemaName, String viewName) { @@ -225,6 +277,11 @@ public Table view(Connection connection, String databaseName, String schemaName, }); } + @Override + public SqlBuilder getSqlBuilder() { + return new SqlServerSqlBuilder(); + } + @Override public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { return TableMeta.builder() @@ -238,6 +295,6 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).map(name -> "[" + name + "]").collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> "[" + name + "]").collect(Collectors.joining(".")); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java index 9aecd681b..55d5ba16b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/type/SqlServerColumnTypeEnum.java @@ -285,6 +285,11 @@ public String buildModifyColumn(TableColumn tableColumn) { StringBuilder script = new StringBuilder(); script.append("ALTER TABLE ").append("[").append(tableColumn.getSchemaName()).append("].[").append(tableColumn.getTableName()).append("]"); script.append(" ").append("ADD ").append(buildCreateColumnSql(tableColumn)).append(" \ngo\n"); + + + if (StringUtils.isNotBlank(tableColumn.getComment())) { + script.append("\n").append(buildModifyColumnComment(tableColumn)); + } return script.toString(); } if (EditStatus.MODIFY.name().equals(tableColumn.getEditStatus())) { diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java index ea30a1574..6ec6b09d0 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultMetaService.java @@ -110,7 +110,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab @Override public String getMetaDataName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isBlank(name)).collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).collect(Collectors.joining(".")); } From 908b025199fffd61d39f881175f887b14385f288 Mon Sep 17 00:00:00 2001 From: heshujun <1026745647@qq.com> Date: Mon, 9 Oct 2023 23:02:34 +0800 Subject: [PATCH 0858/1069] support pgsql create/update --- .../plugin/postgresql/PostgreSQLDBManage.java | 8 + .../plugin/postgresql/PostgreSQLMetaData.java | 268 +- .../builder/PostgreSQLSqlBuilder.java | 160 ++ .../type/PostgreSQLCharsetEnum.java | 68 + .../type/PostgreSQLCollationEnum.java | 2272 +++++++++++++++++ .../type/PostgreSQLColumnTypeEnum.java | 233 ++ .../type/PostgreSQLIndexTypeEnum.java | 165 ++ .../java/ai/chat2db/spi/model/TableIndex.java | 27 + 8 files changed, 3126 insertions(+), 75 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCharsetEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCollationEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java create mode 100644 chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java index aca56e8f2..ff3ffdcb1 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java @@ -5,6 +5,7 @@ import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; import ai.chat2db.spi.sql.ConnectInfo; +import ai.chat2db.spi.sql.SQLExecutor; public class PostgreSQLDBManage extends DefaultDBManage implements DBManage { @Override @@ -49,4 +50,11 @@ public String replaceDatabaseInJdbcUrl(String url, String newDatabase) { return newUrl; } + + @Override + public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { + String sql = "DROP TABLE "+ tableName; + SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); + } + } 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 257372c0b..e5595b629 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 @@ -1,41 +1,34 @@ package ai.chat2db.plugin.postgresql; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +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.server.tools.common.util.EasyCollectionUtils; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Database; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Schema; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import com.alibaba.druid.sql.visitor.functions.If; +import com.alibaba.fastjson2.JSON; +import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import static ai.chat2db.plugin.postgresql.consts.SQLConst.FUNCTION_SQL; public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { - @Override - public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { - SQLExecutor.getInstance().executeSql(connection, FUNCTION_SQL.replaceFirst("tableSchema", schemaName), - resultSet -> null); - String ddlSql = "select showcreatetable('" + schemaName + "','" + tableName + "') as sql"; - return SQLExecutor.getInstance().executeSql(connection, ddlSql, resultSet -> { - try { - if (resultSet.next()) { - return resultSet.getString("sql"); - } - } catch (SQLException e) { - throw new RuntimeException(e); - } - return null; - }); - } + private static final String SELECT_KEY_INDEX = "SELECT ccu.table_schema AS Foreign_schema_name, ccu.table_name AS Foreign_table_name, ccu.column_name AS Foreign_column_name, constraint_type AS Constraint_type, tc.CONSTRAINT_NAME AS Key_name, tc.TABLE_NAME, kcu.Column_name, tc.is_deferrable, tc.initially_deferred FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.TABLE_SCHEMA = '%s' AND tc.TABLE_NAME = '%s';"; @Override public List databases(Connection connection) { @@ -58,31 +51,74 @@ public List databases(Connection connection) { }); } + private static final String SELECT_TABLE_INDEX = "SELECT tmp.INDISPRIMARY AS Index_primary, tmp.TABLE_SCHEM, tmp.TABLE_NAME, tmp.NON_UNIQUE, tmp.INDEX_QUALIFIER, tmp.INDEX_NAME AS Key_name, tmp.indisclustered, tmp.ORDINAL_POSITION AS Seq_in_index, TRIM ( BOTH '\"' FROM pg_get_indexdef ( tmp.CI_OID, tmp.ORDINAL_POSITION, FALSE ) ) AS Column_name,CASE tmp.AM_NAME WHEN 'btree' THEN CASE tmp.I_INDOPTION [ tmp.ORDINAL_POSITION - 1 ] & 1 :: SMALLINT WHEN 1 THEN 'D' ELSE'A' END ELSE NULL END AS Collation, tmp.CARDINALITY, tmp.PAGES, tmp.FILTER_CONDITION , tmp.AM_NAME AS Index_method, tmp.DESCRIPTION AS Index_comment FROM ( SELECT n.nspname AS TABLE_SCHEM, ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME,i.INDISPRIMARY , i.indisclustered , ( information_schema._pg_expandarray ( i.indkey ) ).n AS ORDINAL_POSITION, ci.reltuples AS CARDINALITY, ci.relpages AS PAGES, pg_get_expr ( i.indpred, i.indrelid ) AS FILTER_CONDITION, ci.OID AS CI_OID, i.indoption AS I_INDOPTION, am.amname AS AM_NAME , d.description FROM pg_class ct JOIN pg_namespace n ON ( ct.relnamespace = n.OID ) JOIN pg_index i ON ( ct.OID = i.indrelid ) JOIN pg_class ci ON ( ci.OID = i.indexrelid ) JOIN pg_am am ON ( ci.relam = am.OID ) left outer join pg_description d on i.indexrelid = d.objoid WHERE n.nspname = '%s' AND ct.relname = '%s' ) AS tmp ;"; + private static String ROUTINES_SQL + = " SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p " + + "where p.prokind = '%s' and p.proname='%s';"; + private static String TRIGGER_SQL + = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " + + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" + + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s' AND t.tgname ='%s';"; + private static String TRIGGER_SQL_LIST + = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " + + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" + + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s';"; + private static String VIEW_SQL + = "SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname = '%s' AND viewname = '%s';"; @Override - public List schemas(Connection connection, String databaseName) { - return SQLExecutor.getInstance().execute(connection, - "SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> { - List databases = new ArrayList<>(); - while (resultSet.next()) { - Schema schema = new Schema(); - String name = resultSet.getString("schema_name"); - String catalogName = resultSet.getString("catalog_name"); - schema.setName(name); - schema.setDatabaseName(catalogName); - databases.add(schema); + public List triggers(Connection connection, String databaseName, String schemaName) { + List triggers = new ArrayList<>(); + String sql = String.format(TRIGGER_SQL_LIST, schemaName); + return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { + while (resultSet.next()) { + Trigger trigger = new Trigger(); + trigger.setTriggerName(resultSet.getString("trigger_name")); + trigger.setSchemaName(schemaName); + trigger.setDatabaseName(databaseName); + triggers.add(trigger); + } + return triggers; + }); + } + + @Override + public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { + SQLExecutor.getInstance().executeSql(connection, FUNCTION_SQL.replaceFirst("tableSchema", schemaName), + resultSet -> null); + String ddlSql = "select showcreatetable('" + schemaName + "','" + tableName + "') as sql"; + return SQLExecutor.getInstance().executeSql(connection, ddlSql, resultSet -> { + try { + if (resultSet.next()) { + return resultSet.getString("sql"); } - return databases; - }); + } catch (SQLException e) { + throw new RuntimeException(e); + } + return null; + }); } - private static String ROUTINES_SQL - = " SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p " - + "where p.prokind = '%s' and p.proname='%s';"; + @Override + public List schemas(Connection connection, String databaseName) { + return SQLExecutor.getInstance().execute(connection, + "SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> { + List databases = new ArrayList<>(); + while (resultSet.next()) { + Schema schema = new Schema(); + String name = resultSet.getString("schema_name"); + String catalogName = resultSet.getString("catalog_name"); + schema.setName(name); + schema.setDatabaseName(catalogName); + databases.add(schema); + } + return databases; + }); + } @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, - String functionName) { + String functionName) { String sql = String.format(ROUTINES_SQL, "f", functionName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -98,35 +134,24 @@ public Function function(Connection connection, @NotEmpty String databaseName, S } - private static String TRIGGER_SQL - = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " - + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" - + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s' AND t.tgname ='%s';"; - - private static String TRIGGER_SQL_LIST - = "SELECT n.nspname AS \"schema\", c.relname AS \"table_name\", t.tgname AS \"trigger_name\", t.tgenabled AS " - + "\"enabled\", pg_get_triggerdef(t.oid) AS \"trigger_body\" FROM pg_trigger t JOIN pg_class c ON c.oid = t" - + ".tgrelid JOIN pg_namespace n ON n.oid = c.relnamespace WHERE n.nspname = '%s';"; - @Override - public List triggers(Connection connection, String databaseName, String schemaName) { - List triggers = new ArrayList<>(); - String sql = String.format(TRIGGER_SQL_LIST, schemaName); + public Table view(Connection connection, String databaseName, String schemaName, String viewName) { + String sql = String.format(VIEW_SQL, schemaName, viewName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - while (resultSet.next()) { - Trigger trigger = new Trigger(); - trigger.setTriggerName(resultSet.getString("trigger_name")); - trigger.setSchemaName(schemaName); - trigger.setDatabaseName(databaseName); - triggers.add(trigger); + Table table = new Table(); + table.setDatabaseName(databaseName); + table.setSchemaName(schemaName); + table.setName(viewName); + if (resultSet.next()) { + table.setDdl(resultSet.getString("definition")); } - return triggers; + return table; }); } @Override public Trigger trigger(Connection connection, @NotEmpty String databaseName, String schemaName, - String triggerName) { + String triggerName) { String sql = String.format(TRIGGER_SQL, schemaName, triggerName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { @@ -144,7 +169,7 @@ public Trigger trigger(Connection connection, @NotEmpty String databaseName, Str @Override public Procedure procedure(Connection connection, @NotEmpty String databaseName, String schemaName, - String procedureName) { + String procedureName) { String sql = String.format(ROUTINES_SQL, "p", procedureName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { Procedure procedure = new Procedure(); @@ -158,21 +183,114 @@ public Procedure procedure(Connection connection, @NotEmpty String databaseName, }); } - private static String VIEW_SQL - = "SELECT schemaname, viewname, definition FROM pg_views WHERE schemaname = '%s' AND viewname = '%s';"; - @Override - public Table view(Connection connection, String databaseName, String schemaName, String viewName) { - String sql = String.format(VIEW_SQL, schemaName, viewName); + public List indexes(Connection connection, String databaseName, String schemaName, String tableName) { + + String constraintSql = String.format(SELECT_KEY_INDEX, schemaName, tableName); + Map constraintMap = new HashMap(); + LinkedHashMap foreignMap = new LinkedHashMap(); + SQLExecutor.getInstance().execute(connection, constraintSql, resultSet -> { + while (resultSet.next()) { + String keyName = resultSet.getString("Key_name"); + String constraintType = resultSet.getString("Constraint_type"); + constraintMap.put(keyName, constraintType); + if (StringUtils.equalsIgnoreCase(constraintType, PostgreSQLIndexTypeEnum.FOREIGN.getKeyword())) { + TableIndex tableIndex = foreignMap.get(keyName); + String columnName = resultSet.getString("Column_name"); + if (tableIndex == null) { + tableIndex = new TableIndex(); + tableIndex.setDatabaseName(databaseName); + tableIndex.setSchemaName(schemaName); + tableIndex.setTableName(tableName); + tableIndex.setName(keyName); + tableIndex.setForeignSchemaName(resultSet.getString("Foreign_schema_name")); + tableIndex.setForeignTableName(resultSet.getString("Foreign_table_name")); + tableIndex.setForeignColumnNamelist(Lists.newArrayList(columnName)); + tableIndex.setType(PostgreSQLIndexTypeEnum.FOREIGN.getName()); + foreignMap.put(keyName, tableIndex); + } else { + tableIndex.getForeignColumnNamelist().add(columnName); + } + } + } + return null; + }); + + String sql = String.format(SELECT_TABLE_INDEX, schemaName, tableName); return SQLExecutor.getInstance().execute(connection, sql, resultSet -> { - Table table = new Table(); - table.setDatabaseName(databaseName); - table.setSchemaName(schemaName); - table.setName(viewName); - if (resultSet.next()) { - table.setDdl(resultSet.getString("definition")); + LinkedHashMap map = new LinkedHashMap(foreignMap); + + while (resultSet.next()) { + String keyName = resultSet.getString("Key_name"); + TableIndex tableIndex = map.get(keyName); + if (tableIndex != null) { + List columnList = tableIndex.getColumnList(); + columnList.add(getTableIndexColumn(resultSet)); + columnList = columnList.stream().sorted(Comparator.comparing(TableIndexColumn::getOrdinalPosition)) + .collect(Collectors.toList()); + tableIndex.setColumnList(columnList); + } else { + TableIndex index = new TableIndex(); + index.setDatabaseName(databaseName); + index.setSchemaName(schemaName); + index.setTableName(tableName); + index.setName(keyName); + index.setUnique(!StringUtils.equals("t", resultSet.getString("NON_UNIQUE"))); + index.setMethod(resultSet.getString("Index_method")); + index.setComment(resultSet.getString("Index_comment")); + List tableIndexColumns = new ArrayList<>(); + tableIndexColumns.add(getTableIndexColumn(resultSet)); + index.setColumnList(tableIndexColumns); + String constraintType = constraintMap.get(keyName); + if (StringUtils.equals("t", resultSet.getString("Index_primary"))) { + index.setType(PostgreSQLIndexTypeEnum.PRIMARY.getName()); + } else if (StringUtils.equalsIgnoreCase(constraintType, PostgreSQLIndexTypeEnum.UNIQUE.getName())) { + index.setType(PostgreSQLIndexTypeEnum.UNIQUE.getName()); + } else { + index.setType(PostgreSQLIndexTypeEnum.NORMAL.getName()); + } + map.put(keyName, index); + } } - return table; + return map.values().stream().collect(Collectors.toList()); }); + + } + + @Override + public List columns(Connection connection, String databaseName, String schemaName, String tableName) { + List columnList = super.columns(connection, databaseName, schemaName, tableName); + + EasyCollectionUtils.stream(columnList).forEach(v -> { + if (StringUtils.equalsIgnoreCase(v.getColumnType(), "bpchar")) { + v.setColumnType(PostgreSQLColumnTypeEnum.CHAR.getColumnType().getTypeName().toUpperCase()); + } else { + v.setColumnType(v.getColumnType().toUpperCase()); + } + }); + return columnList; + } + + private TableIndexColumn getTableIndexColumn(ResultSet resultSet) throws SQLException { + TableIndexColumn tableIndexColumn = new TableIndexColumn(); + tableIndexColumn.setColumnName(resultSet.getString("Column_name")); + tableIndexColumn.setOrdinalPosition(resultSet.getShort("Seq_in_index")); + tableIndexColumn.setCollation(resultSet.getString("Collation")); + tableIndexColumn.setAscOrDesc(resultSet.getString("Collation")); + return tableIndexColumn; + } + + @Override + public SqlBuilder getSqlBuilder() { + return new PostgreSQLSqlBuilder(); + } + + @Override + public TableMeta getTableMeta(String databaseName, String schemaName, String tableName) { + return TableMeta.builder() + .columnTypes(PostgreSQLColumnTypeEnum.getTypes()) + .charsets(PostgreSQLCharsetEnum.getCharsets()) + .collations(PostgreSQLCollationEnum.getCollations()) + .build(); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java new file mode 100644 index 000000000..833c66d53 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java @@ -0,0 +1,160 @@ +package ai.chat2db.plugin.postgresql.builder; + +import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum; +import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum; +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Table; +import ai.chat2db.spi.model.TableColumn; +import ai.chat2db.spi.model.TableIndex; +import com.google.common.collect.Lists; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +public class PostgreSQLSqlBuilder implements SqlBuilder { + @Override + public String buildCreateTableSql(Table table) { + StringBuilder script = new StringBuilder(); + script.append("CREATE TABLE "); + script.append("\"").append(table.getName()).append("\"").append(" (").append(" ").append("\n"); + // append column + for (TableColumn column : table.getColumnList()) { + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { + continue; + } + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(column.getColumnType()); + script.append("\t").append(typeEnum.buildCreateColumnSql(column)).append(",\n"); + } + Map> tableIndexMap = table.getIndexList().stream() + .collect(Collectors.partitioningBy(v -> PostgreSQLIndexTypeEnum.NORMAL.getName().equals(v.getType()))); + // append constraint key + List constraintList = tableIndexMap.get(Boolean.FALSE); + if (CollectionUtils.isNotEmpty(constraintList)) { + for (TableIndex index : constraintList) { + if (StringUtils.isBlank(index.getName()) || StringUtils.isBlank(index.getType())) { + continue; + } + PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(index.getType()); + script.append("\t").append("").append(indexTypeEnum.buildIndexScript(index)); + script.append(",\n"); + } + + } + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append("\n)").append(";"); + + // append index + List tableIndexList = tableIndexMap.get(Boolean.TRUE); + for (TableIndex tableIndex : tableIndexList) { + if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { + continue; + } + script.append("\n"); + PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType()); + script.append("").append(indexTypeEnum.buildIndexScript(tableIndex)).append(";"); + } + + // append comment + if (StringUtils.isNotBlank(table.getComment())) { + script.append("\n"); + script.append("COMMENT ON TABLE").append(" ").append("\"").append(table.getName()).append("\" IS '") + .append(table.getComment()).append("';\n"); + } + List tableColumnList = table.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); + for (TableColumn tableColumn : tableColumnList) { + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); + ; + } + List indexList = table.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); + for (TableIndex index : indexList) { + PostgreSQLIndexTypeEnum indexEnum = PostgreSQLIndexTypeEnum.getByType(index.getType()); + script.append(indexEnum.buildIndexComment(index)).append("\n"); + ; + } + + return script.toString(); + } + + @Override + public String buildModifyTaleSql(Table oldTable, Table newTable) { + StringBuilder script = new StringBuilder(); + if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { + script.append("ALTER TABLE ").append("\"").append(oldTable.getName()).append("\""); + script.append("\t").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); + + } + newTable.setColumnList(newTable.getColumnList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList()); + newTable.setIndexList(newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getEditStatus())).toList()); + + //update name + List columnNameList = newTable.getColumnList().stream().filter(v -> + v.getOldName() != null && !StringUtils.equals(v.getOldName(), v.getName())).toList(); + for (TableColumn tableColumn : columnNameList) { + script.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" ").append("RENAME COLUMN \"") + .append(tableColumn.getOldName()).append("\" TO \"").append(tableColumn.getName()).append("\";\n"); + } + + Map> tableIndexMap = newTable.getIndexList().stream() + .collect(Collectors.partitioningBy(v -> PostgreSQLIndexTypeEnum.NORMAL.getName().equals(v.getType()))); + StringBuilder scriptModify = new StringBuilder(); + Boolean modify = false; + scriptModify.append("ALTER TABLE ").append("\"").append(newTable.getName()).append("\" \n"); + // append modify column + for (TableColumn tableColumn : newTable.getColumnList()) { + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); + scriptModify.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(",\n"); + modify = true; + + } + + // append modify constraint + for (TableIndex tableIndex : tableIndexMap.get(Boolean.FALSE)) { + if (StringUtils.isNotBlank(tableIndex.getType())) { + PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType()); + scriptModify.append("\t").append(indexTypeEnum.buildModifyIndex(tableIndex)).append(",\n"); + modify = true; + } + } + + if (BooleanUtils.isTrue(modify)) { + script.append(scriptModify); + script = new StringBuilder(script.substring(0, script.length() - 2)); + script.append(";\n"); + } + + // append modify index + for (TableIndex tableIndex : tableIndexMap.get(Boolean.TRUE)) { + if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { + PostgreSQLIndexTypeEnum indexTypeEnum = PostgreSQLIndexTypeEnum.getByType(tableIndex.getType()); + script.append(indexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); + } + } + + // append comment + if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) { + script.append("\n"); + script.append("COMMENT ON TABLE").append(" ").append("\"").append(newTable.getName()).append("\" IS '") + .append(newTable.getComment()).append("';\n"); + } + for (TableColumn tableColumn : newTable.getColumnList()) { + PostgreSQLColumnTypeEnum typeEnum = PostgreSQLColumnTypeEnum.getByType(tableColumn.getColumnType()); + script.append(typeEnum.buildComment(tableColumn, typeEnum)).append("\n"); + ; + } + List indexList = newTable.getIndexList().stream().filter(v -> StringUtils.isNotBlank(v.getComment())).toList(); + for (TableIndex index : indexList) { + PostgreSQLIndexTypeEnum indexEnum = PostgreSQLIndexTypeEnum.getByType(index.getType()); + script.append(indexEnum.buildIndexComment(index)).append("\n"); + } + + return script.toString(); + } + + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCharsetEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCharsetEnum.java new file mode 100644 index 000000000..be5164d0b --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCharsetEnum.java @@ -0,0 +1,68 @@ +package ai.chat2db.plugin.postgresql.type; + +import ai.chat2db.spi.model.Charset; + +import java.util.Arrays; +import java.util.List; + +public enum PostgreSQLCharsetEnum { + BIG5("BIG5",null), + EUC_CN("EUC_CN",null), + EUC_JP("EUC_JP",null), + EUC_JIS_2004("EUC_JIS_2004",null), + EUC_KR("EUC_KR",null), + EUC_TW("EUC_TW",null), + GB18030("GB18030",null), + GBK("GBK",null), + ISO_8859_5("ISO_8859_5",null), + ISO_8859_6("ISO_8859_6",null), + ISO_8859_7("ISO_8859_7",null), + ISO_8859_8("ISO_8859_8",null), + JOHAB("JOHAB",null), + KOI8R("KOI8R",null), + KOI8U("KOI8U",null), + LATIN1("LATIN1",null), + LATIN2("LATIN2",null), + LATIN3("LATIN3",null), + LATIN4("LATIN4",null), + LATIN5("LATIN5",null), + LATIN6("LATIN6",null), + LATIN7("LATIN7",null), + LATIN8("LATIN8",null), + LATIN9("LATIN9",null), + LATIN10("LATIN10",null), + MULE_INTERNAL("MULE_INTERNAL",null), + SJIS("SJIS",null), + SHIFT_JIS_2004("SHIFT_JIS_2004",null), + SQL_ASCII("SQL_ASCII",null), + UHC("UHC",null), + UTF8("UTF8",null), + WIN866("WIN866",null), + WIN874("WIN874",null), + WIN1250("WIN1250",null), + WIN1251("WIN1251",null), + WIN1252("WIN1252",null), + WIN1253("WIN1253",null), + WIN1254("WIN1254",null), + WIN1255("WIN1255",null), + WIN1256("WIN1256",null), + WIN1257("WIN1257",null), + WIN1258("WIN1258",null), + + ; + + private Charset charset; + + PostgreSQLCharsetEnum(String charsetName, String defaultCollationName) { + this.charset = new Charset(charsetName, defaultCollationName); + } + + public static List getCharsets() { + return Arrays.stream(PostgreSQLCharsetEnum.values()).map(PostgreSQLCharsetEnum::getCharset).collect(java.util.stream.Collectors.toList()); + } + + public Charset getCharset() { + return charset; + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCollationEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCollationEnum.java new file mode 100644 index 000000000..b449265c5 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLCollationEnum.java @@ -0,0 +1,2272 @@ +package ai.chat2db.plugin.postgresql.type; + +import ai.chat2db.spi.model.Collation; + +import java.util.Arrays; +import java.util.List; + +public enum PostgreSQLCollationEnum { + + COLLATION1("default"), + COLLATION2("C"), + COLLATION3("POSIX"), + COLLATION4("ucs_basic"), + COLLATION5("unicode"), + COLLATION6("und-x-icu"), + COLLATION7("af-x-icu"), + COLLATION8("af-NA-x-icu"), + COLLATION9("af-ZA-x-icu"), + COLLATION10("agq-x-icu"), + COLLATION11("agq-CM-x-icu"), + COLLATION12("ak-x-icu"), + COLLATION13("ak-GH-x-icu"), + COLLATION14("am-x-icu"), + COLLATION15("am-ET-x-icu"), + COLLATION16("ar-x-icu"), + COLLATION17("ar-001-x-icu"), + COLLATION18("ar-AE-x-icu"), + COLLATION19("ar-BH-x-icu"), + COLLATION20("ar-DJ-x-icu"), + COLLATION21("ar-DZ-x-icu"), + COLLATION22("ar-EG-x-icu"), + COLLATION23("ar-EH-x-icu"), + COLLATION24("ar-ER-x-icu"), + COLLATION25("ar-IL-x-icu"), + COLLATION26("ar-IQ-x-icu"), + COLLATION27("ar-JO-x-icu"), + COLLATION28("ar-KM-x-icu"), + COLLATION29("ar-KW-x-icu"), + COLLATION30("ar-LB-x-icu"), + COLLATION31("ar-LY-x-icu"), + COLLATION32("ar-MA-x-icu"), + COLLATION33("ar-MR-x-icu"), + COLLATION34("ar-OM-x-icu"), + COLLATION35("ar-PS-x-icu"), + COLLATION36("ar-QA-x-icu"), + COLLATION37("ar-SA-x-icu"), + COLLATION38("ar-SD-x-icu"), + COLLATION39("ar-SO-x-icu"), + COLLATION40("ar-SS-x-icu"), + COLLATION41("ar-SY-x-icu"), + COLLATION42("ar-TD-x-icu"), + COLLATION43("ar-TN-x-icu"), + COLLATION44("ar-YE-x-icu"), + COLLATION45("as-x-icu"), + COLLATION46("as-IN-x-icu"), + COLLATION47("asa-x-icu"), + COLLATION48("asa-TZ-x-icu"), + COLLATION49("ast-x-icu"), + COLLATION50("ast-ES-x-icu"), + COLLATION51("az-x-icu"), + COLLATION52("az-Cyrl-x-icu"), + COLLATION53("az-Cyrl-AZ-x-icu"), + COLLATION54("az-Latn-x-icu"), + COLLATION55("az-Latn-AZ-x-icu"), + COLLATION56("bas-x-icu"), + COLLATION57("bas-CM-x-icu"), + COLLATION58("be-x-icu"), + COLLATION59("be-BY-x-icu"), + COLLATION60("bem-x-icu"), + COLLATION61("bem-ZM-x-icu"), + COLLATION62("bez-x-icu"), + COLLATION63("bez-TZ-x-icu"), + COLLATION64("bg-x-icu"), + COLLATION65("bg-BG-x-icu"), + COLLATION66("bm-x-icu"), + COLLATION67("bm-ML-x-icu"), + COLLATION68("bn-x-icu"), + COLLATION69("bn-BD-x-icu"), + COLLATION70("bn-IN-x-icu"), + COLLATION71("bo-x-icu"), + COLLATION72("bo-CN-x-icu"), + COLLATION73("bo-IN-x-icu"), + COLLATION74("br-x-icu"), + COLLATION75("br-FR-x-icu"), + COLLATION76("brx-x-icu"), + COLLATION77("brx-IN-x-icu"), + COLLATION78("bs-x-icu"), + COLLATION79("bs-Cyrl-x-icu"), + COLLATION80("bs-Cyrl-BA-x-icu"), + COLLATION81("bs-Latn-x-icu"), + COLLATION82("bs-Latn-BA-x-icu"), + COLLATION83("ca-x-icu"), + COLLATION84("ca-AD-x-icu"), + COLLATION85("ca-ES-x-icu"), + COLLATION86("ca-FR-x-icu"), + COLLATION87("ca-IT-x-icu"), + COLLATION88("ccp-x-icu"), + COLLATION89("ccp-BD-x-icu"), + COLLATION90("ccp-IN-x-icu"), + COLLATION91("ce-x-icu"), + COLLATION92("ce-RU-x-icu"), + COLLATION93("ceb-x-icu"), + COLLATION94("ceb-PH-x-icu"), + COLLATION95("cgg-x-icu"), + COLLATION96("cgg-UG-x-icu"), + COLLATION97("chr-x-icu"), + COLLATION98("chr-US-x-icu"), + COLLATION99("ckb-x-icu"), + COLLATION100("ckb-IQ-x-icu"), + COLLATION101("ckb-IR-x-icu"), + COLLATION102("cs-x-icu"), + COLLATION103("cs-CZ-x-icu"), + COLLATION104("cy-x-icu"), + COLLATION105("cy-GB-x-icu"), + COLLATION106("da-x-icu"), + COLLATION107("da-DK-x-icu"), + COLLATION108("da-GL-x-icu"), + COLLATION109("dav-x-icu"), + COLLATION110("dav-KE-x-icu"), + COLLATION111("de-x-icu"), + COLLATION112("de-AT-x-icu"), + COLLATION113("de-BE-x-icu"), + COLLATION114("de-CH-x-icu"), + COLLATION115("de-DE-x-icu"), + COLLATION116("de-IT-x-icu"), + COLLATION117("id-x-icu"), + COLLATION118("de-LI-x-icu"), + COLLATION119("de-LU-x-icu"), + COLLATION120("dje-x-icu"), + COLLATION121("dje-NE-x-icu"), + COLLATION122("dsb-x-icu"), + COLLATION123("dsb-DE-x-icu"), + COLLATION124("dua-x-icu"), + COLLATION125("dua-CM-x-icu"), + COLLATION126("dyo-x-icu"), + COLLATION127("dyo-SN-x-icu"), + COLLATION128("dz-x-icu"), + COLLATION129("dz-BT-x-icu"), + COLLATION130("ebu-x-icu"), + COLLATION131("ebu-KE-x-icu"), + COLLATION132("ee-x-icu"), + COLLATION133("ee-GH-x-icu"), + COLLATION134("ee-TG-x-icu"), + COLLATION135("el-x-icu"), + COLLATION136("el-CY-x-icu"), + COLLATION137("el-GR-x-icu"), + COLLATION138("en-x-icu"), + COLLATION139("en-001-x-icu"), + COLLATION140("en-150-x-icu"), + COLLATION141("en-AE-x-icu"), + COLLATION142("en-AG-x-icu"), + COLLATION143("en-AI-x-icu"), + COLLATION144("en-AS-x-icu"), + COLLATION145("en-AT-x-icu"), + COLLATION146("en-AU-x-icu"), + COLLATION147("en-BB-x-icu"), + COLLATION148("en-BE-x-icu"), + COLLATION149("en-BI-x-icu"), + COLLATION150("en-BM-x-icu"), + COLLATION151("en-BS-x-icu"), + COLLATION152("en-BW-x-icu"), + COLLATION153("en-BZ-x-icu"), + COLLATION154("en-CA-x-icu"), + COLLATION155("en-CC-x-icu"), + COLLATION156("en-CH-x-icu"), + COLLATION157("en-CK-x-icu"), + COLLATION158("en-CM-x-icu"), + COLLATION159("en-CX-x-icu"), + COLLATION160("en-CY-x-icu"), + COLLATION161("en-DE-x-icu"), + COLLATION162("en-DG-x-icu"), + COLLATION163("en-DK-x-icu"), + COLLATION164("en-DM-x-icu"), + COLLATION165("en-ER-x-icu"), + COLLATION166("en-FI-x-icu"), + COLLATION167("en-FJ-x-icu"), + COLLATION168("en-FK-x-icu"), + COLLATION169("en-FM-x-icu"), + COLLATION170("en-GB-x-icu"), + COLLATION171("en-GD-x-icu"), + COLLATION172("en-GG-x-icu"), + COLLATION173("en-GH-x-icu"), + COLLATION174("en-GI-x-icu"), + COLLATION175("en-GM-x-icu"), + COLLATION176("en-GU-x-icu"), + COLLATION177("en-GY-x-icu"), + COLLATION178("en-HK-x-icu"), + COLLATION179("en-IE-x-icu"), + COLLATION180("en-IL-x-icu"), + COLLATION181("en-IM-x-icu"), + COLLATION182("en-IN-x-icu"), + COLLATION183("en-IO-x-icu"), + COLLATION184("en-JE-x-icu"), + COLLATION185("en-JM-x-icu"), + COLLATION186("en-KE-x-icu"), + COLLATION187("en-KI-x-icu"), + COLLATION188("en-KN-x-icu"), + COLLATION189("en-KY-x-icu"), + COLLATION190("en-LC-x-icu"), + COLLATION191("en-LR-x-icu"), + COLLATION192("en-LS-x-icu"), + COLLATION193("en-MG-x-icu"), + COLLATION194("en-MH-x-icu"), + COLLATION195("en-MO-x-icu"), + COLLATION196("en-MP-x-icu"), + COLLATION197("en-MS-x-icu"), + COLLATION198("en-MT-x-icu"), + COLLATION199("en-MU-x-icu"), + COLLATION200("en-MW-x-icu"), + COLLATION201("en-MY-x-icu"), + COLLATION202("en-NA-x-icu"), + COLLATION203("en-NF-x-icu"), + COLLATION204("en-NG-x-icu"), + COLLATION205("en-NL-x-icu"), + COLLATION206("en-NR-x-icu"), + COLLATION207("en-NU-x-icu"), + COLLATION208("en-NZ-x-icu"), + COLLATION209("en-PG-x-icu"), + COLLATION210("en-PH-x-icu"), + COLLATION211("en-PK-x-icu"), + COLLATION212("en-PN-x-icu"), + COLLATION213("en-PR-x-icu"), + COLLATION214("en-PW-x-icu"), + COLLATION215("en-RW-x-icu"), + COLLATION216("en-SB-x-icu"), + COLLATION217("en-SC-x-icu"), + COLLATION218("en-SD-x-icu"), + COLLATION219("en-SE-x-icu"), + COLLATION220("en-SG-x-icu"), + COLLATION221("en-SH-x-icu"), + COLLATION222("en-SI-x-icu"), + COLLATION223("en-SL-x-icu"), + COLLATION224("en-SS-x-icu"), + COLLATION225("en-SX-x-icu"), + COLLATION226("en-SZ-x-icu"), + COLLATION227("en-TC-x-icu"), + COLLATION228("en-TK-x-icu"), + COLLATION229("en-TO-x-icu"), + COLLATION230("en-TT-x-icu"), + COLLATION231("en-TV-x-icu"), + COLLATION232("en-TZ-x-icu"), + COLLATION233("en-UG-x-icu"), + COLLATION234("en-UM-x-icu"), + COLLATION235("en-US-x-icu"), + COLLATION236("en-US-u-va-posix-x-icu"), + COLLATION237("en-VC-x-icu"), + COLLATION238("en-VG-x-icu"), + COLLATION239("en-VI-x-icu"), + COLLATION240("en-VU-x-icu"), + COLLATION241("en-WS-x-icu"), + COLLATION242("en-ZA-x-icu"), + COLLATION243("en-ZM-x-icu"), + COLLATION244("en-ZW-x-icu"), + COLLATION245("eo-x-icu"), + COLLATION246("eo-001-x-icu"), + COLLATION247("es-x-icu"), + COLLATION248("es-419-x-icu"), + COLLATION249("es-AR-x-icu"), + COLLATION250("es-BO-x-icu"), + COLLATION251("es-BR-x-icu"), + COLLATION252("es-BZ-x-icu"), + COLLATION253("es-CL-x-icu"), + COLLATION254("es-CO-x-icu"), + COLLATION255("es-CR-x-icu"), + COLLATION256("es-CU-x-icu"), + COLLATION257("es-DO-x-icu"), + COLLATION258("es-EA-x-icu"), + COLLATION259("es-EC-x-icu"), + COLLATION260("es-ES-x-icu"), + COLLATION261("es-GQ-x-icu"), + COLLATION262("es-GT-x-icu"), + COLLATION263("es-HN-x-icu"), + COLLATION264("es-IC-x-icu"), + COLLATION265("es-MX-x-icu"), + COLLATION266("es-NI-x-icu"), + COLLATION267("es-PA-x-icu"), + COLLATION268("es-PE-x-icu"), + COLLATION269("es-PH-x-icu"), + COLLATION270("es-PR-x-icu"), + COLLATION271("es-PY-x-icu"), + COLLATION272("es-SV-x-icu"), + COLLATION273("es-US-x-icu"), + COLLATION274("es-UY-x-icu"), + COLLATION275("es-VE-x-icu"), + COLLATION276("et-x-icu"), + COLLATION277("et-EE-x-icu"), + COLLATION278("eu-x-icu"), + COLLATION279("eu-ES-x-icu"), + COLLATION280("ewo-x-icu"), + COLLATION281("ewo-CM-x-icu"), + COLLATION282("fa-x-icu"), + COLLATION283("fa-AF-x-icu"), + COLLATION284("fa-IR-x-icu"), + COLLATION285("ff-x-icu"), + COLLATION286("ff-Adlm-x-icu"), + COLLATION287("ff-Adlm-BF-x-icu"), + COLLATION288("ff-Adlm-CM-x-icu"), + COLLATION289("ff-Adlm-GH-x-icu"), + COLLATION290("ff-Adlm-GM-x-icu"), + COLLATION291("ff-Adlm-GN-x-icu"), + COLLATION292("ff-Adlm-GW-x-icu"), + COLLATION293("ff-Adlm-LR-x-icu"), + COLLATION294("ff-Adlm-MR-x-icu"), + COLLATION295("ff-Adlm-NE-x-icu"), + COLLATION296("ff-Adlm-NG-x-icu"), + COLLATION297("ff-Adlm-SL-x-icu"), + COLLATION298("ff-Adlm-SN-x-icu"), + COLLATION299("ff-Latn-x-icu"), + COLLATION300("ff-Latn-BF-x-icu"), + COLLATION301("ff-Latn-CM-x-icu"), + COLLATION302("ff-Latn-GH-x-icu"), + COLLATION303("ff-Latn-GM-x-icu"), + COLLATION304("ff-Latn-GN-x-icu"), + COLLATION305("ff-Latn-GW-x-icu"), + COLLATION306("ff-Latn-LR-x-icu"), + COLLATION307("ff-Latn-MR-x-icu"), + COLLATION308("ff-Latn-NE-x-icu"), + COLLATION309("ff-Latn-NG-x-icu"), + COLLATION310("ff-Latn-SL-x-icu"), + COLLATION311("ff-Latn-SN-x-icu"), + COLLATION312("fi-x-icu"), + COLLATION313("fi-FI-x-icu"), + COLLATION314("fil-x-icu"), + COLLATION315("fil-PH-x-icu"), + COLLATION316("fo-x-icu"), + COLLATION317("fo-DK-x-icu"), + COLLATION318("fo-FO-x-icu"), + COLLATION319("fr-x-icu"), + COLLATION320("fr-BE-x-icu"), + COLLATION321("fr-BF-x-icu"), + COLLATION322("fr-BI-x-icu"), + COLLATION323("fr-BJ-x-icu"), + COLLATION324("fr-BL-x-icu"), + COLLATION325("fr-CA-x-icu"), + COLLATION326("fr-CD-x-icu"), + COLLATION327("fr-CF-x-icu"), + COLLATION328("fr-CG-x-icu"), + COLLATION329("fr-CH-x-icu"), + COLLATION330("fr-CI-x-icu"), + COLLATION331("fr-CM-x-icu"), + COLLATION332("fr-DJ-x-icu"), + COLLATION333("fr-DZ-x-icu"), + COLLATION334("fr-FR-x-icu"), + COLLATION335("fr-GA-x-icu"), + COLLATION336("fr-GF-x-icu"), + COLLATION337("fr-GN-x-icu"), + COLLATION338("fr-GP-x-icu"), + COLLATION339("fr-GQ-x-icu"), + COLLATION340("fr-HT-x-icu"), + COLLATION341("fr-KM-x-icu"), + COLLATION342("fr-LU-x-icu"), + COLLATION343("fr-MA-x-icu"), + COLLATION344("fr-MC-x-icu"), + COLLATION345("fr-MF-x-icu"), + COLLATION346("fr-MG-x-icu"), + COLLATION347("fr-ML-x-icu"), + COLLATION348("fr-MQ-x-icu"), + COLLATION349("fr-MR-x-icu"), + COLLATION350("fr-MU-x-icu"), + COLLATION351("fr-NC-x-icu"), + COLLATION352("fr-NE-x-icu"), + COLLATION353("fr-PF-x-icu"), + COLLATION354("fr-PM-x-icu"), + COLLATION355("fr-RE-x-icu"), + COLLATION356("fr-RW-x-icu"), + COLLATION357("fr-SC-x-icu"), + COLLATION358("fr-SN-x-icu"), + COLLATION359("fr-SY-x-icu"), + COLLATION360("fr-TD-x-icu"), + COLLATION361("fr-TG-x-icu"), + COLLATION362("fr-TN-x-icu"), + COLLATION363("fr-VU-x-icu"), + COLLATION364("fr-WF-x-icu"), + COLLATION365("fr-YT-x-icu"), + COLLATION366("fur-x-icu"), + COLLATION367("fur-IT-x-icu"), + COLLATION368("fy-x-icu"), + COLLATION369("fy-NL-x-icu"), + COLLATION370("ga-x-icu"), + COLLATION371("ga-GB-x-icu"), + COLLATION372("ga-IE-x-icu"), + COLLATION373("gd-x-icu"), + COLLATION374("gd-GB-x-icu"), + COLLATION375("gl-x-icu"), + COLLATION376("gl-ES-x-icu"), + COLLATION377("gsw-x-icu"), + COLLATION378("gsw-CH-x-icu"), + COLLATION379("gsw-FR-x-icu"), + COLLATION380("gsw-LI-x-icu"), + COLLATION381("gu-x-icu"), + COLLATION382("gu-IN-x-icu"), + COLLATION383("guz-x-icu"), + COLLATION384("guz-KE-x-icu"), + COLLATION385("gv-x-icu"), + COLLATION386("gv-IM-x-icu"), + COLLATION387("ha-x-icu"), + COLLATION388("ha-GH-x-icu"), + COLLATION389("ha-NE-x-icu"), + COLLATION390("ha-NG-x-icu"), + COLLATION391("haw-x-icu"), + COLLATION392("haw-US-x-icu"), + COLLATION393("he-x-icu"), + COLLATION394("he-IL-x-icu"), + COLLATION395("hi-x-icu"), + COLLATION396("hi-IN-x-icu"), + COLLATION397("hr-x-icu"), + COLLATION398("hr-BA-x-icu"), + COLLATION399("hr-HR-x-icu"), + COLLATION400("hsb-x-icu"), + COLLATION401("hsb-DE-x-icu"), + COLLATION402("hu-x-icu"), + COLLATION403("hu-HU-x-icu"), + COLLATION404("hy-x-icu"), + COLLATION405("hy-AM-x-icu"), + COLLATION406("ia-x-icu"), + COLLATION407("ia-001-x-icu"), + COLLATION408("id-ID-x-icu"), + COLLATION409("ig-x-icu"), + COLLATION410("ig-NG-x-icu"), + COLLATION411("ii-x-icu"), + COLLATION412("ii-CN-x-icu"), + COLLATION413("is-x-icu"), + COLLATION414("is-IS-x-icu"), + COLLATION415("it-x-icu"), + COLLATION416("it-CH-x-icu"), + COLLATION417("it-IT-x-icu"), + COLLATION418("it-SM-x-icu"), + COLLATION419("it-VA-x-icu"), + COLLATION420("ja-x-icu"), + COLLATION421("ja-JP-x-icu"), + COLLATION422("jgo-x-icu"), + COLLATION423("jgo-CM-x-icu"), + COLLATION424("jmc-x-icu"), + COLLATION425("jmc-TZ-x-icu"), + COLLATION426("jv-x-icu"), + COLLATION427("jv-ID-x-icu"), + COLLATION428("ka-x-icu"), + COLLATION429("ka-GE-x-icu"), + COLLATION430("kab-x-icu"), + COLLATION431("kab-DZ-x-icu"), + COLLATION432("kam-x-icu"), + COLLATION433("kam-KE-x-icu"), + COLLATION434("kde-x-icu"), + COLLATION435("kde-TZ-x-icu"), + COLLATION436("kea-x-icu"), + COLLATION437("kea-CV-x-icu"), + COLLATION438("khq-x-icu"), + COLLATION439("khq-ML-x-icu"), + COLLATION440("ki-x-icu"), + COLLATION441("ki-KE-x-icu"), + COLLATION442("kk-x-icu"), + COLLATION443("kk-KZ-x-icu"), + COLLATION444("kkj-x-icu"), + COLLATION445("kkj-CM-x-icu"), + COLLATION446("kl-x-icu"), + COLLATION447("kl-GL-x-icu"), + COLLATION448("kln-x-icu"), + COLLATION449("kln-KE-x-icu"), + COLLATION450("km-x-icu"), + COLLATION451("km-KH-x-icu"), + COLLATION452("kn-x-icu"), + COLLATION453("kn-IN-x-icu"), + COLLATION454("ko-x-icu"), + COLLATION455("ko-KP-x-icu"), + COLLATION456("ko-KR-x-icu"), + COLLATION457("kok-x-icu"), + COLLATION458("kok-IN-x-icu"), + COLLATION459("ks-x-icu"), + COLLATION460("ks-Arab-x-icu"), + COLLATION461("ks-Arab-IN-x-icu"), + COLLATION462("ksb-x-icu"), + COLLATION463("ksb-TZ-x-icu"), + COLLATION464("ksf-x-icu"), + COLLATION465("ksf-CM-x-icu"), + COLLATION466("ksh-x-icu"), + COLLATION467("ksh-DE-x-icu"), + COLLATION468("ku-x-icu"), + COLLATION469("ku-TR-x-icu"), + COLLATION470("kw-x-icu"), + COLLATION471("kw-GB-x-icu"), + COLLATION472("ky-x-icu"), + COLLATION473("ky-KG-x-icu"), + COLLATION474("lag-x-icu"), + COLLATION475("lag-TZ-x-icu"), + COLLATION476("lb-x-icu"), + COLLATION477("lb-LU-x-icu"), + COLLATION478("lg-x-icu"), + COLLATION479("lg-UG-x-icu"), + COLLATION480("lkt-x-icu"), + COLLATION481("lkt-US-x-icu"), + COLLATION482("ln-x-icu"), + COLLATION483("ln-AO-x-icu"), + COLLATION484("ln-CD-x-icu"), + COLLATION485("ln-CF-x-icu"), + COLLATION486("ln-CG-x-icu"), + COLLATION487("lo-x-icu"), + COLLATION488("lo-LA-x-icu"), + COLLATION489("lrc-x-icu"), + COLLATION490("lrc-IQ-x-icu"), + COLLATION491("lrc-IR-x-icu"), + COLLATION492("lt-x-icu"), + COLLATION493("lt-LT-x-icu"), + COLLATION494("lu-x-icu"), + COLLATION495("lu-CD-x-icu"), + COLLATION496("luo-x-icu"), + COLLATION497("luo-KE-x-icu"), + COLLATION498("luy-x-icu"), + COLLATION499("luy-KE-x-icu"), + COLLATION500("lv-x-icu"), + COLLATION501("lv-LV-x-icu"), + COLLATION502("mai-x-icu"), + COLLATION503("mai-IN-x-icu"), + COLLATION504("mas-x-icu"), + COLLATION505("mas-KE-x-icu"), + COLLATION506("mas-TZ-x-icu"), + COLLATION507("mer-x-icu"), + COLLATION508("mer-KE-x-icu"), + COLLATION509("mfe-x-icu"), + COLLATION510("mfe-MU-x-icu"), + COLLATION511("mg-x-icu"), + COLLATION512("mg-MG-x-icu"), + COLLATION513("mgh-x-icu"), + COLLATION514("mgh-MZ-x-icu"), + COLLATION515("mgo-x-icu"), + COLLATION516("mgo-CM-x-icu"), + COLLATION517("mi-x-icu"), + COLLATION518("mi-NZ-x-icu"), + COLLATION519("mk-x-icu"), + COLLATION520("mk-MK-x-icu"), + COLLATION521("ml-x-icu"), + COLLATION522("ml-IN-x-icu"), + COLLATION523("mn-x-icu"), + COLLATION524("mn-MN-x-icu"), + COLLATION525("mni-x-icu"), + COLLATION526("mni-Beng-x-icu"), + COLLATION527("mni-Beng-IN-x-icu"), + COLLATION528("mr-x-icu"), + COLLATION529("mr-IN-x-icu"), + COLLATION530("ms-x-icu"), + COLLATION531("ms-BN-x-icu"), + COLLATION532("ms-ID-x-icu"), + COLLATION533("ms-MY-x-icu"), + COLLATION534("ms-SG-x-icu"), + COLLATION535("mt-x-icu"), + COLLATION536("mt-MT-x-icu"), + COLLATION537("mua-x-icu"), + COLLATION538("mua-CM-x-icu"), + COLLATION539("my-x-icu"), + COLLATION540("my-MM-x-icu"), + COLLATION541("mzn-x-icu"), + COLLATION542("mzn-IR-x-icu"), + COLLATION543("naq-x-icu"), + COLLATION544("naq-NA-x-icu"), + COLLATION545("nb-x-icu"), + COLLATION546("nb-NO-x-icu"), + COLLATION547("nb-SJ-x-icu"), + COLLATION548("nd-x-icu"), + COLLATION549("nd-ZW-x-icu"), + COLLATION550("nds-x-icu"), + COLLATION551("nds-DE-x-icu"), + COLLATION552("nds-NL-x-icu"), + COLLATION553("ne-x-icu"), + COLLATION554("ne-IN-x-icu"), + COLLATION555("ne-NP-x-icu"), + COLLATION556("nl-x-icu"), + COLLATION557("nl-AW-x-icu"), + COLLATION558("nl-BE-x-icu"), + COLLATION559("nl-BQ-x-icu"), + COLLATION560("nl-CW-x-icu"), + COLLATION561("nl-NL-x-icu"), + COLLATION562("nl-SR-x-icu"), + COLLATION563("nl-SX-x-icu"), + COLLATION564("nmg-x-icu"), + COLLATION565("nmg-CM-x-icu"), + COLLATION566("nn-x-icu"), + COLLATION567("nn-NO-x-icu"), + COLLATION568("nnh-x-icu"), + COLLATION569("nnh-CM-x-icu"), + COLLATION570("nus-x-icu"), + COLLATION571("nus-SS-x-icu"), + COLLATION572("nyn-x-icu"), + COLLATION573("nyn-UG-x-icu"), + COLLATION574("om-x-icu"), + COLLATION575("om-ET-x-icu"), + COLLATION576("om-KE-x-icu"), + COLLATION577("or-x-icu"), + COLLATION578("or-IN-x-icu"), + COLLATION579("os-x-icu"), + COLLATION580("os-GE-x-icu"), + COLLATION581("os-RU-x-icu"), + COLLATION582("pa-x-icu"), + COLLATION583("pa-Arab-x-icu"), + COLLATION584("pa-Arab-PK-x-icu"), + COLLATION585("pa-Guru-x-icu"), + COLLATION586("pa-Guru-IN-x-icu"), + COLLATION587("pcm-x-icu"), + COLLATION588("pcm-NG-x-icu"), + COLLATION589("pl-x-icu"), + COLLATION590("pl-PL-x-icu"), + COLLATION591("ps-x-icu"), + COLLATION592("ps-AF-x-icu"), + COLLATION593("ps-PK-x-icu"), + COLLATION594("pt-x-icu"), + COLLATION595("pt-AO-x-icu"), + COLLATION596("pt-BR-x-icu"), + COLLATION597("pt-CH-x-icu"), + COLLATION598("pt-CV-x-icu"), + COLLATION599("pt-GQ-x-icu"), + COLLATION600("pt-GW-x-icu"), + COLLATION601("pt-LU-x-icu"), + COLLATION602("pt-MO-x-icu"), + COLLATION603("pt-MZ-x-icu"), + COLLATION604("pt-PT-x-icu"), + COLLATION605("pt-ST-x-icu"), + COLLATION606("pt-TL-x-icu"), + COLLATION607("qu-x-icu"), + COLLATION608("qu-BO-x-icu"), + COLLATION609("qu-EC-x-icu"), + COLLATION610("qu-PE-x-icu"), + COLLATION611("rm-x-icu"), + COLLATION612("rm-CH-x-icu"), + COLLATION613("rn-x-icu"), + COLLATION614("rn-BI-x-icu"), + COLLATION615("ro-x-icu"), + COLLATION616("ro-MD-x-icu"), + COLLATION617("ro-RO-x-icu"), + COLLATION618("rof-x-icu"), + COLLATION619("rof-TZ-x-icu"), + COLLATION620("ru-x-icu"), + COLLATION621("ru-BY-x-icu"), + COLLATION622("ru-KG-x-icu"), + COLLATION623("ru-KZ-x-icu"), + COLLATION624("ru-MD-x-icu"), + COLLATION625("ru-RU-x-icu"), + COLLATION626("ru-UA-x-icu"), + COLLATION627("rw-x-icu"), + COLLATION628("rw-RW-x-icu"), + COLLATION629("rwk-x-icu"), + COLLATION630("rwk-TZ-x-icu"), + COLLATION631("sah-x-icu"), + COLLATION632("sah-RU-x-icu"), + COLLATION633("saq-x-icu"), + COLLATION634("saq-KE-x-icu"), + COLLATION635("sat-x-icu"), + COLLATION636("sat-Olck-x-icu"), + COLLATION637("sat-Olck-IN-x-icu"), + COLLATION638("sbp-x-icu"), + COLLATION639("sbp-TZ-x-icu"), + COLLATION640("sd-x-icu"), + COLLATION641("sd-Arab-x-icu"), + COLLATION642("sd-Arab-PK-x-icu"), + COLLATION643("sd-Deva-x-icu"), + COLLATION644("sd-Deva-IN-x-icu"), + COLLATION645("se-x-icu"), + COLLATION646("se-FI-x-icu"), + COLLATION647("se-NO-x-icu"), + COLLATION648("se-SE-x-icu"), + COLLATION649("seh-x-icu"), + COLLATION650("seh-MZ-x-icu"), + COLLATION651("ses-x-icu"), + COLLATION652("ses-ML-x-icu"), + COLLATION653("sg-x-icu"), + COLLATION654("sg-CF-x-icu"), + COLLATION655("shi-x-icu"), + COLLATION656("shi-Latn-x-icu"), + COLLATION657("shi-Latn-MA-x-icu"), + COLLATION658("shi-Tfng-x-icu"), + COLLATION659("shi-Tfng-MA-x-icu"), + COLLATION660("si-x-icu"), + COLLATION661("si-LK-x-icu"), + COLLATION662("sk-x-icu"), + COLLATION663("sk-SK-x-icu"), + COLLATION664("sl-x-icu"), + COLLATION665("sl-SI-x-icu"), + COLLATION666("smn-x-icu"), + COLLATION667("smn-FI-x-icu"), + COLLATION668("sn-x-icu"), + COLLATION669("sn-ZW-x-icu"), + COLLATION670("so-x-icu"), + COLLATION671("so-DJ-x-icu"), + COLLATION672("so-ET-x-icu"), + COLLATION673("so-KE-x-icu"), + COLLATION674("so-SO-x-icu"), + COLLATION675("sq-x-icu"), + COLLATION676("sq-AL-x-icu"), + COLLATION677("sq-MK-x-icu"), + COLLATION678("sq-XK-x-icu"), + COLLATION679("sr-x-icu"), + COLLATION680("sr-Cyrl-x-icu"), + COLLATION681("sr-Cyrl-BA-x-icu"), + COLLATION682("sr-Cyrl-ME-x-icu"), + COLLATION683("sr-Cyrl-RS-x-icu"), + COLLATION684("sr-Cyrl-XK-x-icu"), + COLLATION685("sr-Latn-x-icu"), + COLLATION686("sr-Latn-BA-x-icu"), + COLLATION687("sr-Latn-ME-x-icu"), + COLLATION688("sr-Latn-RS-x-icu"), + COLLATION689("sr-Latn-XK-x-icu"), + COLLATION690("su-x-icu"), + COLLATION691("su-Latn-x-icu"), + COLLATION692("su-Latn-ID-x-icu"), + COLLATION693("sv-x-icu"), + COLLATION694("sv-AX-x-icu"), + COLLATION695("sv-FI-x-icu"), + COLLATION696("sv-SE-x-icu"), + COLLATION697("sw-x-icu"), + COLLATION698("sw-CD-x-icu"), + COLLATION699("sw-KE-x-icu"), + COLLATION700("sw-TZ-x-icu"), + COLLATION701("sw-UG-x-icu"), + COLLATION702("ta-x-icu"), + COLLATION703("ta-IN-x-icu"), + COLLATION704("ta-LK-x-icu"), + COLLATION705("ta-MY-x-icu"), + COLLATION706("ta-SG-x-icu"), + COLLATION707("te-x-icu"), + COLLATION708("te-IN-x-icu"), + COLLATION709("teo-x-icu"), + COLLATION710("teo-KE-x-icu"), + COLLATION711("teo-UG-x-icu"), + COLLATION712("tg-x-icu"), + COLLATION713("tg-TJ-x-icu"), + COLLATION714("th-x-icu"), + COLLATION715("th-TH-x-icu"), + COLLATION716("ti-x-icu"), + COLLATION717("ti-ER-x-icu"), + COLLATION718("ti-ET-x-icu"), + COLLATION719("tk-x-icu"), + COLLATION720("tk-TM-x-icu"), + COLLATION721("to-x-icu"), + COLLATION722("to-TO-x-icu"), + COLLATION723("tr-x-icu"), + COLLATION724("tr-CY-x-icu"), + COLLATION725("tr-TR-x-icu"), + COLLATION726("tt-x-icu"), + COLLATION727("tt-RU-x-icu"), + COLLATION728("twq-x-icu"), + COLLATION729("twq-NE-x-icu"), + COLLATION730("tzm-x-icu"), + COLLATION731("tzm-MA-x-icu"), + COLLATION732("ug-x-icu"), + COLLATION733("ug-CN-x-icu"), + COLLATION734("uk-x-icu"), + COLLATION735("uk-UA-x-icu"), + COLLATION736("ur-x-icu"), + COLLATION737("ur-IN-x-icu"), + COLLATION738("ur-PK-x-icu"), + COLLATION739("uz-x-icu"), + COLLATION740("uz-Arab-x-icu"), + COLLATION741("uz-Arab-AF-x-icu"), + COLLATION742("uz-Cyrl-x-icu"), + COLLATION743("uz-Cyrl-UZ-x-icu"), + COLLATION744("uz-Latn-x-icu"), + COLLATION745("uz-Latn-UZ-x-icu"), + COLLATION746("vai-x-icu"), + COLLATION747("vai-Latn-x-icu"), + COLLATION748("vai-Latn-LR-x-icu"), + COLLATION749("vai-Vaii-x-icu"), + COLLATION750("vai-Vaii-LR-x-icu"), + COLLATION751("vi-x-icu"), + COLLATION752("vi-VN-x-icu"), + COLLATION753("vun-x-icu"), + COLLATION754("vun-TZ-x-icu"), + COLLATION755("wae-x-icu"), + COLLATION756("wae-CH-x-icu"), + COLLATION757("wo-x-icu"), + COLLATION758("wo-SN-x-icu"), + COLLATION759("xh-x-icu"), + COLLATION760("xh-ZA-x-icu"), + COLLATION761("xog-x-icu"), + COLLATION762("xog-UG-x-icu"), + COLLATION763("yav-x-icu"), + COLLATION764("yav-CM-x-icu"), + COLLATION765("yi-x-icu"), + COLLATION766("yi-001-x-icu"), + COLLATION767("yo-x-icu"), + COLLATION768("yo-BJ-x-icu"), + COLLATION769("yo-NG-x-icu"), + COLLATION770("yue-x-icu"), + COLLATION771("yue-Hans-x-icu"), + COLLATION772("yue-Hans-CN-x-icu"), + COLLATION773("yue-Hant-x-icu"), + COLLATION774("yue-Hant-HK-x-icu"), + COLLATION775("zgh-x-icu"), + COLLATION776("zgh-MA-x-icu"), + COLLATION777("zh-x-icu"), + COLLATION778("zh-Hans-x-icu"), + COLLATION779("zh-Hans-CN-x-icu"), + COLLATION780("zh-Hans-HK-x-icu"), + COLLATION781("zh-Hans-MO-x-icu"), + COLLATION782("zh-Hans-SG-x-icu"), + COLLATION783("zh-Hant-x-icu"), + COLLATION784("zh-Hant-HK-x-icu"), + COLLATION785("zh-Hant-MO-x-icu"), + COLLATION786("zh-Hant-TW-x-icu"), + COLLATION787("zu-x-icu"), + COLLATION788("zu-ZA-x-icu"), + COLLATION789("aa"), + COLLATION790("aa-DJ"), + COLLATION791("aa_DJ"), + COLLATION792("aa-ER"), + COLLATION793("aa_ER"), + COLLATION794("aa-ET"), + COLLATION795("aa_ET"), + COLLATION796("af"), + COLLATION797("af-NA"), + COLLATION798("af_NA"), + COLLATION799("af-ZA"), + COLLATION800("af_ZA"), + COLLATION801("agq"), + COLLATION802("agq-CM"), + COLLATION803("agq_CM"), + COLLATION804("ak"), + COLLATION805("ak-GH"), + COLLATION806("ak_GH"), + COLLATION807("am"), + COLLATION808("am-ET"), + COLLATION809("am_ET"), + COLLATION810("ar"), + COLLATION811("ar-001"), + COLLATION812("ar_001"), + COLLATION813("ar-AE"), + COLLATION814("ar_AE"), + COLLATION815("ar-BH"), + COLLATION816("ar_BH"), + COLLATION817("ar-DJ"), + COLLATION818("ar_DJ"), + COLLATION819("ar-DZ"), + COLLATION820("ar_DZ"), + COLLATION821("ar-EG"), + COLLATION822("ar_EG"), + COLLATION823("ar-ER"), + COLLATION824("ar_ER"), + COLLATION825("ar-IL"), + COLLATION826("ar_IL"), + COLLATION827("ar-IQ"), + COLLATION828("ar_IQ"), + COLLATION829("ar-JO"), + COLLATION830("ar_JO"), + COLLATION831("ar-KM"), + COLLATION832("ar_KM"), + COLLATION833("ar-KW"), + COLLATION834("ar_KW"), + COLLATION835("ar-LB"), + COLLATION836("ar_LB"), + COLLATION837("ar-LY"), + COLLATION838("ar_LY"), + COLLATION839("ar-MA"), + COLLATION840("ar_MA"), + COLLATION841("ar-MR"), + COLLATION842("ar_MR"), + COLLATION843("ar-OM"), + COLLATION844("ar_OM"), + COLLATION845("ar-PS"), + COLLATION846("ar_PS"), + COLLATION847("ar-QA"), + COLLATION848("ar_QA"), + COLLATION849("ar-SA"), + COLLATION850("ar_SA"), + COLLATION851("ar-SD"), + COLLATION852("ar_SD"), + COLLATION853("ar-SO"), + COLLATION854("ar_SO"), + COLLATION855("ar-SS"), + COLLATION856("ar_SS"), + COLLATION857("ar-SY"), + COLLATION858("ar_SY"), + COLLATION859("ar-TD"), + COLLATION860("ar_TD"), + COLLATION861("ar-TN"), + COLLATION862("ar_TN"), + COLLATION863("ar-YE"), + COLLATION864("ar_YE"), + COLLATION865("arn"), + COLLATION866("arn-CL"), + COLLATION867("arn_CL"), + COLLATION868("as"), + COLLATION869("as-IN"), + COLLATION870("as_IN"), + COLLATION871("asa"), + COLLATION872("asa-TZ"), + COLLATION873("asa_TZ"), + COLLATION874("ast"), + COLLATION875("ast-ES"), + COLLATION876("ast_ES"), + COLLATION877("az"), + COLLATION878("az-Cyrl"), + COLLATION879("az_Cyrl"), + COLLATION880("az-Cyrl-AZ"), + COLLATION881("az_Cyrl_AZ"), + COLLATION882("az-Latn"), + COLLATION883("az_Latn"), + COLLATION884("az-Latn-AZ"), + COLLATION885("az_Latn_AZ"), + COLLATION886("ba"), + COLLATION887("ba-RU"), + COLLATION888("ba_RU"), + COLLATION889("bas"), + COLLATION890("bas-CM"), + COLLATION891("bas_CM"), + COLLATION892("be"), + COLLATION893("be-BY"), + COLLATION894("be_BY"), + COLLATION895("bem"), + COLLATION896("bem-ZM"), + COLLATION897("bem_ZM"), + COLLATION898("bez"), + COLLATION899("bez-TZ"), + COLLATION900("bez_TZ"), + COLLATION901("bg"), + COLLATION902("bg-BG"), + COLLATION903("bg_BG"), + COLLATION904("bin"), + COLLATION905("bin-NG"), + COLLATION906("bin_NG"), + COLLATION907("bm"), + COLLATION908("bm-Latn"), + COLLATION909("bm_Latn"), + COLLATION910("bm-Latn-ML"), + COLLATION911("bm_Latn_ML"), + COLLATION912("bn"), + COLLATION913("bn-BD"), + COLLATION914("bn_BD"), + COLLATION915("bn-IN"), + COLLATION916("bn_IN"), + COLLATION917("bo"), + COLLATION918("bo-CN"), + COLLATION919("bo_CN"), + COLLATION920("bo-IN"), + COLLATION921("bo_IN"), + COLLATION922("br"), + COLLATION923("br-FR"), + COLLATION924("br_FR"), + COLLATION925("brx"), + COLLATION926("brx-IN"), + COLLATION927("brx_IN"), + COLLATION928("bs"), + COLLATION929("bs-Cyrl"), + COLLATION930("bs_Cyrl"), + COLLATION931("bs-Cyrl-BA"), + COLLATION932("bs_Cyrl_BA"), + COLLATION933("bs-Latn"), + COLLATION934("bs_Latn"), + COLLATION935("bs-Latn-BA"), + COLLATION936("bs_Latn_BA"), + COLLATION937("byn"), + COLLATION938("byn-ER"), + COLLATION939("byn_ER"), + COLLATION940("ca"), + COLLATION941("ca-AD"), + COLLATION942("ca_AD"), + COLLATION943("ca-ES"), + COLLATION944("ca_ES"), + COLLATION945("ca-ES-valencia"), + COLLATION946("ca_ES_valencia"), + COLLATION947("ca-FR"), + COLLATION948("ca_FR"), + COLLATION949("ca-IT"), + COLLATION950("ca_IT"), + COLLATION951("ccp"), + COLLATION952("ccp-Cakm"), + COLLATION953("ccp_Cakm"), + COLLATION954("ccp-Cakm-BD"), + COLLATION955("ccp_Cakm_BD"), + COLLATION956("ccp-Cakm-IN"), + COLLATION957("ccp_Cakm_IN"), + COLLATION958("ce"), + COLLATION959("ce-RU"), + COLLATION960("ce_RU"), + COLLATION961("ceb"), + COLLATION962("ceb-Latn"), + COLLATION963("ceb_Latn"), + COLLATION964("ceb-Latn-PH"), + COLLATION965("ceb_Latn_PH"), + COLLATION966("cgg"), + COLLATION967("cgg-UG"), + COLLATION968("cgg_UG"), + COLLATION969("chr"), + COLLATION970("chr-Cher"), + COLLATION971("chr_Cher"), + COLLATION972("chr-Cher-US"), + COLLATION973("chr_Cher_US"), + COLLATION974("co"), + COLLATION975("co-FR"), + COLLATION976("co_FR"), + COLLATION977("cs"), + COLLATION978("cs-CZ"), + COLLATION979("cs_CZ"), + COLLATION980("cu"), + COLLATION981("cu-RU"), + COLLATION982("cu_RU"), + COLLATION983("cy"), + COLLATION984("cy-GB"), + COLLATION985("cy_GB"), + COLLATION986("da"), + COLLATION987("da-DK"), + COLLATION988("da_DK"), + COLLATION989("da-GL"), + COLLATION990("da_GL"), + COLLATION991("dav"), + COLLATION992("dav-KE"), + COLLATION993("dav_KE"), + COLLATION994("de"), + COLLATION995("de-AT"), + COLLATION996("de_AT"), + COLLATION997("de-BE"), + COLLATION998("de_BE"), + COLLATION999("de-CH"), + COLLATION1000("de_CH"), + COLLATION1001("de-DE"), + COLLATION1002("de_DE"), + COLLATION1003("de-DE_phoneb"), + COLLATION1004("de_DE_phoneb"), + COLLATION1005("de-IT"), + COLLATION1006("de_IT"), + COLLATION1007("de-LI"), + COLLATION1008("de_LI"), + COLLATION1009("de-LU"), + COLLATION1010("de_LU"), + COLLATION1011("dje"), + COLLATION1012("dje-NE"), + COLLATION1013("dje_NE"), + COLLATION1014("dsb"), + COLLATION1015("dsb-DE"), + COLLATION1016("dsb_DE"), + COLLATION1017("dua"), + COLLATION1018("dua-CM"), + COLLATION1019("dua_CM"), + COLLATION1020("dv"), + COLLATION1021("dv-MV"), + COLLATION1022("dv_MV"), + COLLATION1023("dyo"), + COLLATION1024("dyo-SN"), + COLLATION1025("dyo_SN"), + COLLATION1026("dz"), + COLLATION1027("dz-BT"), + COLLATION1028("dz_BT"), + COLLATION1029("ebu"), + COLLATION1030("ebu-KE"), + COLLATION1031("ebu_KE"), + COLLATION1032("ee"), + COLLATION1033("ee-GH"), + COLLATION1034("ee_GH"), + COLLATION1035("ee-TG"), + COLLATION1036("ee_TG"), + COLLATION1037("el"), + COLLATION1038("el-CY"), + COLLATION1039("el_CY"), + COLLATION1040("el-GR"), + COLLATION1041("el_GR"), + COLLATION1042("en"), + COLLATION1043("en-001"), + COLLATION1044("en_001"), + COLLATION1045("en-029"), + COLLATION1046("en_029"), + COLLATION1047("en-150"), + COLLATION1048("en_150"), + COLLATION1049("en-AE"), + COLLATION1050("en_AE"), + COLLATION1051("en-AG"), + COLLATION1052("en_AG"), + COLLATION1053("en-AI"), + COLLATION1054("en_AI"), + COLLATION1055("en-AS"), + COLLATION1056("en_AS"), + COLLATION1057("en-AT"), + COLLATION1058("en_AT"), + COLLATION1059("en-AU"), + COLLATION1060("en_AU"), + COLLATION1061("en-BB"), + COLLATION1062("en_BB"), + COLLATION1063("en-BE"), + COLLATION1064("en_BE"), + COLLATION1065("en-BI"), + COLLATION1066("en_BI"), + COLLATION1067("en-BM"), + COLLATION1068("en_BM"), + COLLATION1069("en-BS"), + COLLATION1070("en_BS"), + COLLATION1071("en-BW"), + COLLATION1072("en_BW"), + COLLATION1073("en-BZ"), + COLLATION1074("en_BZ"), + COLLATION1075("en-CA"), + COLLATION1076("en_CA"), + COLLATION1077("en-CC"), + COLLATION1078("en_CC"), + COLLATION1079("en-CH"), + COLLATION1080("en_CH"), + COLLATION1081("en-CK"), + COLLATION1082("en_CK"), + COLLATION1083("en-CM"), + COLLATION1084("en_CM"), + COLLATION1085("en-CX"), + COLLATION1086("en_CX"), + COLLATION1087("en-CY"), + COLLATION1088("en_CY"), + COLLATION1089("en-DE"), + COLLATION1090("en_DE"), + COLLATION1091("en-DK"), + COLLATION1092("en_DK"), + COLLATION1093("en-DM"), + COLLATION1094("en_DM"), + COLLATION1095("en-ER"), + COLLATION1096("en_ER"), + COLLATION1097("en-FI"), + COLLATION1098("en_FI"), + COLLATION1099("en-FJ"), + COLLATION1100("en_FJ"), + COLLATION1101("en-FK"), + COLLATION1102("en_FK"), + COLLATION1103("en-FM"), + COLLATION1104("en_FM"), + COLLATION1105("en-GB"), + COLLATION1106("en_GB"), + COLLATION1107("en-GD"), + COLLATION1108("en_GD"), + COLLATION1109("en-GG"), + COLLATION1110("en_GG"), + COLLATION1111("en-GH"), + COLLATION1112("en_GH"), + COLLATION1113("en-GI"), + COLLATION1114("en_GI"), + COLLATION1115("en-GM"), + COLLATION1116("en_GM"), + COLLATION1117("en-GU"), + COLLATION1118("en_GU"), + COLLATION1119("en-GY"), + COLLATION1120("en_GY"), + COLLATION1121("en-HK"), + COLLATION1122("en_HK"), + COLLATION1123("en-ID"), + COLLATION1124("en_ID"), + COLLATION1125("en-IE"), + COLLATION1126("en_IE"), + COLLATION1127("en-IL"), + COLLATION1128("en_IL"), + COLLATION1129("en-IM"), + COLLATION1130("en_IM"), + COLLATION1131("en-IN"), + COLLATION1132("en_IN"), + COLLATION1133("en-IO"), + COLLATION1134("en_IO"), + COLLATION1135("en-JE"), + COLLATION1136("en_JE"), + COLLATION1137("en-JM"), + COLLATION1138("en_JM"), + COLLATION1139("en-KE"), + COLLATION1140("en_KE"), + COLLATION1141("en-KI"), + COLLATION1142("en_KI"), + COLLATION1143("en-KN"), + COLLATION1144("en_KN"), + COLLATION1145("en-KY"), + COLLATION1146("en_KY"), + COLLATION1147("en-LC"), + COLLATION1148("en_LC"), + COLLATION1149("en-LR"), + COLLATION1150("en_LR"), + COLLATION1151("en-LS"), + COLLATION1152("en_LS"), + COLLATION1153("en-MG"), + COLLATION1154("en_MG"), + COLLATION1155("en-MH"), + COLLATION1156("en_MH"), + COLLATION1157("en-MO"), + COLLATION1158("en_MO"), + COLLATION1159("en-MP"), + COLLATION1160("en_MP"), + COLLATION1161("en-MS"), + COLLATION1162("en_MS"), + COLLATION1163("en-MT"), + COLLATION1164("en_MT"), + COLLATION1165("en-MU"), + COLLATION1166("en_MU"), + COLLATION1167("en-MW"), + COLLATION1168("en_MW"), + COLLATION1169("en-MY"), + COLLATION1170("en_MY"), + COLLATION1171("en-NA"), + COLLATION1172("en_NA"), + COLLATION1173("en-NF"), + COLLATION1174("en_NF"), + COLLATION1175("en-NG"), + COLLATION1176("en_NG"), + COLLATION1177("en-NL"), + COLLATION1178("en_NL"), + COLLATION1179("en-NR"), + COLLATION1180("en_NR"), + COLLATION1181("en-NU"), + COLLATION1182("en_NU"), + COLLATION1183("en-NZ"), + COLLATION1184("en_NZ"), + COLLATION1185("en-PG"), + COLLATION1186("en_PG"), + COLLATION1187("en-PH"), + COLLATION1188("en_PH"), + COLLATION1189("en-PK"), + COLLATION1190("en_PK"), + COLLATION1191("en-PN"), + COLLATION1192("en_PN"), + COLLATION1193("en-PR"), + COLLATION1194("en_PR"), + COLLATION1195("en-PW"), + COLLATION1196("en_PW"), + COLLATION1197("en-RW"), + COLLATION1198("en_RW"), + COLLATION1199("en-SB"), + COLLATION1200("en_SB"), + COLLATION1201("en-SC"), + COLLATION1202("en_SC"), + COLLATION1203("en-SD"), + COLLATION1204("en_SD"), + COLLATION1205("en-SE"), + COLLATION1206("en_SE"), + COLLATION1207("en-SG"), + COLLATION1208("en_SG"), + COLLATION1209("en-SH"), + COLLATION1210("en_SH"), + COLLATION1211("en-SI"), + COLLATION1212("en_SI"), + COLLATION1213("en-SL"), + COLLATION1214("en_SL"), + COLLATION1215("en-SS"), + COLLATION1216("en_SS"), + COLLATION1217("en-SX"), + COLLATION1218("en_SX"), + COLLATION1219("en-SZ"), + COLLATION1220("en_SZ"), + COLLATION1221("en-TC"), + COLLATION1222("en_TC"), + COLLATION1223("en-TK"), + COLLATION1224("en_TK"), + COLLATION1225("en-TO"), + COLLATION1226("en_TO"), + COLLATION1227("en-TT"), + COLLATION1228("en_TT"), + COLLATION1229("en-TV"), + COLLATION1230("en_TV"), + COLLATION1231("en-TZ"), + COLLATION1232("en_TZ"), + COLLATION1233("en-UG"), + COLLATION1234("en_UG"), + COLLATION1235("en-UM"), + COLLATION1236("en_UM"), + COLLATION1237("en-US"), + COLLATION1238("en_US"), + COLLATION1239("en-VC"), + COLLATION1240("en_VC"), + COLLATION1241("en-VG"), + COLLATION1242("en_VG"), + COLLATION1243("en-VI"), + COLLATION1244("en_VI"), + COLLATION1245("en-VU"), + COLLATION1246("en_VU"), + COLLATION1247("en-WS"), + COLLATION1248("en_WS"), + COLLATION1249("en-ZA"), + COLLATION1250("en_ZA"), + COLLATION1251("en-ZM"), + COLLATION1252("en_ZM"), + COLLATION1253("en-ZW"), + COLLATION1254("en_ZW"), + COLLATION1255("eo"), + COLLATION1256("eo-001"), + COLLATION1257("eo_001"), + COLLATION1258("es"), + COLLATION1259("es-419"), + COLLATION1260("es_419"), + COLLATION1261("es-AR"), + COLLATION1262("es_AR"), + COLLATION1263("es-BO"), + COLLATION1264("es_BO"), + COLLATION1265("es-BR"), + COLLATION1266("es_BR"), + COLLATION1267("es-BZ"), + COLLATION1268("es_BZ"), + COLLATION1269("es-CL"), + COLLATION1270("es_CL"), + COLLATION1271("es-CO"), + COLLATION1272("es_CO"), + COLLATION1273("es-CR"), + COLLATION1274("es_CR"), + COLLATION1275("es-CU"), + COLLATION1276("es_CU"), + COLLATION1277("es-DO"), + COLLATION1278("es_DO"), + COLLATION1279("es-EC"), + COLLATION1280("es_EC"), + COLLATION1281("es-ES"), + COLLATION1282("es_ES"), + COLLATION1283("es-ES_tradnl"), + COLLATION1284("es_ES_tradnl"), + COLLATION1285("es-GQ"), + COLLATION1286("es_GQ"), + COLLATION1287("es-GT"), + COLLATION1288("es_GT"), + COLLATION1289("es-HN"), + COLLATION1290("es_HN"), + COLLATION1291("es-MX"), + COLLATION1292("es_MX"), + COLLATION1293("es-NI"), + COLLATION1294("es_NI"), + COLLATION1295("es-PA"), + COLLATION1296("es_PA"), + COLLATION1297("es-PE"), + COLLATION1298("es_PE"), + COLLATION1299("es-PH"), + COLLATION1300("es_PH"), + COLLATION1301("es-PR"), + COLLATION1302("es_PR"), + COLLATION1303("es-PY"), + COLLATION1304("es_PY"), + COLLATION1305("es-SV"), + COLLATION1306("es_SV"), + COLLATION1307("es-US"), + COLLATION1308("es_US"), + COLLATION1309("es-UY"), + COLLATION1310("es_UY"), + COLLATION1311("es-VE"), + COLLATION1312("es_VE"), + COLLATION1313("et"), + COLLATION1314("et-EE"), + COLLATION1315("et_EE"), + COLLATION1316("eu"), + COLLATION1317("eu-ES"), + COLLATION1318("eu_ES"), + COLLATION1319("ewo"), + COLLATION1320("ewo-CM"), + COLLATION1321("ewo_CM"), + COLLATION1322("fa"), + COLLATION1323("fa-IR"), + COLLATION1324("fa_IR"), + COLLATION1325("ff"), + COLLATION1326("ff-Latn"), + COLLATION1327("ff_Latn"), + COLLATION1328("ff-Latn-BF"), + COLLATION1329("ff_Latn_BF"), + COLLATION1330("ff-Latn-CM"), + COLLATION1331("ff_Latn_CM"), + COLLATION1332("ff-Latn-GH"), + COLLATION1333("ff_Latn_GH"), + COLLATION1334("ff-Latn-GM"), + COLLATION1335("ff_Latn_GM"), + COLLATION1336("ff-Latn-GN"), + COLLATION1337("ff_Latn_GN"), + COLLATION1338("ff-Latn-GW"), + COLLATION1339("ff_Latn_GW"), + COLLATION1340("ff-Latn-LR"), + COLLATION1341("ff_Latn_LR"), + COLLATION1342("ff-Latn-MR"), + COLLATION1343("ff_Latn_MR"), + COLLATION1344("ff-Latn-NE"), + COLLATION1345("ff_Latn_NE"), + COLLATION1346("ff-Latn-NG"), + COLLATION1347("ff_Latn_NG"), + COLLATION1348("ff-Latn-SL"), + COLLATION1349("ff_Latn_SL"), + COLLATION1350("ff-Latn-SN"), + COLLATION1351("ff_Latn_SN"), + COLLATION1352("fi"), + COLLATION1353("fi-FI"), + COLLATION1354("fi_FI"), + COLLATION1355("fil"), + COLLATION1356("fil-PH"), + COLLATION1357("fil_PH"), + COLLATION1358("fo"), + COLLATION1359("fo-DK"), + COLLATION1360("fo_DK"), + COLLATION1361("fo-FO"), + COLLATION1362("fo_FO"), + COLLATION1363("fr"), + COLLATION1364("fr-029"), + COLLATION1365("fr_029"), + COLLATION1366("fr-BE"), + COLLATION1367("fr_BE"), + COLLATION1368("fr-BF"), + COLLATION1369("fr_BF"), + COLLATION1370("fr-BI"), + COLLATION1371("fr_BI"), + COLLATION1372("fr-BJ"), + COLLATION1373("fr_BJ"), + COLLATION1374("fr-BL"), + COLLATION1375("fr_BL"), + COLLATION1376("fr-CA"), + COLLATION1377("fr_CA"), + COLLATION1378("fr-CD"), + COLLATION1379("fr_CD"), + COLLATION1380("fr-CF"), + COLLATION1381("fr_CF"), + COLLATION1382("fr-CG"), + COLLATION1383("fr_CG"), + COLLATION1384("fr-CH"), + COLLATION1385("fr_CH"), + COLLATION1386("fr-CI"), + COLLATION1387("fr_CI"), + COLLATION1388("fr-CM"), + COLLATION1389("fr_CM"), + COLLATION1390("fr-DJ"), + COLLATION1391("fr_DJ"), + COLLATION1392("fr-DZ"), + COLLATION1393("fr_DZ"), + COLLATION1394("fr-FR"), + COLLATION1395("fr_FR"), + COLLATION1396("fr-GA"), + COLLATION1397("fr_GA"), + COLLATION1398("fr-GF"), + COLLATION1399("fr_GF"), + COLLATION1400("fr-GN"), + COLLATION1401("fr_GN"), + COLLATION1402("fr-GP"), + COLLATION1403("fr_GP"), + COLLATION1404("fr-GQ"), + COLLATION1405("fr_GQ"), + COLLATION1406("fr-HT"), + COLLATION1407("fr_HT"), + COLLATION1408("fr-KM"), + COLLATION1409("fr_KM"), + COLLATION1410("fr-LU"), + COLLATION1411("fr_LU"), + COLLATION1412("fr-MA"), + COLLATION1413("fr_MA"), + COLLATION1414("fr-MC"), + COLLATION1415("fr_MC"), + COLLATION1416("fr-MF"), + COLLATION1417("fr_MF"), + COLLATION1418("fr-MG"), + COLLATION1419("fr_MG"), + COLLATION1420("fr-ML"), + COLLATION1421("fr_ML"), + COLLATION1422("fr-MQ"), + COLLATION1423("fr_MQ"), + COLLATION1424("fr-MR"), + COLLATION1425("fr_MR"), + COLLATION1426("fr-MU"), + COLLATION1427("fr_MU"), + COLLATION1428("fr-NC"), + COLLATION1429("fr_NC"), + COLLATION1430("fr-NE"), + COLLATION1431("fr_NE"), + COLLATION1432("fr-PF"), + COLLATION1433("fr_PF"), + COLLATION1434("fr-PM"), + COLLATION1435("fr_PM"), + COLLATION1436("fr-RE"), + COLLATION1437("fr_RE"), + COLLATION1438("fr-RW"), + COLLATION1439("fr_RW"), + COLLATION1440("fr-SC"), + COLLATION1441("fr_SC"), + COLLATION1442("fr-SN"), + COLLATION1443("fr_SN"), + COLLATION1444("fr-SY"), + COLLATION1445("fr_SY"), + COLLATION1446("fr-TD"), + COLLATION1447("fr_TD"), + COLLATION1448("fr-TG"), + COLLATION1449("fr_TG"), + COLLATION1450("fr-TN"), + COLLATION1451("fr_TN"), + COLLATION1452("fr-VU"), + COLLATION1453("fr_VU"), + COLLATION1454("fr-WF"), + COLLATION1455("fr_WF"), + COLLATION1456("fr-YT"), + COLLATION1457("fr_YT"), + COLLATION1458("fur"), + COLLATION1459("fur-IT"), + COLLATION1460("fur_IT"), + COLLATION1461("fy"), + COLLATION1462("fy-NL"), + COLLATION1463("fy_NL"), + COLLATION1464("ga"), + COLLATION1465("ga-IE"), + COLLATION1466("ga_IE"), + COLLATION1467("gd"), + COLLATION1468("gd-GB"), + COLLATION1469("gd_GB"), + COLLATION1470("gl"), + COLLATION1471("gl-ES"), + COLLATION1472("gl_ES"), + COLLATION1473("gn"), + COLLATION1474("gn-PY"), + COLLATION1475("gn_PY"), + COLLATION1476("gsw"), + COLLATION1477("gsw-CH"), + COLLATION1478("gsw_CH"), + COLLATION1479("gsw-FR"), + COLLATION1480("gsw_FR"), + COLLATION1481("gsw-LI"), + COLLATION1482("gsw_LI"), + COLLATION1483("gu"), + COLLATION1484("gu-IN"), + COLLATION1485("gu_IN"), + COLLATION1486("guz"), + COLLATION1487("guz-KE"), + COLLATION1488("guz_KE"), + COLLATION1489("gv"), + COLLATION1490("gv-IM"), + COLLATION1491("gv_IM"), + COLLATION1492("ha"), + COLLATION1493("ha-Latn"), + COLLATION1494("ha_Latn"), + COLLATION1495("ha-Latn-GH"), + COLLATION1496("ha_Latn_GH"), + COLLATION1497("ha-Latn-NE"), + COLLATION1498("ha_Latn_NE"), + COLLATION1499("ha-Latn-NG"), + COLLATION1500("ha_Latn_NG"), + COLLATION1501("haw"), + COLLATION1502("haw-US"), + COLLATION1503("haw_US"), + COLLATION1504("he"), + COLLATION1505("he-IL"), + COLLATION1506("he_IL"), + COLLATION1507("hi"), + COLLATION1508("hi-IN"), + COLLATION1509("hi_IN"), + COLLATION1510("hr"), + COLLATION1511("hr-BA"), + COLLATION1512("hr_BA"), + COLLATION1513("hr-HR"), + COLLATION1514("hr_HR"), + COLLATION1515("hsb"), + COLLATION1516("hsb-DE"), + COLLATION1517("hsb_DE"), + COLLATION1518("hu"), + COLLATION1519("hu-HU"), + COLLATION1520("hu_HU"), + COLLATION1521("hu-HU_technl"), + COLLATION1522("hu_HU_technl"), + COLLATION1523("hy"), + COLLATION1524("hy-AM"), + COLLATION1525("hy_AM"), + COLLATION1526("ia"), + COLLATION1527("ia-001"), + COLLATION1528("ia_001"), + COLLATION1529("ibb"), + COLLATION1530("ibb-NG"), + COLLATION1531("ibb_NG"), + COLLATION1532("id"), + COLLATION1533("id-ID"), + COLLATION1534("id_ID"), + COLLATION1535("ig"), + COLLATION1536("ig-NG"), + COLLATION1537("ig_NG"), + COLLATION1538("ii"), + COLLATION1539("ii-CN"), + COLLATION1540("ii_CN"), + COLLATION1541("is"), + COLLATION1542("is-IS"), + COLLATION1543("is_IS"), + COLLATION1544("it"), + COLLATION1545("it-CH"), + COLLATION1546("it_CH"), + COLLATION1547("it-IT"), + COLLATION1548("it_IT"), + COLLATION1549("it-SM"), + COLLATION1550("it_SM"), + COLLATION1551("it-VA"), + COLLATION1552("it_VA"), + COLLATION1553("iu"), + COLLATION1554("iu-Cans"), + COLLATION1555("iu_Cans"), + COLLATION1556("iu-Cans-CA"), + COLLATION1557("iu_Cans_CA"), + COLLATION1558("iu-Latn"), + COLLATION1559("iu_Latn"), + COLLATION1560("iu-Latn-CA"), + COLLATION1561("iu_Latn_CA"), + COLLATION1562("jgo"), + COLLATION1563("jgo-CM"), + COLLATION1564("jgo_CM"), + COLLATION1565("jmc"), + COLLATION1566("jmc-TZ"), + COLLATION1567("jmc_TZ"), + COLLATION1568("jv"), + COLLATION1569("jv-Java"), + COLLATION1570("jv_Java"), + COLLATION1571("jv-Java-ID"), + COLLATION1572("jv_Java_ID"), + COLLATION1573("jv-Latn"), + COLLATION1574("jv_Latn"), + COLLATION1575("jv-Latn-ID"), + COLLATION1576("jv_Latn_ID"), + COLLATION1577("ka"), + COLLATION1578("ka-GE"), + COLLATION1579("ka_GE"), + COLLATION1580("ka-GE_modern"), + COLLATION1581("ka_GE_modern"), + COLLATION1582("kab"), + COLLATION1583("kab-DZ"), + COLLATION1584("kab_DZ"), + COLLATION1585("kam"), + COLLATION1586("kam-KE"), + COLLATION1587("kam_KE"), + COLLATION1588("kde"), + COLLATION1589("kde-TZ"), + COLLATION1590("kde_TZ"), + COLLATION1591("kea"), + COLLATION1592("kea-CV"), + COLLATION1593("kea_CV"), + COLLATION1594("khq"), + COLLATION1595("khq-ML"), + COLLATION1596("khq_ML"), + COLLATION1597("ki"), + COLLATION1598("ki-KE"), + COLLATION1599("ki_KE"), + COLLATION1600("kk"), + COLLATION1601("kk-KZ"), + COLLATION1602("kk_KZ"), + COLLATION1603("kkj"), + COLLATION1604("kkj-CM"), + COLLATION1605("kkj_CM"), + COLLATION1606("kl"), + COLLATION1607("kl-GL"), + COLLATION1608("kl_GL"), + COLLATION1609("kln"), + COLLATION1610("kln-KE"), + COLLATION1611("kln_KE"), + COLLATION1612("km"), + COLLATION1613("km-KH"), + COLLATION1614("km_KH"), + COLLATION1615("kn"), + COLLATION1616("kn-IN"), + COLLATION1617("kn_IN"), + COLLATION1618("ko-KP"), + COLLATION1619("ko_KP"), + COLLATION1620("kok"), + COLLATION1621("kok-IN"), + COLLATION1622("kok_IN"), + COLLATION1623("kr"), + COLLATION1624("kr-Latn"), + COLLATION1625("kr_Latn"), + COLLATION1626("kr-Latn-NG"), + COLLATION1627("kr_Latn_NG"), + COLLATION1628("ks"), + COLLATION1629("ks-Arab"), + COLLATION1630("ks_Arab"), + COLLATION1631("ks-Arab-IN"), + COLLATION1632("ks_Arab_IN"), + COLLATION1633("ks-Deva"), + COLLATION1634("ks_Deva"), + COLLATION1635("ks-Deva-IN"), + COLLATION1636("ks_Deva_IN"), + COLLATION1637("ksb"), + COLLATION1638("ksb-TZ"), + COLLATION1639("ksb_TZ"), + COLLATION1640("ksf"), + COLLATION1641("ksf-CM"), + COLLATION1642("ksf_CM"), + COLLATION1643("ksh"), + COLLATION1644("ksh-DE"), + COLLATION1645("ksh_DE"), + COLLATION1646("ku"), + COLLATION1647("ku-Arab"), + COLLATION1648("ku_Arab"), + COLLATION1649("ku-Arab-IQ"), + COLLATION1650("ku_Arab_IQ"), + COLLATION1651("ku-Arab-IR"), + COLLATION1652("ku_Arab_IR"), + COLLATION1653("kw"), + COLLATION1654("kw-GB"), + COLLATION1655("kw_GB"), + COLLATION1656("ky"), + COLLATION1657("ky-KG"), + COLLATION1658("ky_KG"), + COLLATION1659("la"), + COLLATION1660("la-001"), + COLLATION1661("la_001"), + COLLATION1662("lag"), + COLLATION1663("lag-TZ"), + COLLATION1664("lag_TZ"), + COLLATION1665("lb"), + COLLATION1666("lb-LU"), + COLLATION1667("lb_LU"), + COLLATION1668("lg"), + COLLATION1669("lg-UG"), + COLLATION1670("lg_UG"), + COLLATION1671("lkt"), + COLLATION1672("lkt-US"), + COLLATION1673("lkt_US"), + COLLATION1674("ln"), + COLLATION1675("ln-AO"), + COLLATION1676("ln_AO"), + COLLATION1677("ln-CD"), + COLLATION1678("ln_CD"), + COLLATION1679("ln-CF"), + COLLATION1680("ln_CF"), + COLLATION1681("ln-CG"), + COLLATION1682("ln_CG"), + COLLATION1683("lo"), + COLLATION1684("lo-LA"), + COLLATION1685("lo_LA"), + COLLATION1686("lrc"), + COLLATION1687("lrc-IQ"), + COLLATION1688("lrc_IQ"), + COLLATION1689("lrc-IR"), + COLLATION1690("lrc_IR"), + COLLATION1691("lt"), + COLLATION1692("lt-LT"), + COLLATION1693("lt_LT"), + COLLATION1694("lu"), + COLLATION1695("lu-CD"), + COLLATION1696("lu_CD"), + COLLATION1697("luo"), + COLLATION1698("luo-KE"), + COLLATION1699("luo_KE"), + COLLATION1700("luy"), + COLLATION1701("luy-KE"), + COLLATION1702("luy_KE"), + COLLATION1703("lv"), + COLLATION1704("lv-LV"), + COLLATION1705("lv_LV"), + COLLATION1706("mas"), + COLLATION1707("mas-KE"), + COLLATION1708("mas_KE"), + COLLATION1709("mas-TZ"), + COLLATION1710("mas_TZ"), + COLLATION1711("mer"), + COLLATION1712("mer-KE"), + COLLATION1713("mer_KE"), + COLLATION1714("mfe"), + COLLATION1715("mfe-MU"), + COLLATION1716("mfe_MU"), + COLLATION1717("mg"), + COLLATION1718("mg-MG"), + COLLATION1719("mg_MG"), + COLLATION1720("mgh"), + COLLATION1721("mgh-MZ"), + COLLATION1722("mgh_MZ"), + COLLATION1723("mgo"), + COLLATION1724("mgo-CM"), + COLLATION1725("mgo_CM"), + COLLATION1726("mi"), + COLLATION1727("mi-NZ"), + COLLATION1728("mi_NZ"), + COLLATION1729("mk"), + COLLATION1730("mk-MK"), + COLLATION1731("mk_MK"), + COLLATION1732("ml"), + COLLATION1733("ml-IN"), + COLLATION1734("ml_IN"), + COLLATION1735("mn"), + COLLATION1736("mn-Cyrl"), + COLLATION1737("mn_Cyrl"), + COLLATION1738("mn-MN"), + COLLATION1739("mn_MN"), + COLLATION1740("mn-Mong"), + COLLATION1741("mn_Mong"), + COLLATION1742("mn-Mong-CN"), + COLLATION1743("mn_Mong_CN"), + COLLATION1744("mn-Mong-MN"), + COLLATION1745("mn_Mong_MN"), + COLLATION1746("mni"), + COLLATION1747("mni-IN"), + COLLATION1748("mni_IN"), + COLLATION1749("moh"), + COLLATION1750("moh-CA"), + COLLATION1751("moh_CA"), + COLLATION1752("mr"), + COLLATION1753("mr-IN"), + COLLATION1754("mr_IN"), + COLLATION1755("ms"), + COLLATION1756("ms-BN"), + COLLATION1757("ms_BN"), + COLLATION1758("ms-MY"), + COLLATION1759("ms_MY"), + COLLATION1760("ms-SG"), + COLLATION1761("ms_SG"), + COLLATION1762("mt"), + COLLATION1763("mt-MT"), + COLLATION1764("mt_MT"), + COLLATION1765("mua"), + COLLATION1766("mua-CM"), + COLLATION1767("mua_CM"), + COLLATION1768("my"), + COLLATION1769("my-MM"), + COLLATION1770("my_MM"), + COLLATION1771("mzn"), + COLLATION1772("mzn-IR"), + COLLATION1773("mzn_IR"), + COLLATION1774("naq"), + COLLATION1775("naq-NA"), + COLLATION1776("naq_NA"), + COLLATION1777("nb"), + COLLATION1778("nb-NO"), + COLLATION1779("nb_NO"), + COLLATION1780("nb-SJ"), + COLLATION1781("nb_SJ"), + COLLATION1782("nd"), + COLLATION1783("nd-ZW"), + COLLATION1784("nd_ZW"), + COLLATION1785("nds"), + COLLATION1786("nds-DE"), + COLLATION1787("nds_DE"), + COLLATION1788("nds-NL"), + COLLATION1789("nds_NL"), + COLLATION1790("ne"), + COLLATION1791("ne-IN"), + COLLATION1792("ne_IN"), + COLLATION1793("ne-NP"), + COLLATION1794("ne_NP"), + COLLATION1795("nl"), + COLLATION1796("nl-AW"), + COLLATION1797("nl_AW"), + COLLATION1798("nl-BE"), + COLLATION1799("nl_BE"), + COLLATION1800("nl-BQ"), + COLLATION1801("nl_BQ"), + COLLATION1802("nl-CW"), + COLLATION1803("nl_CW"), + COLLATION1804("nl-NL"), + COLLATION1805("nl_NL"), + COLLATION1806("nl-SR"), + COLLATION1807("nl_SR"), + COLLATION1808("nl-SX"), + COLLATION1809("nl_SX"), + COLLATION1810("nmg"), + COLLATION1811("nmg-CM"), + COLLATION1812("nmg_CM"), + COLLATION1813("nn"), + COLLATION1814("nn-NO"), + COLLATION1815("nn_NO"), + COLLATION1816("nnh"), + COLLATION1817("nnh-CM"), + COLLATION1818("nnh_CM"), + COLLATION1819("no"), + COLLATION1820("nqo"), + COLLATION1821("nqo-GN"), + COLLATION1822("nqo_GN"), + COLLATION1823("nr"), + COLLATION1824("nr-ZA"), + COLLATION1825("nr_ZA"), + COLLATION1826("nso"), + COLLATION1827("nso-ZA"), + COLLATION1828("nso_ZA"), + COLLATION1829("nus"), + COLLATION1830("nus-SS"), + COLLATION1831("nus_SS"), + COLLATION1832("nyn"), + COLLATION1833("nyn-UG"), + COLLATION1834("nyn_UG"), + COLLATION1835("oc"), + COLLATION1836("oc-FR"), + COLLATION1837("oc_FR"), + COLLATION1838("om"), + COLLATION1839("om-ET"), + COLLATION1840("om_ET"), + COLLATION1841("om-KE"), + COLLATION1842("om_KE"), + COLLATION1843("or"), + COLLATION1844("or-IN"), + COLLATION1845("or_IN"), + COLLATION1846("os"), + COLLATION1847("os-GE"), + COLLATION1848("os_GE"), + COLLATION1849("os-RU"), + COLLATION1850("os_RU"), + COLLATION1851("pa"), + COLLATION1852("pa-Arab"), + COLLATION1853("pa_Arab"), + COLLATION1854("pa-Arab-PK"), + COLLATION1855("pa_Arab_PK"), + COLLATION1856("pa-Guru"), + COLLATION1857("pa_Guru"), + COLLATION1858("pa-IN"), + COLLATION1859("pa_IN"), + COLLATION1860("pap"), + COLLATION1861("pap-029"), + COLLATION1862("pap_029"), + COLLATION1863("pl"), + COLLATION1864("pl-PL"), + COLLATION1865("pl_PL"), + COLLATION1866("prg"), + COLLATION1867("prg-001"), + COLLATION1868("prg_001"), + COLLATION1869("prs"), + COLLATION1870("prs-AF"), + COLLATION1871("prs_AF"), + COLLATION1872("ps"), + COLLATION1873("ps-AF"), + COLLATION1874("ps_AF"), + COLLATION1875("ps-PK"), + COLLATION1876("ps_PK"), + COLLATION1877("pt"), + COLLATION1878("pt-AO"), + COLLATION1879("pt_AO"), + COLLATION1880("pt-BR"), + COLLATION1881("pt_BR"), + COLLATION1882("pt-CH"), + COLLATION1883("pt_CH"), + COLLATION1884("pt-CV"), + COLLATION1885("pt_CV"), + COLLATION1886("pt-GQ"), + COLLATION1887("pt_GQ"), + COLLATION1888("pt-GW"), + COLLATION1889("pt_GW"), + COLLATION1890("pt-LU"), + COLLATION1891("pt_LU"), + COLLATION1892("pt-MO"), + COLLATION1893("pt_MO"), + COLLATION1894("pt-MZ"), + COLLATION1895("pt_MZ"), + COLLATION1896("pt-PT"), + COLLATION1897("pt_PT"), + COLLATION1898("pt-ST"), + COLLATION1899("pt_ST"), + COLLATION1900("pt-TL"), + COLLATION1901("pt_TL"), + COLLATION1902("quc"), + COLLATION1903("quc-Latn"), + COLLATION1904("quc_Latn"), + COLLATION1905("quc-Latn-GT"), + COLLATION1906("quc_Latn_GT"), + COLLATION1907("quz"), + COLLATION1908("quz-BO"), + COLLATION1909("quz_BO"), + COLLATION1910("quz-EC"), + COLLATION1911("quz_EC"), + COLLATION1912("quz-PE"), + COLLATION1913("quz_PE"), + COLLATION1914("rm"), + COLLATION1915("rm-CH"), + COLLATION1916("rm_CH"), + COLLATION1917("rn"), + COLLATION1918("rn-BI"), + COLLATION1919("rn_BI"), + COLLATION1920("ro"), + COLLATION1921("ro-MD"), + COLLATION1922("ro_MD"), + COLLATION1923("ro-RO"), + COLLATION1924("ro_RO"), + COLLATION1925("rof"), + COLLATION1926("rof-TZ"), + COLLATION1927("rof_TZ"), + COLLATION1928("ru"), + COLLATION1929("ru-BY"), + COLLATION1930("ru_BY"), + COLLATION1931("ru-KG"), + COLLATION1932("ru_KG"), + COLLATION1933("ru-KZ"), + COLLATION1934("ru_KZ"), + COLLATION1935("ru-MD"), + COLLATION1936("ru_MD"), + COLLATION1937("ru-RU"), + COLLATION1938("ru_RU"), + COLLATION1939("ru-UA"), + COLLATION1940("ru_UA"), + COLLATION1941("rw"), + COLLATION1942("rw-RW"), + COLLATION1943("rw_RW"), + COLLATION1944("rwk"), + COLLATION1945("rwk-TZ"), + COLLATION1946("rwk_TZ"), + COLLATION1947("sa"), + COLLATION1948("sa-IN"), + COLLATION1949("sa_IN"), + COLLATION1950("sah"), + COLLATION1951("sah-RU"), + COLLATION1952("sah_RU"), + COLLATION1953("saq"), + COLLATION1954("saq-KE"), + COLLATION1955("saq_KE"), + COLLATION1956("sbp"), + COLLATION1957("sbp-TZ"), + COLLATION1958("sbp_TZ"), + COLLATION1959("sd"), + COLLATION1960("sd-Arab"), + COLLATION1961("sd_Arab"), + COLLATION1962("sd-Arab-PK"), + COLLATION1963("sd_Arab_PK"), + COLLATION1964("sd-Deva"), + COLLATION1965("sd_Deva"), + COLLATION1966("sd-Deva-IN"), + COLLATION1967("sd_Deva_IN"), + COLLATION1968("se"), + COLLATION1969("se-FI"), + COLLATION1970("se_FI"), + COLLATION1971("se-NO"), + COLLATION1972("se_NO"), + COLLATION1973("se-SE"), + COLLATION1974("se_SE"), + COLLATION1975("seh"), + COLLATION1976("seh-MZ"), + COLLATION1977("seh_MZ"), + COLLATION1978("ses"), + COLLATION1979("ses-ML"), + COLLATION1980("ses_ML"), + COLLATION1981("sg"), + COLLATION1982("sg-CF"), + COLLATION1983("sg_CF"), + COLLATION1984("shi"), + COLLATION1985("shi-Latn"), + COLLATION1986("shi_Latn"), + COLLATION1987("shi-Latn-MA"), + COLLATION1988("shi_Latn_MA"), + COLLATION1989("shi-Tfng"), + COLLATION1990("shi_Tfng"), + COLLATION1991("shi-Tfng-MA"), + COLLATION1992("shi_Tfng_MA"), + COLLATION1993("si"), + COLLATION1994("si-LK"), + COLLATION1995("si_LK"), + COLLATION1996("sk"), + COLLATION1997("sk-SK"), + COLLATION1998("sk_SK"), + COLLATION1999("sl"), + COLLATION2000("sl-SI"), + COLLATION2001("sl_SI"), + COLLATION2002("sma"), + COLLATION2003("sma-NO"), + COLLATION2004("sma_NO"), + COLLATION2005("sma-SE"), + COLLATION2006("sma_SE"), + COLLATION2007("smj"), + COLLATION2008("smj-NO"), + COLLATION2009("smj_NO"), + COLLATION2010("smj-SE"), + COLLATION2011("smj_SE"), + COLLATION2012("smn"), + COLLATION2013("smn-FI"), + COLLATION2014("smn_FI"), + COLLATION2015("sms"), + COLLATION2016("sms-FI"), + COLLATION2017("sms_FI"), + COLLATION2018("sn"), + COLLATION2019("sn-Latn"), + COLLATION2020("sn_Latn"), + COLLATION2021("sn-Latn-ZW"), + COLLATION2022("sn_Latn_ZW"), + COLLATION2023("so"), + COLLATION2024("so-DJ"), + COLLATION2025("so_DJ"), + COLLATION2026("so-ET"), + COLLATION2027("so_ET"), + COLLATION2028("so-KE"), + COLLATION2029("so_KE"), + COLLATION2030("so-SO"), + COLLATION2031("so_SO"), + COLLATION2032("sq"), + COLLATION2033("sq-AL"), + COLLATION2034("sq_AL"), + COLLATION2035("sq-MK"), + COLLATION2036("sq_MK"), + COLLATION2037("sq-XK"), + COLLATION2038("sq_XK"), + COLLATION2039("sr"), + COLLATION2040("sr-Cyrl"), + COLLATION2041("sr_Cyrl"), + COLLATION2042("sr-Cyrl-BA"), + COLLATION2043("sr_Cyrl_BA"), + COLLATION2044("sr-Cyrl-ME"), + COLLATION2045("sr_Cyrl_ME"), + COLLATION2046("sr-Cyrl-RS"), + COLLATION2047("sr_Cyrl_RS"), + COLLATION2048("sr-Cyrl-XK"), + COLLATION2049("sr_Cyrl_XK"), + COLLATION2050("sr-Latn"), + COLLATION2051("sr_Latn"), + COLLATION2052("sr-Latn-BA"), + COLLATION2053("sr_Latn_BA"), + COLLATION2054("sr-Latn-ME"), + COLLATION2055("sr_Latn_ME"), + COLLATION2056("sr-Latn-RS"), + COLLATION2057("sr_Latn_RS"), + COLLATION2058("sr-Latn-XK"), + COLLATION2059("sr_Latn_XK"), + COLLATION2060("ss"), + COLLATION2061("ss-SZ"), + COLLATION2062("ss_SZ"), + COLLATION2063("ss-ZA"), + COLLATION2064("ss_ZA"), + COLLATION2065("ssy"), + COLLATION2066("ssy-ER"), + COLLATION2067("ssy_ER"), + COLLATION2068("st"), + COLLATION2069("st-LS"), + COLLATION2070("st_LS"), + COLLATION2071("st-ZA"), + COLLATION2072("st_ZA"), + COLLATION2073("sv"), + COLLATION2074("sv-AX"), + COLLATION2075("sv_AX"), + COLLATION2076("sv-FI"), + COLLATION2077("sv_FI"), + COLLATION2078("sv-SE"), + COLLATION2079("sv_SE"), + COLLATION2080("sw"), + COLLATION2081("sw-CD"), + COLLATION2082("sw_CD"), + COLLATION2083("sw-KE"), + COLLATION2084("sw_KE"), + COLLATION2085("sw-TZ"), + COLLATION2086("sw_TZ"), + COLLATION2087("sw-UG"), + COLLATION2088("sw_UG"), + COLLATION2089("syr"), + COLLATION2090("syr-SY"), + COLLATION2091("syr_SY"), + COLLATION2092("ta"), + COLLATION2093("ta-IN"), + COLLATION2094("ta_IN"), + COLLATION2095("ta-LK"), + COLLATION2096("ta_LK"), + COLLATION2097("ta-MY"), + COLLATION2098("ta_MY"), + COLLATION2099("ta-SG"), + COLLATION2100("ta_SG"), + COLLATION2101("te"), + COLLATION2102("te-IN"), + COLLATION2103("te_IN"), + COLLATION2104("teo"), + COLLATION2105("teo-KE"), + COLLATION2106("teo_KE"), + COLLATION2107("teo-UG"), + COLLATION2108("teo_UG"), + COLLATION2109("tg"), + COLLATION2110("tg-Cyrl"), + COLLATION2111("tg_Cyrl"), + COLLATION2112("tg-Cyrl-TJ"), + COLLATION2113("tg_Cyrl_TJ"), + COLLATION2114("th"), + COLLATION2115("th-TH"), + COLLATION2116("th_TH"), + COLLATION2117("ti"), + COLLATION2118("ti-ER"), + COLLATION2119("ti_ER"), + COLLATION2120("ti-ET"), + COLLATION2121("ti_ET"), + COLLATION2122("tig"), + COLLATION2123("tig-ER"), + COLLATION2124("tig_ER"), + COLLATION2125("tk"), + COLLATION2126("tk-TM"), + COLLATION2127("tk_TM"), + COLLATION2128("tn"), + COLLATION2129("tn-BW"), + COLLATION2130("tn_BW"), + COLLATION2131("tn-ZA"), + COLLATION2132("tn_ZA"), + COLLATION2133("to"), + COLLATION2134("to-TO"), + COLLATION2135("to_TO"), + COLLATION2136("tr"), + COLLATION2137("tr-CY"), + COLLATION2138("tr_CY"), + COLLATION2139("tr-TR"), + COLLATION2140("tr_TR"), + COLLATION2141("ts"), + COLLATION2142("ts-ZA"), + COLLATION2143("ts_ZA"), + COLLATION2144("tt"), + COLLATION2145("tt-RU"), + COLLATION2146("tt_RU"), + COLLATION2147("twq"), + COLLATION2148("twq-NE"), + COLLATION2149("twq_NE"), + COLLATION2150("tzm"), + COLLATION2151("tzm-Arab"), + COLLATION2152("tzm_Arab"), + COLLATION2153("tzm-Arab-MA"), + COLLATION2154("tzm_Arab_MA"), + COLLATION2155("tzm-Latn"), + COLLATION2156("tzm_Latn"), + COLLATION2157("tzm-Latn-DZ"), + COLLATION2158("tzm_Latn_DZ"), + COLLATION2159("tzm-Latn-MA"), + COLLATION2160("tzm_Latn_MA"), + COLLATION2161("tzm-Tfng"), + COLLATION2162("tzm_Tfng"), + COLLATION2163("tzm-Tfng-MA"), + COLLATION2164("tzm_Tfng_MA"), + COLLATION2165("ug"), + COLLATION2166("ug-CN"), + COLLATION2167("ug_CN"), + COLLATION2168("uk"), + COLLATION2169("uk-UA"), + COLLATION2170("uk_UA"), + COLLATION2171("ur"), + COLLATION2172("ur-IN"), + COLLATION2173("ur_IN"), + COLLATION2174("ur-PK"), + COLLATION2175("ur_PK"), + COLLATION2176("uz"), + COLLATION2177("uz-Arab"), + COLLATION2178("uz_Arab"), + COLLATION2179("uz-Arab-AF"), + COLLATION2180("uz_Arab_AF"), + COLLATION2181("uz-Cyrl"), + COLLATION2182("uz_Cyrl"), + COLLATION2183("uz-Cyrl-UZ"), + COLLATION2184("uz_Cyrl_UZ"), + COLLATION2185("uz-Latn"), + COLLATION2186("uz_Latn"), + COLLATION2187("uz-Latn-UZ"), + COLLATION2188("uz_Latn_UZ"), + COLLATION2189("vai"), + COLLATION2190("vai-Latn"), + COLLATION2191("vai_Latn"), + COLLATION2192("vai-Latn-LR"), + COLLATION2193("vai_Latn_LR"), + COLLATION2194("vai-Vaii"), + COLLATION2195("vai_Vaii"), + COLLATION2196("vai-Vaii-LR"), + COLLATION2197("vai_Vaii_LR"), + COLLATION2198("ve"), + COLLATION2199("ve-ZA"), + COLLATION2200("ve_ZA"), + COLLATION2201("vi"), + COLLATION2202("vi-VN"), + COLLATION2203("vi_VN"), + COLLATION2204("vo"), + COLLATION2205("vo-001"), + COLLATION2206("vo_001"), + COLLATION2207("vun"), + COLLATION2208("vun-TZ"), + COLLATION2209("vun_TZ"), + COLLATION2210("wae"), + COLLATION2211("wae-CH"), + COLLATION2212("wae_CH"), + COLLATION2213("wal"), + COLLATION2214("wal-ET"), + COLLATION2215("wal_ET"), + COLLATION2216("wo"), + COLLATION2217("wo-SN"), + COLLATION2218("wo_SN"), + COLLATION2219("x-IV_mathan"), + COLLATION2220("x_IV_mathan"), + COLLATION2221("xh"), + COLLATION2222("xh-ZA"), + COLLATION2223("xh_ZA"), + COLLATION2224("xog"), + COLLATION2225("xog-UG"), + COLLATION2226("xog_UG"), + COLLATION2227("yav"), + COLLATION2228("yav-CM"), + COLLATION2229("yav_CM"), + COLLATION2230("yi"), + COLLATION2231("yi-001"), + COLLATION2232("yi_001"), + COLLATION2233("yo"), + COLLATION2234("yo-BJ"), + COLLATION2235("yo_BJ"), + COLLATION2236("yo-NG"), + COLLATION2237("yo_NG"), + COLLATION2238("zgh"), + COLLATION2239("zgh-Tfng"), + COLLATION2240("zgh_Tfng"), + COLLATION2241("zgh-Tfng-MA"), + COLLATION2242("zgh_Tfng_MA"), + COLLATION2243("zu"), + COLLATION2244("zu-ZA"), + COLLATION2245("zu_ZA"), + + + ; + private Collation collation; + + PostgreSQLCollationEnum(String collationName) { + this.collation = new Collation(collationName); + } + + public static List getCollations() { + return Arrays.asList(PostgreSQLCollationEnum.values()).stream().map(PostgreSQLCollationEnum::getCollation).collect(java.util.stream.Collectors.toList()); + } + + public Collation getCollation() { + return collation; + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java new file mode 100644 index 000000000..23977676e --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLColumnTypeEnum.java @@ -0,0 +1,233 @@ +package ai.chat2db.plugin.postgresql.type; + +import ai.chat2db.spi.ColumnBuilder; +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.ColumnType; +import ai.chat2db.spi.model.TableColumn; +import com.google.common.collect.Maps; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +public enum PostgreSQLColumnTypeEnum implements ColumnBuilder { + + BIGSERIAL("BIGSERIAL", false, false, true, false, false, false, true, true, false, false), + BIT("BIT", true, false, true, false, false, false, true, true, false, false), + BOOL("BOOL", false, false, true, false, false, false, true, true, false, false), + BOX("BOX", false, false, true, false, false, false, true, true, false, false), + BYTEA("BYTEA", false, false, true, false, false, false, true, true, false, false), + CHAR("CHAR", true, false, true, false, false, true, true, true, false, false), + CIDR("CIDR", false, false, true, false, false, false, true, true, false, false), + CIRCLE("CIRCLE", false, false, true, false, false, false, true, true, false, false), + DATE("DATE", false, false, true, false, false, false, true, true, false, false), + DECIMAL("DECIMAL", true, false, true, false, false, false, true, true, false, false), + FLOAT4("FLOAT4", false, false, true, false, false, false, true, true, false, false), + FLOAT8("FLOAT8", false, false, true, false, false, false, true, true, false, false), + INET("INET", false, false, true, false, false, false, true, true, false, false), + INT2("INT2", false, false, true, false, false, false, true, true, false, false), + INT4("INT4", false, false, true, false, false, false, true, true, false, false), + INT8("INT8", false, false, true, false, false, false, true, true, false, false), + INTERVAL("INTERVAL", false, false, true, false, false, false, true, true, false, false), + JSON("JSON", false, false, true, false, false, false, true, true, false, false), + JSONB("JSONB", false, false, true, false, false, false, true, true, false, false), + LINE("LINE", false, false, true, false, false, false, true, true, false, false), + LSEG("LSEG", false, false, true, false, false, false, true, true, false, false), + MACADDR("MACADDR", false, false, true, false, false, false, true, true, false, false), + MONEY("MONEY", false, false, true, false, false, false, true, true, false, false), + NUMERIC("NUMERIC", true, false, true, false, false, false, true, true, false, false), + PATH("PATH", false, false, true, false, false, false, true, true, false, false), + POINT("POINT", false, false, true, false, false, false, true, true, false, false), + POLYGON("POLYGON", false, false, true, false, false, false, true, true, false, false), + SERIAL("SERIAL", false, false, true, false, false, false, true, true, false, false), + SERIAL2("SERIAL2", false, false, true, false, false, false, true, true, false, false), + SERIAL4("SERIAL4", false, false, true, false, false, false, true, true, false, false), + SERIAL8("SERIAL8", false, false, true, false, false, false, true, true, false, false), + SMALLSERIAL("SMALLSERIAL", false, false, true, false, false, false, true, true, false, false), + TEXT("TEXT", false, false, true, false, false, true, true, true, false, false), + TIME("TIME", true, false, true, false, false, false, true, true, false, false), + TIMESTAMP("TIMESTAMP", true, false, true, false, false, false, true, true, false, false), + TIMESTAMPTZ("TIMESTAMPTZ", true, false, true, false, false, false, true, true, false, false), + TIMETZ("TIMETZ", true, false, true, false, false, false, true, true, false, false), + TSQUERY("TSQUERY", false, false, true, false, false, false, true, true, false, false), + TSVECTOR("TSVECTOR", false, false, true, false, false, false, true, true, false, false), + TXID_SNAPSHOT("TXID_SNAPSHOT", false, false, true, false, false, false, true, true, false, false), + UUID("UUID", false, false, true, false, false, false, true, true, false, false), + VARBIT("VARBIT", true, false, true, false, false, false, true, true, false, false), + VARCHAR("VARCHAR", true, false, true, false, false, true, true, true, false, false), + XML("XML", false, false, true, false, false, false, true, true, false, false), + + ; + + private static Map COLUMN_TYPE_MAP = Maps.newHashMap(); + + static { + for (PostgreSQLColumnTypeEnum value : PostgreSQLColumnTypeEnum.values()) { + COLUMN_TYPE_MAP.put(value.getColumnType().getTypeName(), value); + } + } + + private ColumnType columnType; + + + PostgreSQLColumnTypeEnum(String dataTypeName, boolean supportLength, boolean supportScale, boolean supportNullable, boolean supportAutoIncrement, boolean supportCharset, boolean supportCollation, boolean supportComments, boolean supportDefaultValue, boolean supportExtent, boolean supportValue) { + this.columnType = new ColumnType(dataTypeName, supportLength, supportScale, supportNullable, supportAutoIncrement, supportCharset, supportCollation, supportComments, supportDefaultValue, supportExtent, supportValue, false); + } + + public static PostgreSQLColumnTypeEnum getByType(String dataType) { + return COLUMN_TYPE_MAP.get(dataType.toUpperCase()); + } + + public static List getTypes() { + return Arrays.stream(PostgreSQLColumnTypeEnum.values()).map(columnTypeEnum -> + columnTypeEnum.getColumnType() + ).toList(); + } + + public ColumnType getColumnType() { + return columnType; + } + + @Override + public String buildCreateColumnSql(TableColumn column) { + PostgreSQLColumnTypeEnum type = COLUMN_TYPE_MAP.get(column.getColumnType().toUpperCase()); + if (type == null) { + return ""; + } + StringBuilder script = new StringBuilder(); + + script.append("\"").append(column.getName()).append("\"").append(" "); + + script.append(buildDataType(column, type)).append(" "); + + + script.append(buildCollation(column, type)).append(" "); + + script.append(buildNullable(column, type)).append(" "); + + script.append(buildDefaultValue(column, type)).append(" "); + + return script.toString(); + } + + private String buildCollation(TableColumn column, PostgreSQLColumnTypeEnum type) { + if (!type.getColumnType().isSupportCollation() || StringUtils.isEmpty(column.getCollationName())) { + return ""; + } + return StringUtils.join("\"", column.getCollationName(), "\""); + } + + @Override + public String buildModifyColumn(TableColumn column) { + + if (EditStatus.DELETE.name().equals(column.getEditStatus())) { + return StringUtils.join("DROP COLUMN `", column.getName() + "`"); + } + if (EditStatus.ADD.name().equals(column.getEditStatus())) { + return StringUtils.join("ADD COLUMN ", buildCreateColumnSql(column)); + } + if (EditStatus.MODIFY.name().equals(column.getEditStatus())) { + StringBuilder script = new StringBuilder(); + script.append("ALTER COLUMN \"").append(column.getName()).append("\" TYPE ").append(buildDataType(column, this)).append(",\n"); + if (column.getNullable() != null && 1 == column.getNullable()) { + script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" DROP NOT NULL ,\n"); + } else { + script.append("\t").append("ALTER COLUMN \"").append(column.getName()).append("\" SET NOT NULL ,\n"); + + } + String defaultValue = buildDefaultValue(column, this); + if (StringUtils.isNotBlank(defaultValue)) { + script.append("ALTER COLUMN \"").append(column.getName()).append("\" SET ").append(defaultValue).append(",\n"); + } + script = new StringBuilder(script.substring(0, script.length() - 2)); + return script.toString(); + } + return ""; + } + + public String buildComment(TableColumn column, PostgreSQLColumnTypeEnum type) { + if (!this.columnType.isSupportComments() || column.getComment() == null + || EditStatus.DELETE.name().equals(column.getEditStatus())) { + return ""; + } + return StringUtils.join("COMMENT ON COLUMN", " \"", column.getTableName(), + "\".\"", column.getName(), "\" IS '", column.getComment(), "';"); + } + + private String buildDefaultValue(TableColumn column, PostgreSQLColumnTypeEnum type) { + if (!type.getColumnType().isSupportDefaultValue() || StringUtils.isEmpty(column.getDefaultValue())) { + return ""; + } + + if("EMPTY_STRING".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT ''"); + } + + if("NULL".equalsIgnoreCase(column.getDefaultValue().trim())){ + return StringUtils.join("DEFAULT NULL"); + } + + if (Arrays.asList(CHAR, VARCHAR).contains(type)) { + return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); + } + + if (Arrays.asList(TIMESTAMP, TIME, TIMETZ, TIMESTAMPTZ, DATE).contains(type)) { + if ("CURRENT_TIMESTAMP".equalsIgnoreCase(column.getDefaultValue().trim())) { + return StringUtils.join("DEFAULT ", column.getDefaultValue()); + } + return StringUtils.join("DEFAULT '", column.getDefaultValue(), "'"); + } + + return StringUtils.join("DEFAULT ", column.getDefaultValue()); + } + + private String buildNullable(TableColumn column, PostgreSQLColumnTypeEnum type) { + if (!type.getColumnType().isSupportNullable()) { + return ""; + } + if (column.getNullable() != null && 1 == column.getNullable()) { + return "NULL"; + } else { + return "NOT NULL"; + } + } + + private String buildDataType(TableColumn column, PostgreSQLColumnTypeEnum type) { + String columnType = type.columnType.getTypeName(); + if (Arrays.asList(VARCHAR, CHAR).contains(type)) { + if (column.getColumnSize() == null ) { + return columnType; + } + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + + if (Arrays.asList(VARBIT, BIT).contains(type)) { + if (column.getColumnSize() == null ) { + return columnType; + } + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + + if (Arrays.asList(TIME, TIMETZ, TIMESTAMPTZ, TIMESTAMP).contains(type)) { + if (column.getColumnSize() == null || column.getColumnSize() == 0) { + return columnType; + } else { + return StringUtils.join(columnType, "(", column.getColumnSize(), ")"); + } + } + + if (Arrays.asList(DECIMAL, NUMERIC).contains(type)) { + if (column.getColumnSize() == null && column.getDecimalDigits() == null) { + return columnType; + } + if (column.getColumnSize() != null && column.getDecimalDigits() == null) { + return StringUtils.join(columnType, "(", column.getColumnSize() + ")"); + } else { + return StringUtils.join(columnType, "(", column.getColumnSize() + "," + column.getDecimalDigits() + ")"); + } + } + return columnType; + } + +} diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java new file mode 100644 index 000000000..4506b1e88 --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java @@ -0,0 +1,165 @@ +package ai.chat2db.plugin.postgresql.type; + +import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.model.TableIndexColumn; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; + +public enum PostgreSQLIndexTypeEnum { + + PRIMARY("Primary", "PRIMARY KEY"), + + FOREIGN("Foreign", "FOREIGN KEY"), + + NORMAL("Normal", "INDEX"), + + UNIQUE("Unique", "UNIQUE"), + ; + + private String name; + private String keyword; + + + PostgreSQLIndexTypeEnum(String name, String keyword) { + this.name = name; + this.keyword = keyword; + } + + public static PostgreSQLIndexTypeEnum getByType(String type) { + for (PostgreSQLIndexTypeEnum value : PostgreSQLIndexTypeEnum.values()) { + if (value.name.equalsIgnoreCase(type)) { + return value; + } + } + return null; + } + + public String getName() { + return name; + } + + public String getKeyword() { + return keyword; + } + + public String buildIndexScript(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + if (NORMAL.equals(this)) { + script.append("CREATE").append(" "); + script.append(buildIndexUnique(tableIndex)).append(" "); + script.append(buildIndexConcurrently(tableIndex)).append(" "); + script.append(buildIndexName(tableIndex)).append(" "); + script.append("ON ").append("\"").append(tableIndex.getTableName()).append("\"").append(" "); + script.append(buildIndexMethod(tableIndex)).append(" "); + script.append(buildIndexColumn(tableIndex)); + } else { + script.append("CONSTRAINT").append(" "); + script.append(buildIndexName(tableIndex)).append(" "); + script.append(keyword).append(" "); + script.append(buildIndexColumn(tableIndex)); + script.append(buildForeignColum(tableIndex)); + } + return script.toString(); + } + + private String buildForeignColum(TableIndex tableIndex) { + if (FOREIGN.equals(this)) { + StringBuilder script = new StringBuilder(); + script.append(" REFERENCES "); + if (StringUtils.isNotBlank(tableIndex.getForeignSchemaName())) { + script.append(tableIndex.getForeignSchemaName()).append("."); + } + if (StringUtils.isNotBlank(tableIndex.getForeignTableName())) { + script.append(tableIndex.getForeignTableName()).append(" "); + } + if (CollectionUtils.isNotEmpty(tableIndex.getForeignColumnNamelist())) { + script.append("("); + for (String column : tableIndex.getForeignColumnNamelist()) { + if (StringUtils.isNotBlank(column)) { + script.append("\"").append(column).append("\"").append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + } + return script.toString(); + } + return ""; + } + + private String buildIndexMethod(TableIndex tableIndex) { + if (StringUtils.isNotBlank(tableIndex.getMethod())) { + return "USING " + tableIndex.getMethod(); + } else { + return ""; + } + } + + private String buildIndexConcurrently(TableIndex tableIndex) { + if (BooleanUtils.isTrue(tableIndex.getConcurrently())) { + return "CONCURRENTLY"; + } else { + return ""; + } + } + + private String buildIndexUnique(TableIndex tableIndex) { + if (BooleanUtils.isTrue(tableIndex.getUnique())) { + return "UNIQUE " + keyword; + } else { + return keyword; + } + } + + public String buildIndexComment(TableIndex tableIndex) { + if (StringUtils.isBlank(tableIndex.getComment()) || EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return ""; + } else if (NORMAL.equals(this)) { + return StringUtils.join("COMMENT ON INDEX", " ", + "\"", tableIndex.getName(), "\" IS '", tableIndex.getComment(), "';"); + } else { + return StringUtils.join("COMMENT ON CONSTRAINT", " \"", tableIndex.getName(), "\" ON \"", tableIndex.getSchemaName(), + "\".\"", tableIndex.getTableName(), "\" IS '", tableIndex.getComment(), "';"); + } + } + + private String buildIndexColumn(TableIndex tableIndex) { + StringBuilder script = new StringBuilder(); + script.append("("); + for (TableIndexColumn column : tableIndex.getColumnList()) { + if (StringUtils.isNotBlank(column.getColumnName())) { + script.append("\"").append(column.getColumnName()).append("\"").append(","); + } + } + script.deleteCharAt(script.length() - 1); + script.append(")"); + return script.toString(); + } + + private String buildIndexName(TableIndex tableIndex) { + return "\"" + tableIndex.getName() + "\""; + } + + public String buildModifyIndex(TableIndex tableIndex) { + boolean isNormal = NORMAL.equals(this); + if (EditStatus.DELETE.name().equals(tableIndex.getEditStatus())) { + return buildDropIndex(tableIndex); + } + if (EditStatus.MODIFY.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(buildDropIndex(tableIndex), isNormal ? ";\n" : ",\n\tADD ", buildIndexScript(tableIndex)); + } + if (EditStatus.ADD.name().equals(tableIndex.getEditStatus())) { + return StringUtils.join(isNormal ? "" : "ADD ", buildIndexScript(tableIndex)); + } + return ""; + } + + private String buildDropIndex(TableIndex tableIndex) { + if (NORMAL.equals(this)) { + return StringUtils.join("DROP INDEX \"", tableIndex.getOldName(), "\""); + } + return StringUtils.join("DROP CONSTRAINT \"", tableIndex.getOldName(), "\""); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java index 3561490fc..4ef9feddf 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/TableIndex.java @@ -65,4 +65,31 @@ public class TableIndex { private String editStatus; + + /** + * 是否并发 + */ + private Boolean concurrently; + + /** + * 索引方法 + */ + private String method; + + + /** + * 外键指向schema + */ + private String foreignSchemaName; + + /** + * 外键指向表名 + */ + private String foreignTableName; + + /** + * 外键指向的列名 + */ + private List foreignColumnNamelist; + } From 077c230b3b25f77d78d57043f98e36797e97eb98 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 11 Oct 2023 10:38:15 +0800 Subject: [PATCH 0859/1069] feat: Optimize --- chat2db-client/src/pages/main/index.tsx | 40 ++++++++++++++----------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 359498ea3..e816c38f4 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -1,27 +1,33 @@ -import React, { useEffect, useState, PropsWithChildren, lazy, Suspense } from 'react'; -import { history, connect } from 'umi'; +import React, { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { connect } from 'umi'; +import { Dropdown } from 'antd'; import classnames from 'classnames'; -import Setting from '@/blocks/Setting'; + import Iconfont from '@/components/Iconfont'; import BrandLogo from '@/components/BrandLogo'; + +import { findObjListValue } from '@/utils'; +import { getUser, userLogout } from '@/service/user'; +import { INavItem } from '@/typings/main'; +import { ILoginUser } from '@/typings/user'; +import i18n from '@/i18n'; + +// ----- model ----- import { IMainPageType } from '@/models/mainPage'; import { IWorkspaceModelType } from '@/models/workspace'; import { IConnectionModelType } from '@/models/connection'; -import { findObjListValue } from '@/utils'; -import { INavItem } from '@/typings/main'; -import Connection from './connection'; + +// ----- block ----- import Workspace from './workspace'; import Dashboard from './dashboard'; +import Connection from './connection'; +import Team from './team'; +import Setting from '@/blocks/Setting'; import styles from './index.less'; -import { getUser, userLogout } from '@/service/user'; -import { ILoginUser } from '@/typings/user'; -import { Dropdown } from 'antd'; -import Team from './team'; -import i18n from '@/i18n'; -import { useNavigate } from 'react-router-dom'; -let navConfig: INavItem[] = [ +const navConfig: INavItem[] = [ { key: 'workspace', icon: '\ue616', @@ -68,14 +74,13 @@ function MainPage(props: IProps) { const { mainModel, dispatch } = props; const { curPage } = mainModel; const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); - // const [activeNav, setActiveNav] = useState(navConfig[4]); const [userInfo, setUserInfo] = useState(); useEffect(() => { getUser().then((res) => { if (res) { setUserInfo(res); - const hasTeamIcon = navConfig.find((i) => i.key === 'team') + const hasTeamIcon = navConfig.find((i) => i.key === 'team'); if (res.admin && !hasTeamIcon) { navConfig.splice(3, 0, { key: 'team', @@ -88,7 +93,7 @@ function MainPage(props: IProps) { setActiveNav(navConfig[3]); } } - if(!res.admin && hasTeamIcon){ + if (!res.admin && hasTeamIcon) { navConfig.splice(3, 1); if (localStorage.getItem('curPage') === 'team') { setActiveNav(navConfig[2]); @@ -171,7 +176,7 @@ function MainPage(props: IProps) {
    {}} className={styles.brandLogo} />
    tables(Connection connection, String databaseName, String sch * @param columnName * @return */ - public List columns(Connection connection, String databaseName, String schemaName, String tableName, + public List columns(Connection connection, String databaseName, String schemaName, String + tableName, String columnName) { try (ResultSet resultSet = connection.getMetaData().getColumns(databaseName, schemaName, tableName, columnName)) { From 2af02c989eb28434c04250f97f72d6007fd2b802 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 11 Oct 2023 15:37:11 +0800 Subject: [PATCH 0869/1069] support select result data update --- .../api/controller/rdb/RdbDmlController.java | 35 +++---------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 59213c2b2..1d59afefd 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -65,38 +65,11 @@ public ListResult manage(@RequestBody DmlRequest request) { @RequestMapping(value = "/execute_update", method = {RequestMethod.POST, RequestMethod.PUT}) public DataResult executeSelectResultUpdate(@RequestBody DmlRequest request) { DlExecuteParam param = rdbWebConverter.request2param(request); - Connection connection = Chat2DBContext.getConnection(); - if (connection != null) { - try { - boolean flag = true; - ExecuteResultVO executeResult = null; - connection.setAutoCommit(false); - ListResult resultDTOListResult = dlTemplateService.execute(param); - List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); - if (!CollectionUtils.isEmpty(resultVOS)) { - for (ExecuteResultVO resultVO : resultVOS) { - if (!resultVO.getSuccess()) { - flag = false; - executeResult = resultVO; - break; - - } - } - } - if (flag) { - connection.commit(); - return DataResult.of(resultVOS.get(0)); - }else { - connection.rollback(); - return DataResult.of(executeResult); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - - } else { - return DataResult.error("connection error", ""); + DataResult result = dlTemplateService.executeUpdate(param); + if(!result.success()){ + return DataResult.error(result.getErrorCode(),result.getErrorMessage()); } + return DataResult.of(rdbWebConverter.dto2vo(result.getData())); } @RequestMapping(value = "/get_update_sql", method = {RequestMethod.POST, RequestMethod.PUT}) From 35d70d1b45a65bae6ecd58eb81ef6bd7d915dba9 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Wed, 11 Oct 2023 15:04:09 +0800 Subject: [PATCH 0870/1069] doc update --- .../server/web/api/controller/ai/EmbeddingController.java | 2 +- .../server/web/api/controller/ai/KnowledgeController.java | 4 ++-- .../web/api/controller/ai/TextGenerationController.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java index e3d9b033d..e9d2e4a7e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java @@ -53,7 +53,7 @@ public class EmbeddingController extends ChatController { private TableService tableService; /** - * save knowledge embeddings from pdf file + * save datasource embeddings * * @param request * @return diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java index e4a35c393..6ff16ee09 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/KnowledgeController.java @@ -51,7 +51,7 @@ public class KnowledgeController extends ChatController { private GatewayClientService gatewayClientService; /** - * save knowledge embeddings from pdf file + * save knowledge from pdf file * * @param file * @return @@ -86,7 +86,7 @@ public ActionResult embeddings(MultipartFile file, HttpServletRequest request) } /** - * search knowledge embeddings + * search knowledge * * @param queryRequest * @return diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java index 506e007d9..0c6180667 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java @@ -27,7 +27,7 @@ public class TextGenerationController extends ChatController { /** - * chat的超时时间 + * chat timeout time */ private static final Long CHAT_TIMEOUT = Duration.ofMinutes(50).toMillis(); From e36c38dea927a191ace34b1feb235b972b03fd43 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 11 Oct 2023 16:07:49 +0800 Subject: [PATCH 0871/1069] Change api name --- chat2db-client/.umirc.ts | 1 + chat2db-client/.vscode/settings.json | 1 + .../src/components/SearchResult/TableBox/index.tsx | 2 +- chat2db-client/src/service/sql.ts | 8 ++++++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 2e9f7176e..55b34350d 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -96,4 +96,5 @@ export default defineConfig({ __APP_VERSION__: yarn_config.app_version || '0.0.0', __APP_PORT__: yarn_config.app_port, }, + esbuildMinifyIIFE: true }); diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 6f942c93b..23f53cd04 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -44,6 +44,7 @@ "hexi", "icns", "Iconfont", + "IIFE", "indexs", "JDBC", "KEYPAIR", diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index fce774d40..9c7d8733d 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -530,7 +530,7 @@ export default function TableBox(props: ITableProps) { schemaName: props.executeSqlParams?.schemaName, tableName: queryResultData.tableName, }; - sqlService.executeDDL(executeSQLParams).then((res) => { + sqlService.executeUpdateDataSql(executeSQLParams).then((res) => { if (res.success) { // 更新成功后,需要重新获取表格数据 getTableData().then(() => { diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 327ba4e84..2cf941a96 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -211,12 +211,16 @@ export interface IModifyTableSqlParams { const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); /** 执行编辑表的sql, 专为编辑表而生 */ -const executeDDL = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); +const executeDDL = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); + +// 执行修改表数据的sql +const executeUpdateDataSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); /** 获取修改表数据的接口 */ -const getExecuteUpdateSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); +const getExecuteUpdateSql = createRequest('/api/rdb/dml/get_update_sql', { method: 'post' }); export default { + executeUpdateDataSql, getExecuteUpdateSql, executeDDL, getModifyTableSql, From 996706cd6a8062572ebe6a7f09e457e89ce22984 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 11 Oct 2023 16:12:11 +0800 Subject: [PATCH 0872/1069] support select result data update --- .../chat2db/server/web/api/controller/rdb/RdbDmlController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 1d59afefd..626663fec 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -73,7 +73,7 @@ public DataResult executeSelectResultUpdate(@RequestBody DmlRe } @RequestMapping(value = "/get_update_sql", method = {RequestMethod.POST, RequestMethod.PUT}) - public DataResult getUpdateSelectResultSql(SelectResultUpdateRequest request) { + public DataResult getUpdateSelectResultSql(@RequestBody SelectResultUpdateRequest request) { UpdateSelectResultParam param = rdbWebConverter.request2param(request); return dlTemplateService.updateSelectResult(param); } From cf82f76fddfd3b3fd38d25e45031851392dd69e8 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 11 Oct 2023 16:32:01 +0800 Subject: [PATCH 0873/1069] fix:Edited data should be restored when deleted --- README.md | 2 +- README_CN.md | 2 +- .../src/components/SearchResult/TableBox/index.tsx | 12 +++++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 32d6e334c..d1dcf1327 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ $ yarn run start:web $ cd ../chat2db-server $ mvn clean install # maven 3.8 or later needs to be installed $ cd chat2db-server/chat2db-server-start/target/ -$ java -jar -Dloader.path=/lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. +$ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar #java 17 or later must be installed, To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. ``` ## 📑 Documentation diff --git a/README_CN.md b/README_CN.md index c3c8c22fa..ef901439f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -160,7 +160,7 @@ $ yarn run start:web $ cd ../chat2db-server $ mvn clean install # 需要安装maven 3.8以上版本 $ cd chat2db-server/chat2db-server-start/target/ -$ java -jar -Dloader.path=/lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 +$ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 需要安装java 17以上版本,启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 ``` ## 📑 文档 diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 9c7d8733d..e1f76842b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -471,6 +471,16 @@ export default function TableBox(props: ITableProps) { if (deleteIndex !== -1) { updateData.splice(deleteIndex, 1); } + + // 如果删除的这个数据时编辑过的,要把这个数据恢复 + setTableData( + tableData.map((item) => + item[`${preCode}0No.`] === curOperationRowNo + ? oldTableData.find((i) => i[`${preCode}0No.`] === curOperationRowNo)! + : item, + ), + ); + setUpdateData([ ...updateData, { @@ -531,7 +541,7 @@ export default function TableBox(props: ITableProps) { tableName: queryResultData.tableName, }; sqlService.executeUpdateDataSql(executeSQLParams).then((res) => { - if (res.success) { + if (res?.success) { // 更新成功后,需要重新获取表格数据 getTableData().then(() => { message.success(i18n('common.text.successfulExecution')); From ae083cdd8f7062b2ef779bc590dac579a273be71 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 11 Oct 2023 16:41:30 +0800 Subject: [PATCH 0874/1069] support select result data update --- .../domain/core/impl/DlTemplateServiceImpl.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 3a4f6df43..d7491d542 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -80,6 +80,7 @@ public ListResult execute(DlExecuteParam param) { @Override public DataResult executeUpdate(DlExecuteParam param) { DataResult dataResult = new DataResult<>(); + dataResult.setSuccess(true); RemoveSpecialGO(param); DbType dbType = JdbcUtils.parse2DruidDbType(Chat2DBContext.getConnectInfo().getDbType()); @@ -89,16 +90,11 @@ public DataResult executeUpdate(DlExecuteParam param) { connection.setAutoCommit(false); for (String originalSql : sqlList) { ExecuteResult executeResult = SQLExecutor.getInstance().executeUpdate(originalSql, connection, 1); - if (!executeResult.getSuccess()) { - dataResult.setSuccess(false); - dataResult.errorCode(executeResult.getDescription()); - dataResult.setErrorMessage(executeResult.getMessage()); - connection.rollback(); - return dataResult; - } + dataResult.setData(executeResult); } connection.commit(); }catch (Exception e){ + log.error("executeUpdate error",e); dataResult.setSuccess(false); dataResult.setErrorCode("connection error"); dataResult.setErrorMessage(e.getMessage()); @@ -262,17 +258,12 @@ private String buildWhere(List
    headerList, List row, MetaData me script.append(" where "); for (int i = 1; i < row.size(); i++) { String oldValue = row.get(i); - if (oldValue == null) { - continue; - } Header header = headerList.get(i); - String value = SqlUtils.getSqlValue(oldValue, header.getDataType()); if (value == null) { script.append(metaSchema.getMetaDataName(header.getName())) .append(" is null and "); } else { - script.append(metaSchema.getMetaDataName(header.getName())) .append(" = ") .append(value) From 15d152bc17007f447e96beb7ad89dc14be13cad6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 11 Oct 2023 17:47:28 +0800 Subject: [PATCH 0875/1069] fix:some bug --- .../src/blocks/SQLExecute/index.less | 9 ---- .../src/blocks/SQLExecute/index.tsx | 52 +++--------------- .../src/components/ExecuteSQL/index.tsx | 5 +- .../SearchResult/TableBox/index.tsx | 42 ++++++++------- .../src/components/SearchResult/index.less | 9 ++++ .../src/components/SearchResult/index.tsx | 54 ++++++++++++------- chat2db-client/src/service/sql.ts | 4 +- 7 files changed, 80 insertions(+), 95 deletions(-) diff --git a/chat2db-client/src/blocks/SQLExecute/index.less b/chat2db-client/src/blocks/SQLExecute/index.less index a082b7261..52b2a3a3b 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.less +++ b/chat2db-client/src/blocks/SQLExecute/index.less @@ -41,12 +41,3 @@ } } } - -.noData { - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - font-size: 12px; -} diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 7ca16da4d..2589d46b2 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -15,7 +15,6 @@ import { v4 as uuidV4 } from 'uuid'; import { Spin } from 'antd'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import i18n from '@/i18n'; -import EmptyImg from '@/assets/img/empty.svg'; interface IProps { className?: string; isActive: boolean; @@ -45,7 +44,6 @@ const SQLExecute = memo((props) => { const draggableRef = useRef(); const [appendValue, setAppendValue] = useState(); const [resultData, setResultData] = useState([]); - const [resultConfig, setResultConfig] = useState([]); const { doubleClickTreeNodeData, curTableList, curWorkspaceParams } = workspaceModel; const [tableLoading, setTableLoading] = useState(false); const controllerRef = useRef(); @@ -71,13 +69,16 @@ const SQLExecute = memo((props) => { setAppendValue({ text: data.initDDL }); }, [data.initDDL]); + useEffect(() => { + console.log(resultData); + }, [resultData]); + /** * 执行SQL * @param sql */ const handleExecuteSQL = async (sql: string) => { setTableLoading(true); - setResultData([]); const executeSQLParams: IExecuteSqlParams = { sql, @@ -96,18 +97,6 @@ const SQLExecute = memo((props) => { uuid: uuidV4(), })); - // 获取当前SQL的总条数 - // let reqDMLCountPromiseArr: Array> = []; - // (sqlResult || []).forEach((res) => { - // const { originalSql } = res; - // let p = sqlServer.getDMLCount({ ...executeSQLParams, sql: originalSql }); - // reqDMLCountPromiseArr.push(p); - // }); - // let reqDMLCountArr = await Promise.all(reqDMLCountPromiseArr); - - setResultConfig( - sqlResult.map((res) => ({ ...defaultResultConfig, total: res.fuzzyTotal, hasNextPage: res.hasNextPage })), - ); setResultData(sqlResult); setTableLoading(false); @@ -119,39 +108,12 @@ const SQLExecute = memo((props) => { historyServer.createHistory(createHistoryParams); }; - /** - * 因为 pageNo、pageSize等信息导致的 - * 单条SQL执行 - */ - // const handleExecuteSQLbyConfigChanged = async (sql: string, config: IResultConfig, index: number) => { - // setTableLoading(true); - // const param = { ...data, ...config, sql }; - // const sqlResult = await sqlServer.executeSql(param); - // resultData[index] = { ...resultData[index], ...sqlResult[0] }; - // setResultData([...resultData]); - // resultConfig[index] = { - // ...config, - // total: isNumber(resultConfig[index].total) ? resultConfig[index].total : sqlResult[0].fuzzyTotal, - // hasNextPage: sqlResult[0].hasNextPage, - // }; - // setResultConfig([...resultConfig]); - // setTableLoading(false); - // }; - const stopExecuteSql = () => { controllerRef.current && controllerRef.current.abort(); + setResultData([]); setTableLoading(false); }; - const renderEmpty = () => { - return ( -
    - -

    {i18n('common.text.noData')}

    -
    - ); - }; - return (
    @@ -193,10 +155,8 @@ const SQLExecute = memo((props) => { {i18n('common.button.cancelRequest')}
    - ) : resultData?.length ? ( - ) : ( - renderEmpty() + )} diff --git a/chat2db-client/src/components/ExecuteSQL/index.tsx b/chat2db-client/src/components/ExecuteSQL/index.tsx index 80c2f10b3..fb5f9eff3 100644 --- a/chat2db-client/src/components/ExecuteSQL/index.tsx +++ b/chat2db-client/src/components/ExecuteSQL/index.tsx @@ -19,6 +19,7 @@ interface IProps { schemaName: string | undefined; tableName?: string; executeSuccessCallBack: () => void; + executeSqlApi?: 'executeUpdateDataSql'; } export default memo((props) => { @@ -31,6 +32,7 @@ export default memo((props) => { dataSourceId, schemaName, tableName, + executeSqlApi = 'executeDDL', executeSuccessCallBack, } = props; const monacoEditorRef = useRef(null); @@ -59,8 +61,7 @@ export default memo((props) => { tableName, }; setExecuteLoading(true); - sqlService - .executeDDL(executeSQLParams) + sqlService[executeSqlApi](executeSQLParams) .then((res) => { if (res.success) { executeSuccessCallBack?.(); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index e1f76842b..c97775bc6 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -96,6 +96,8 @@ export default function TableBox(props: ITableProps) { const [viewUpdateDataSqlModal, setViewUpdateDataSqlModal] = useState(false); // 用于滚动到底部 const tableBoxRef = React.useRef(null); + // 所有数据准备好了 + const [allDataReady, setAllDataReady] = useState(false); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -124,6 +126,7 @@ export default function TableBox(props: ITableProps) { const newTableData = dataListTransformTableData(queryResultData.dataList); setTableData(newTableData); setOldTableData(newTableData); + setAllDataReady(true); } // 每次data变化,都需要重新计算oldDataList if (queryResultData.dataList?.length) { @@ -398,7 +401,7 @@ export default function TableBox(props: ITableProps) { const onClickTotalBtn = async () => { return sqlService.getDMLCount({ - sql: queryResultData.originalSql, + sql: queryResultData.sql, ...(props.executeSqlParams || {}), }); }; @@ -548,7 +551,7 @@ export default function TableBox(props: ITableProps) { setUpdateData([]); }); } else { - setUpdateDataSql(res.originalSql); + setUpdateDataSql(res.sql); setViewUpdateDataSqlModal(true); setInitError(res.message); } @@ -717,22 +720,24 @@ export default function TableBox(props: ITableProps) { -

    {i18n('common.text.noData')}

    }} - isStickyHead - stickyTop={31} - getRowProps={(record) => { - const rowNo = record[`${preCode}0No.`]; - return { - style: tableRowStyle(rowNo), - onClick() { - setCurOperationRowNo(rowNo); - }, - }; - }} - {...pipeline.getProps()} - /> + {allDataReady && ( +

    {i18n('common.text.noData')}

    }} + isStickyHead + stickyTop={31} + getRowProps={(record) => { + const rowNo = record[`${preCode}0No.`]; + return { + style: tableRowStyle(rowNo), + onClick() { + setCurOperationRowNo(rowNo); + }, + }; + }} + {...pipeline.getProps()} + /> + )} {bottomStatus} ); @@ -794,6 +799,7 @@ export default function TableBox(props: ITableProps) { schemaName={props.executeSqlParams?.schemaName} databaseType={props.executeSqlParams?.databaseType} executeSuccessCallBack={executeSuccessCallBack} + executeSqlApi="executeUpdateDataSql" /> {contextHolder} diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 551a6a8c7..c5cd0bda8 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -47,3 +47,12 @@ align-items: center; justify-content: center; } + +.noData { + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 12px; +} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index ba844df71..4802a37bd 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; +import React, { memo, useCallback, useMemo, useState } from 'react'; import classnames from 'classnames'; import TabsNew from '@/components/TabsNew'; import Iconfont from '@/components/Iconfont'; @@ -6,6 +6,9 @@ import StateIndicator from '@/components/StateIndicator'; import { IManageResultData } from '@/typings'; import TableBox from './TableBox'; import styles from './index.less'; +import EmptyImg from '@/assets/img/empty.svg'; +import i18n from '@/i18n'; +import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { className?: string; @@ -16,9 +19,9 @@ interface IProps { export default memo((props) => { const { className, queryResultDataList = [] } = props; const [currentTab, setCurrentTab] = useState(); - const [resultDataList, setResultDataList] = useState([]); + const [resultDataList, setResultDataList] = useState(queryResultDataList); - useEffect(() => { + useUpdateEffect(() => { if (!queryResultDataList.length) { return; } @@ -26,7 +29,7 @@ export default memo((props) => { if (!currentTab || !queryResultDataList.find((d) => d.uuid === currentTab)) { setCurrentTab(queryResultDataList[0].uuid); } - + console.log([...queryResultDataList]); setResultDataList([...queryResultDataList]); }, [queryResultDataList]); @@ -66,29 +69,44 @@ export default memo((props) => { }); }, [resultDataList]); - const outputTab = useMemo(() => { - return { - prefixIcon: ( - - ), - popover: 'output', - label: 'output', - key: 'output', - children:
    output
    , - }; - }, []); + const onEdit = useCallback( + (type: 'add' | 'remove', value) => { + if (type === 'remove') { + const newResultDataList = resultDataList.filter((d) => d.uuid !== value.key); + setResultDataList(newResultDataList); + } + }, + [resultDataList], + ); + + // const outputTab = useMemo(() => { + // return { + // prefixIcon: ( + // + // ), + // popover: 'output', + // label: 'output', + // key: 'output', + // children:
    output
    , + // }; + // }, []); return (
    - {!!tabsList.length && ( + {tabsList.length ? ( + ) : ( +
    + +

    {i18n('common.text.noData')}

    +
    )}
    ); diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 2cf941a96..7aa35738a 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -214,15 +214,15 @@ const getModifyTableSql = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); // 执行修改表数据的sql -const executeUpdateDataSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); +const executeUpdateDataSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); /** 获取修改表数据的接口 */ const getExecuteUpdateSql = createRequest('/api/rdb/dml/get_update_sql', { method: 'post' }); export default { executeUpdateDataSql, - getExecuteUpdateSql, executeDDL, + getExecuteUpdateSql, getModifyTableSql, getTableDetails, getDatabaseFieldTypeList, From d9bccfb667f0894f2123194d69c01ca530fe7784 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 11 Oct 2023 17:56:00 +0800 Subject: [PATCH 0876/1069] feat: edit table auto focus --- .../src/components/ExecuteSQL/index.tsx | 2 +- .../components/SearchResult/TableBox/index.tsx | 15 ++++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/chat2db-client/src/components/ExecuteSQL/index.tsx b/chat2db-client/src/components/ExecuteSQL/index.tsx index fb5f9eff3..33c487f83 100644 --- a/chat2db-client/src/components/ExecuteSQL/index.tsx +++ b/chat2db-client/src/components/ExecuteSQL/index.tsx @@ -19,7 +19,7 @@ interface IProps { schemaName: string | undefined; tableName?: string; executeSuccessCallBack: () => void; - executeSqlApi?: 'executeUpdateDataSql'; + executeSqlApi?: 'executeUpdateDataSql'; // 两个地方用到了这个组件,但是两个需要的执行sql的接口不一样 } export default memo((props) => { diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index c97775bc6..a6f3820d8 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -98,6 +98,8 @@ export default function TableBox(props: ITableProps) { const tableBoxRef = React.useRef(null); // 所有数据准备好了 const [allDataReady, setAllDataReady] = useState(false); + // 编辑数据的inputRef + const editDataInputRef = React.useRef(null); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -201,6 +203,9 @@ export default function TableBox(props: ITableProps) { } setEditingData(value); setEditingCell([colIndex, rowNo]); + setTimeout(() => { + editDataInputRef?.current?.focus(); + }, 0); }; // 编辑数据失焦 @@ -343,6 +348,7 @@ export default function TableBox(props: ITableProps) { > {editingCell?.join(',') === `${colIndex},${rowNo}` ? ( { setEditingData(e.target.value); @@ -754,15 +760,6 @@ export default function TableBox(props: ITableProps) { width="60vw" maskClosable={false} footer={false} - // footer={ - // <> - // { - // - // } - // - // } >
    Date: Wed, 11 Oct 2023 18:14:02 +0800 Subject: [PATCH 0877/1069] fix:edit table bug --- .../src/blocks/DatabaseTableEditor/index.tsx | 33 ++++++++++--------- .../src/components/ExecuteSQL/index.tsx | 2 +- chat2db-client/src/service/sql.ts | 4 +-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 1d411b63a..a9af26827 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -100,7 +100,7 @@ export default memo((props: IProps) => { useEffect(() => { if (tableName) { - getTableDetails({}); + getTableDetails(); } getDatabaseFieldTypeList(); }, []); @@ -155,20 +155,23 @@ export default memo((props: IProps) => { }); }; - const getTableDetails = ({ tableNameProps }: { tableNameProps?: string }) => { - if (!tableName) return; - const params = { - databaseName, - dataSourceId, - tableName: tableNameProps || tableName, - schemaName, - refresh: true, - }; - sqlService.getTableDetails(params).then((res) => { - const newTableDetails = lodash.cloneDeep(res); - setTableDetails(newTableDetails || {}); - setOldTableDetails(res); - }); + const getTableDetails = (myParams?: { tableNameProps?: string }) => { + const { tableNameProps } = myParams || {}; + const myTableName = tableNameProps || tableName; + if (myTableName) { + const params = { + databaseName, + dataSourceId, + tableName: myTableName, + schemaName, + refresh: true, + }; + sqlService.getTableDetails(params).then((res) => { + const newTableDetails = lodash.cloneDeep(res); + setTableDetails(newTableDetails || {}); + setOldTableDetails(res); + }); + } }; function submit() { diff --git a/chat2db-client/src/components/ExecuteSQL/index.tsx b/chat2db-client/src/components/ExecuteSQL/index.tsx index 33c487f83..746b2bfc8 100644 --- a/chat2db-client/src/components/ExecuteSQL/index.tsx +++ b/chat2db-client/src/components/ExecuteSQL/index.tsx @@ -16,7 +16,7 @@ interface IProps { databaseType: DatabaseTypeCode; databaseName: string; dataSourceId: number; - schemaName: string | undefined; + schemaName?: string | null; tableName?: string; executeSuccessCallBack: () => void; executeSqlApi?: 'executeUpdateDataSql'; // 两个地方用到了这个组件,但是两个需要的执行sql的接口不一样 diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 7aa35738a..3aae6fc1d 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -15,7 +15,7 @@ export interface IExecuteSqlParams { consoleId?: number; dataSourceId?: number; databaseName?: string; - schemaName?: string; + schemaName?: string | null; tableName?: string; pageNo?: number; pageSize?: number; @@ -192,7 +192,7 @@ const getDatabaseFieldTypeList = createRequest<{ const getTableDetails = createRequest<{ dataSourceId: number; databaseName: string; - schemaName?: string; + schemaName?: string | null; tableName: string; refresh: boolean; }, IEditTableInfo>('/api/rdb/table/query', { method: 'get' }); From f8ccf596d4fbd387eef56fe73b524ebcc4d94f19 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 11 Oct 2023 18:21:31 +0800 Subject: [PATCH 0878/1069] fix:package.json add electron --- chat2db-client/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 16eac87af..76cae2ef9 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -58,6 +58,7 @@ "@umijs/plugins": "^4.0.55", "concurrently": "^8.1.0", "cross-env": "^7.0.3", + "electron": "^22.3.0", "electron-builder": "^23.6.0", "electron-debug": "^3.2.0", "electron-reload": "^2.0.0-alpha.1", From d50f9961ba9e17a250ab65e0c69b632ff6d7f7e6 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 11 Oct 2023 20:58:52 +0800 Subject: [PATCH 0879/1069] support table page query --- .../domain/api/param/TablePageQueryParam.java | 3 + .../domain/api/param/TableQueryParam.java | 2 + .../server/domain/core/cache/CacheKey.java | 12 ++ .../domain/core/impl/TableServiceImpl.java | 137 ++++++++++++++++-- .../repository/entity/TableCacheDO.java | 83 +++++++++++ .../entity/TableCacheVersionDO.java | 78 ++++++++++ .../repository/mapper/TableCacheMapper.java | 19 +++ .../mapper/TableCacheVersionMapper.java | 16 ++ .../resources/mapper/TableCacheMapper.xml | 13 ++ .../mapper/TableCacheVersionMapper.xml | 5 + .../test/mybatis/MybatisGeneratorTest.java | 2 +- .../rdb/request/TableBriefQueryRequest.java | 1 + 12 files changed, 360 insertions(+), 11 deletions(-) create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml create mode 100644 chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheVersionMapper.xml diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java index f2102773a..d93c9beff 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TablePageQueryParam.java @@ -47,4 +47,7 @@ public class TablePageQueryParam extends PageQueryParam { * if true, refresh the cache */ private boolean refresh; + + + private String searchKey; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java index 452eb58d7..4f6e3b4cd 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/TableQueryParam.java @@ -44,4 +44,6 @@ public class TableQueryParam extends QueryParam { * 空间名 */ private String schemaName; + + private boolean refresh; } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java index 024b1bbda..f4f5126a9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java @@ -31,4 +31,16 @@ public static String getTableKey(Long dataSourceId, String databaseName, String } return stringBuffer.toString(); } + + public static String getColumnKey(Long dataSourceId, String databaseName, String schemaName,String tableName) { + StringBuffer stringBuffer = new StringBuffer("columns_dataSourceId" + dataSourceId); + if (!StringUtils.isEmpty(databaseName)) { + stringBuffer.append("_databaseName" + databaseName); + } + if (!StringUtils.isEmpty(schemaName)) { + stringBuffer.append("_schemaName" + schemaName); + } + stringBuffer.append("_tableName"+tableName); + return stringBuffer.toString(); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index b4858c8ce..b13e47652 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -1,5 +1,8 @@ package ai.chat2db.server.domain.core.impl; +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -11,6 +14,12 @@ import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.domain.core.converter.PinTableConverter; +import ai.chat2db.server.domain.repository.entity.TableCacheDO; +import ai.chat2db.server.domain.repository.entity.TableCacheVersionDO; +import ai.chat2db.server.domain.repository.entity.TeamDO; +import ai.chat2db.server.domain.repository.entity.TeamUserDO; +import ai.chat2db.server.domain.repository.mapper.TableCacheMapper; +import ai.chat2db.server.domain.repository.mapper.TableCacheVersionMapper; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; @@ -21,12 +30,19 @@ import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.util.ResultSetUtils; import ai.chat2db.spi.util.SqlUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import static ai.chat2db.server.domain.core.cache.CacheKey.getColumnKey; import static ai.chat2db.server.domain.core.cache.CacheKey.getTableKey; /** @@ -35,6 +51,7 @@ * @date 2022/09/23 */ @Service +@Slf4j public class TableServiceImpl implements TableService { @Autowired @@ -43,6 +60,12 @@ public class TableServiceImpl implements TableService { @Autowired private PinTableConverter pinTableConverter; + @Autowired + private TableCacheMapper tableCacheMapper; + + @Autowired + private TableCacheVersionMapper tableCacheVersionMapper; + @Override public DataResult showCreateTable(ShowCreateTableParam param) { MetaData metaSchema = Chat2DBContext.getMetaData(); @@ -112,19 +135,110 @@ private void initOldTable(Table oldTable, Table newTable) { @Override public PageResult
    pageQuery(TablePageQueryParam param, TableSelector selector) { - MetaData metaSchema = Chat2DBContext.getMetaData(); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TableCacheVersionDO::getKey, buildKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + TableCacheVersionDO versionDO = tableCacheVersionMapper.selectOne(queryWrapper); + if (param.isRefresh() || versionDO == null) { + addDBCache(param, versionDO); + } + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.eq(TableCacheDO::getVersion, versionDO.getVersion()); + query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); + if (StringUtils.isNotBlank(param.getDatabaseName())) { + query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); + } + if (StringUtils.isNotBlank(param.getSchemaName())) { + query.eq(TableCacheDO::getSchemaName, param.getSchemaName()); + } + if (StringUtils.isNotBlank(param.getSearchKey())) { + query.like(TableCacheDO::getTableName, param.getSearchKey()); + } + Page page = new Page<>(param.getPageNo(), param.getPageSize()); + // page.setSearchCount(param.getEnableReturnCount()); + IPage iPage = tableCacheMapper.selectPage(page, query); + List
    tables = new ArrayList<>(); + if (CollectionUtils.isNotEmpty(iPage.getRecords())) { + for (TableCacheDO tableCacheDO : iPage.getRecords()) { + Table t = new Table(); + t.setName(tableCacheDO.getTableName()); + t.setComment(tableCacheDO.getExtendInfo()); + t.setSchemaName(tableCacheDO.getSchemaName()); + t.setDatabaseName(tableCacheDO.getDatabaseName()); + tables.add(t); + } + } + return PageResult.of(tables, versionDO.getTableCount(), param); + } - String tableKey = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); + private void addDBCache(TablePageQueryParam param, TableCacheVersionDO versionDO) { + if (versionDO == null) { + versionDO = new TableCacheVersionDO(); + versionDO.setDatabaseName(param.getDatabaseName()); + versionDO.setSchemaName(param.getSchemaName()); + versionDO.setDataSourceId(param.getDataSourceId()); + versionDO.setStatus("2"); + versionDO.setKey(buildKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + versionDO.setVersion(0L); + versionDO.setTableCount(0L); + tableCacheVersionMapper.insert(versionDO); + } else { + versionDO.setVersion(versionDO.getVersion() + 1); + versionDO.setStatus("2"); + tableCacheVersionMapper.updateById(versionDO); + } + Connection connection = Chat2DBContext.getConnection(); + long n = 0; + try (ResultSet resultSet = connection.getMetaData().getTables(param.getDatabaseName(), param.getSchemaName(), param.getTableName(), + new String[]{"TABLE"})) { + List cacheDOS = new ArrayList<>(); + while (resultSet.next()) { + TableCacheDO tableCacheDO = new TableCacheDO(); + tableCacheDO.setDatabaseName(resultSet.getString("TABLE_CAT")); + tableCacheDO.setSchemaName(resultSet.getString("TABLE_SCHEM")); + tableCacheDO.setTableName(resultSet.getString("TABLE_NAME")); + tableCacheDO.setExtendInfo(resultSet.getString("REMARKS")); + tableCacheDO.setDataSourceId(param.getDataSourceId()); + tableCacheDO.setVersion(versionDO.getVersion()); + tableCacheDO.setKey(buildKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + cacheDOS.add(tableCacheDO); + if (cacheDOS.size() >= 500) { + tableCacheMapper.batchInsert(cacheDOS); + cacheDOS = new ArrayList<>(); + } + n++; + } + if (!CollectionUtils.isEmpty(cacheDOS)) { + tableCacheMapper.batchInsert(cacheDOS); + } + versionDO.setStatus("1"); + versionDO.setTableCount(n); + tableCacheVersionMapper.updateById(versionDO); + LambdaQueryWrapper q = new LambdaQueryWrapper(); + q.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); + q.lt(TableCacheDO::getVersion, versionDO.getVersion()); + if (param.getDatabaseName() != null) { + q.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); + } + if (param.getSchemaName() != null) { + q.eq(TableCacheDO::getSchemaName, param.getSchemaName()); + } + tableCacheMapper.delete(q); + } catch (SQLException e) { + throw new RuntimeException(e); + } - List
    list = CacheManage.getList(tableKey, Table.class, - (key) -> param.isRefresh(), (key) -> - metaSchema.tables(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); + } - list = pinTable(list, param); - if (CollectionUtils.isEmpty(list)) { - return PageResult.of(list, 0L, param); + + private String buildKey(Long dataSourceId, String databaseName, String schemaName) { + StringBuilder stringBuilder = new StringBuilder(dataSourceId.toString()); + if (StringUtils.isNotBlank(databaseName)) { + stringBuilder.append("_").append(databaseName); + } + if (StringUtils.isNotBlank(schemaName)) { + stringBuilder.append("_").append(schemaName); } - return PageResult.of(list, Long.valueOf(list.size()), param); + return stringBuilder.toString(); } private List
    pinTable(List
    list, TablePageQueryParam param) { @@ -157,8 +271,11 @@ private List
    pinTable(List
    list, TablePageQueryParam param) { @Override public List queryColumns(TableQueryParam param) { + String tableColumnKey = getColumnKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), param.getTableName()); MetaData metaSchema = Chat2DBContext.getMetaData(); - return metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName(), null); + return CacheManage.getList(tableColumnKey, TableColumn.class, + (key) -> param.isRefresh(), (key) -> + metaSchema.columns(Chat2DBContext.getConnection(), param.getDatabaseName(), param.getSchemaName(), param.getTableName())); } @Override diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java new file mode 100644 index 000000000..5eace2195 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheDO.java @@ -0,0 +1,83 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Getter; +import lombok.Setter; + +/** + *

    + * table cache + *

    + * + * @author chat2db + * @since 2023-10-11 + */ +@Getter +@Setter +@TableName("TABLE_CACHE") +public class TableCacheDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private Date gmtCreate; + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 数据源连接ID + */ + private Long dataSourceId; + + /** + * db名称 + */ + private String databaseName; + + /** + * schema名称 + */ + private String schemaName; + + /** + * table名称 + */ + private String tableName; + + /** + * 唯一索引 + */ + @TableField(value = "`key`") + private String key; + + /** + * 版本 + */ + private Long version; + + /** + * 表字段 + */ + private String columns; + + /** + * 自定义扩展字段json + */ + private String extendInfo; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java new file mode 100644 index 000000000..9c3d4e0f7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/entity/TableCacheVersionDO.java @@ -0,0 +1,78 @@ +package ai.chat2db.server.domain.repository.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Getter; +import lombok.Setter; + +/** + *

    + * table cache version + *

    + * + * @author chat2db + * @since 2023-10-11 + */ +@Getter +@Setter +@TableName("TABLE_CACHE_VERSION") +public class TableCacheVersionDO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键 + */ + @TableId(value = "ID", type = IdType.AUTO) + private Long id; + + /** + * 创建时间 + */ + private Date gmtCreate; + + /** + * 修改时间 + */ + private Date gmtModified; + + /** + * 数据源连接ID + */ + private Long dataSourceId; + + /** + * db名称 + */ + private String databaseName; + + /** + * schema名称 + */ + private String schemaName; + + /** + * 唯一索引 + */ + @TableField(value = "`key`") + private String key; + + /** + * 版本 + */ + private Long version; + + /** + * 表数量 + */ + private Long tableCount; + + /** + * 状态 + */ + private String status; +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java new file mode 100644 index 000000000..191c85fe8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java @@ -0,0 +1,19 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.TableCacheDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; + +/** + *

    + * table cache Mapper 接口 + *

    + * + * @author chat2db + * @since 2023-10-11 + */ +public interface TableCacheMapper extends BaseMapper { + + void batchInsert(List list); +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java new file mode 100644 index 000000000..5ac07037f --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheVersionMapper.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.domain.repository.mapper; + +import ai.chat2db.server.domain.repository.entity.TableCacheVersionDO; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

    + * table cache version Mapper 接口 + *

    + * + * @author chat2db + * @since 2023-10-11 + */ +public interface TableCacheVersionMapper extends BaseMapper { + +} diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml new file mode 100644 index 000000000..10d8441c7 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml @@ -0,0 +1,13 @@ + + + + + + insert into TABLE_CACHE + (data_source_id,database_name,schema_name,table_name,`key`,version,columns,extend_info) + values + + (#{item.dataSourceId},#{item.databaseName},#{item.schemaName},#{item.tableName},#{item.key},#{item.version},#{item.columns},#{item.extendInfo}) + + + diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheVersionMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheVersionMapper.xml new file mode 100644 index 000000000..ccbf193d8 --- /dev/null +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheVersionMapper.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java index f7c655dfe..6e991296f 100644 --- a/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java +++ b/chat2db-server/chat2db-server-start/src/test/java/ai/chat2db/server/start/test/mybatis/MybatisGeneratorTest.java @@ -35,7 +35,7 @@ public void coreGenerator() { //doGenerator(Lists.newArrayList("operation_saved")); //doGenerator(Lists.newArrayList("environment","data_source","team","team_dbhub_user","data_source_access", // "dbhub_user")); - doGenerator(Lists.newArrayList("table_cache_version","table_cache")); + doGenerator(Lists.newArrayList("table_cache")); } private void doGenerator(List tableList) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java index 0b74b7868..ed3395630 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/TableBriefQueryRequest.java @@ -43,4 +43,5 @@ public class TableBriefQueryRequest extends PageQueryRequest implements DataSour * if true, refresh the cache */ private boolean refresh; + } From bd00bd5aefe0d8c2ed812ced962b67adc95849ef Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 11 Oct 2023 21:05:29 +0800 Subject: [PATCH 0880/1069] feat: init output --- .github/workflows/release.yml | 2 +- .github/workflows/release_test.yml | 2 +- .../src/components/Iconfont/index.tsx | 6 +- .../src/components/Output/index.less | 46 ++++ .../src/components/Output/index.tsx | 202 ++++++++++++++++++ .../SearchResult/TableBox/index.tsx | 1 - .../src/components/SearchResult/index.less | 4 + .../src/components/SearchResult/index.tsx | 48 ++--- .../src/components/TabsNew/index.tsx | 51 +++-- 9 files changed, 309 insertions(+), 53 deletions(-) create mode 100644 chat2db-client/src/components/Output/index.less create mode 100644 chat2db-client/src/components/Output/index.tsx diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da04d5610..730645742 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -278,7 +278,7 @@ jobs: endpoint: "oss-accelerate.aliyuncs.com" access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} - ossutil-version: "latest" + ossutil-version: "1.7.16" - name: Upload to oss run: | ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/release/${{ steps.chat2db_version.outputs.substring }}/ diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index d0f47abc0..e518c8093 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -268,7 +268,7 @@ jobs: endpoint: "oss-accelerate.aliyuncs.com" access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} - ossutil-version: "latest" + 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 }}/ diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 581e9906a..f06bba51c 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_h0xsun1lem.woff2?t=1696988651255') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_h0xsun1lem.woff?t=1696988651255') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_h0xsun1lem.ttf?t=1696988651255') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_rr2udfcdk5.woff2?t=1697021469012') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_rr2udfcdk5.woff?t=1697021469012') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_rr2udfcdk5.ttf?t=1697021469012') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/Output/index.less b/chat2db-client/src/components/Output/index.less new file mode 100644 index 000000000..5339e0905 --- /dev/null +++ b/chat2db-client/src/components/Output/index.less @@ -0,0 +1,46 @@ +@import '../../styles/var.less'; + +.output { + overflow: auto; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + :global { + .ant-table-body { + border-top: 0px; + border-bottom: 0px; + } + .ant-table-wrapper .ant-table .ant-table-header { + border-radius: 0px; + } + .ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:first-child { + border-top-left-radius: 0px; + } + .ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:last-child { + border-top-right-radius: 0px; + } + .ant-table-header { + border-bottom: 0px; + } + .ant-table { + border-radius: 10px; + border-bottom: 0px; + } + .ant-table-wrapper .ant-table-tbody > tr > td { + padding: 4px 2px; + } + .ant-table-wrapper .ant-table-thead > tr > th { + padding: 8px 4px; + &::before { + display: none; + } + } + .ant-table-wrapper .ant-table-thead > tr > td { + &::before { + display: none; + } + } + } +} diff --git a/chat2db-client/src/components/Output/index.tsx b/chat2db-client/src/components/Output/index.tsx new file mode 100644 index 000000000..c7ee08bc5 --- /dev/null +++ b/chat2db-client/src/components/Output/index.tsx @@ -0,0 +1,202 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { Table } from 'antd'; + +interface IProps { + className?: string; + dataSourcesId?: string; +} + +// 获取Output的参数 +export interface IGetOutputParams { + dataSourcesId?: string; + databaseName?: string; +} + +export default memo((props) => { + const { className } = props; + const dataSource = [ + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '1', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + { + key: '2', + time: '2022-10-11 12:00:00', + sql: 'select * from test', + executionTime: '100ms', + databaseName: 'sys', + state: '成功', + }, + ]; + + const columns = [ + { + title: '', + dataIndex: 'No', + width: 50, + key: 'No', + align: 'center', + render: (text: any, record: any, index: number) => { + return {index + 1}; + }, + }, + { + title: '开始时间', + dataIndex: 'time', + key: 'time', + }, + { + title: '数据库/schema', + dataIndex: 'databaseName', + key: 'databaseName', + }, + { + title: '状态', + dataIndex: 'state', + key: 'state', + }, + { + title: 'sql', + dataIndex: 'sql', + key: 'sql', + }, + { + title: '执行耗时', + dataIndex: 'executionTime', + key: 'executionTime', + }, + ]; + + return ( +
    +
    + + ); +}); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index a6f3820d8..4e9a0a528 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -338,7 +338,6 @@ export default function TableBox(props: ITableProps) { code: `${preCode}${colIndex}${name}`, name: name, key: name, - width: 120, render: (value: any, rowData) => { const rowNo = rowData[`${preCode}0No.`]; return ( diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index c5cd0bda8..e15fd19c8 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -56,3 +56,7 @@ align-items: center; font-size: 12px; } + +.outputPrefixIcon{ + margin-right: 4px; +} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 4802a37bd..1cafd485c 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,14 +1,14 @@ -import React, { memo, useCallback, useMemo, useState } from 'react'; +import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; import classnames from 'classnames'; import TabsNew from '@/components/TabsNew'; import Iconfont from '@/components/Iconfont'; import StateIndicator from '@/components/StateIndicator'; +import Output from '@/components/Output'; import { IManageResultData } from '@/typings'; import TableBox from './TableBox'; import styles from './index.less'; import EmptyImg from '@/assets/img/empty.svg'; import i18n from '@/i18n'; -import { useUpdateEffect } from '@/hooks/useUpdateEffect'; interface IProps { className?: string; @@ -21,20 +21,13 @@ export default memo((props) => { const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState(queryResultDataList); - useUpdateEffect(() => { - if (!queryResultDataList.length) { - return; - } - - if (!currentTab || !queryResultDataList.find((d) => d.uuid === currentTab)) { - setCurrentTab(queryResultDataList[0].uuid); - } - console.log([...queryResultDataList]); - setResultDataList([...queryResultDataList]); + useEffect(() => { + setCurrentTab(queryResultDataList[0]?.uuid); }, [queryResultDataList]); const onChange = useCallback((uuid: string | number) => { - setCurrentTab(uuid); + console.log('onChange', uuid); + // setCurrentTab(uuid); }, []); const renderTable = (queryResultData) => { @@ -79,28 +72,31 @@ export default memo((props) => { [resultDataList], ); - // const outputTab = useMemo(() => { - // return { - // prefixIcon: ( - // - // ), - // popover: 'output', - // label: 'output', - // key: 'output', - // children:
    output
    , - // }; - // }, []); + const outputTabAndTabsList = useMemo(() => { + return [ + { + prefixIcon: , + label: 'Output', + key: 'output', + children: , + styles: { width: '80px' }, + canClosed: false, + }, + ...tabsList, + ]; + }, [tabsList]); return (
    - {tabsList.length ? ( + {outputTabAndTabsList.length ? ( ) : (
    diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 999e22c5c..9c5db7009 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -11,6 +11,8 @@ export interface ITabItem { popover?: string | React.ReactNode; children?: React.ReactNode; editableName?: boolean | undefined; + canClosed?: boolean; + styles?: React.CSSProperties; } export interface IOnchangeProps { @@ -20,20 +22,22 @@ export interface IOnchangeProps { interface IProps { className?: string; - items: ITabItem[] | undefined; + items?: ITabItem[]; activeKey?: number | string; onChange?: (key: string | number | undefined) => void; onEdit?: (action: 'add' | 'remove', data?: ITabItem, list?: ITabItem[]) => void; hideAdd?: boolean; type?: 'line'; editableNameOnBlur?: (option: ITabItem) => void; + concealTabHeader?: boolean; } export default memo((props) => { - const { className, items, onChange, onEdit, activeKey, hideAdd, type, editableNameOnBlur } = props; + const { className, items, onChange, onEdit, activeKey, hideAdd, type, editableNameOnBlur, concealTabHeader } = props; const [internalTabs, setInternalTabs] = useState([]); const [internalActiveTab, setInternalActiveTab] = useState(); const [editingTab, setEditingTab] = useState(); + console.log('items', items); useEffect(() => { if (activeKey !== null && activeKey !== undefined) { @@ -44,7 +48,7 @@ export default memo((props) => { useEffect(() => { setInternalTabs(items || []); if (items?.length && internalActiveTab === undefined) { - setInternalActiveTab(items[0].key); + setInternalActiveTab(items[0]?.key); } }, [items]); @@ -100,6 +104,7 @@ export default memo((props) => { onDoubleClick={() => { onDoubleClick(t); }} + style={t.styles} className={classnames( { [styles.tabItem]: type !== 'line' }, { [styles.tabItemLine]: type === 'line' }, @@ -129,9 +134,11 @@ export default memo((props) => {
    {t.label}
    )} -
    - -
    + {t.canClosed !== false && ( +
    + +
    + )}
    ); @@ -139,22 +146,24 @@ export default memo((props) => { return (
    -
    - {!!internalTabs?.length && ( -
    - {internalTabs.map((t, index) => { - return renderTabItem(t, index); - })} -
    - )} - {!hideAdd && ( -
    -
    - + {!concealTabHeader && ( +
    + {!!internalTabs?.length && ( +
    + {internalTabs.map((t, index) => { + return renderTabItem(t, index); + })}
    -
    - )} -
    + )} + {!hideAdd && ( +
    +
    + +
    +
    + )} +
    + )}
    {internalTabs?.map((t) => { return ( From fa5b475c4851a147a2186b59413219cfe34d5cc6 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 11 Oct 2023 21:30:29 +0800 Subject: [PATCH 0881/1069] support table page query --- .../domain/api/service/TableService.java | 8 ++ .../domain/core/impl/TableServiceImpl.java | 87 +++++++++++++------ .../api/controller/rdb/TableController.java | 16 ++++ .../ai/chat2db/spi/model/SimpleColumn.java | 30 +++++++ .../ai/chat2db/spi/model/SimpleTable.java | 26 ++++++ 5 files changed, 141 insertions(+), 26 deletions(-) create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleColumn.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleTable.java diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java index 62ecb5e0b..fb59edce3 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/TableService.java @@ -75,6 +75,14 @@ public interface TableService { */ PageResult
    pageQuery(TablePageQueryParam param, TableSelector selector); + + /** + * 查询表信息 + * @param param + * @return + */ + ListResult queryTables(TablePageQueryParam param); + /** * 查询表包含的字段 * diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index b13e47652..188d9ee0b 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -136,10 +136,10 @@ private void initOldTable(Table oldTable, Table newTable) { @Override public PageResult
    pageQuery(TablePageQueryParam param, TableSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(TableCacheVersionDO::getKey, buildKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + queryWrapper.eq(TableCacheVersionDO::getKey, getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); TableCacheVersionDO versionDO = tableCacheVersionMapper.selectOne(queryWrapper); if (param.isRefresh() || versionDO == null) { - addDBCache(param, versionDO); + addDBCache(param.getDataSourceId(),param.getDatabaseName(),param.getSchemaName(), versionDO); } LambdaQueryWrapper query = new LambdaQueryWrapper<>(); query.eq(TableCacheDO::getVersion, versionDO.getVersion()); @@ -154,7 +154,7 @@ public PageResult
    pageQuery(TablePageQueryParam param, TableSelector sele query.like(TableCacheDO::getTableName, param.getSearchKey()); } Page page = new Page<>(param.getPageNo(), param.getPageSize()); - // page.setSearchCount(param.getEnableReturnCount()); + // page.setSearchCount(param.getEnableReturnCount()); IPage iPage = tableCacheMapper.selectPage(page, query); List
    tables = new ArrayList<>(); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { @@ -170,14 +170,49 @@ public PageResult
    pageQuery(TablePageQueryParam param, TableSelector sele return PageResult.of(tables, versionDO.getTableCount(), param); } - private void addDBCache(TablePageQueryParam param, TableCacheVersionDO versionDO) { + @Override + public ListResult queryTables(TablePageQueryParam param) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TableCacheVersionDO::getKey, getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + TableCacheVersionDO versionDO = tableCacheVersionMapper.selectOne(queryWrapper); + if (param.isRefresh() || versionDO == null) { + addDBCache(param.getDataSourceId(),param.getDatabaseName(), param.getSchemaName(), versionDO); + } + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.eq(TableCacheDO::getVersion, versionDO.getVersion()); + query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); + if (StringUtils.isNotBlank(param.getDatabaseName())) { + query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); + } + if (StringUtils.isNotBlank(param.getSchemaName())) { + query.eq(TableCacheDO::getSchemaName, param.getSchemaName()); + } + List tables = new ArrayList<>(); + + for (int i = 0; i < versionDO.getTableCount()/500 +1; i++) { + Page page = new Page<>(i+1, 500); + IPage iPage = tableCacheMapper.selectPage(page, query); + if (CollectionUtils.isNotEmpty(iPage.getRecords())) { + for (TableCacheDO tableCacheDO : iPage.getRecords()) { + SimpleTable t = new SimpleTable(); + t.setName(tableCacheDO.getTableName()); + t.setComment(tableCacheDO.getExtendInfo()); + tables.add(t); + } + } + } + return ListResult.of(tables); + } + + private void addDBCache(Long dataSourceId,String databaseName,String schemaName, TableCacheVersionDO versionDO) { + String key = getTableKey(dataSourceId, databaseName, schemaName); if (versionDO == null) { versionDO = new TableCacheVersionDO(); - versionDO.setDatabaseName(param.getDatabaseName()); - versionDO.setSchemaName(param.getSchemaName()); - versionDO.setDataSourceId(param.getDataSourceId()); + versionDO.setDatabaseName(databaseName); + versionDO.setSchemaName(schemaName); + versionDO.setDataSourceId(dataSourceId); versionDO.setStatus("2"); - versionDO.setKey(buildKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + versionDO.setKey(key); versionDO.setVersion(0L); versionDO.setTableCount(0L); tableCacheVersionMapper.insert(versionDO); @@ -188,7 +223,7 @@ private void addDBCache(TablePageQueryParam param, TableCacheVersionDO versionDO } Connection connection = Chat2DBContext.getConnection(); long n = 0; - try (ResultSet resultSet = connection.getMetaData().getTables(param.getDatabaseName(), param.getSchemaName(), param.getTableName(), + try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, null, new String[]{"TABLE"})) { List cacheDOS = new ArrayList<>(); while (resultSet.next()) { @@ -197,9 +232,9 @@ private void addDBCache(TablePageQueryParam param, TableCacheVersionDO versionDO tableCacheDO.setSchemaName(resultSet.getString("TABLE_SCHEM")); tableCacheDO.setTableName(resultSet.getString("TABLE_NAME")); tableCacheDO.setExtendInfo(resultSet.getString("REMARKS")); - tableCacheDO.setDataSourceId(param.getDataSourceId()); + tableCacheDO.setDataSourceId(dataSourceId); tableCacheDO.setVersion(versionDO.getVersion()); - tableCacheDO.setKey(buildKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + tableCacheDO.setKey(key); cacheDOS.add(tableCacheDO); if (cacheDOS.size() >= 500) { tableCacheMapper.batchInsert(cacheDOS); @@ -214,13 +249,13 @@ private void addDBCache(TablePageQueryParam param, TableCacheVersionDO versionDO versionDO.setTableCount(n); tableCacheVersionMapper.updateById(versionDO); LambdaQueryWrapper q = new LambdaQueryWrapper(); - q.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); + q.eq(TableCacheDO::getDataSourceId, dataSourceId); q.lt(TableCacheDO::getVersion, versionDO.getVersion()); - if (param.getDatabaseName() != null) { - q.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); + if (StringUtils.isNotBlank(databaseName)) { + q.eq(TableCacheDO::getDatabaseName, databaseName); } - if (param.getSchemaName() != null) { - q.eq(TableCacheDO::getSchemaName, param.getSchemaName()); + if (StringUtils.isNotBlank(schemaName)) { + q.eq(TableCacheDO::getSchemaName, schemaName); } tableCacheMapper.delete(q); } catch (SQLException e) { @@ -230,16 +265,16 @@ private void addDBCache(TablePageQueryParam param, TableCacheVersionDO versionDO } - private String buildKey(Long dataSourceId, String databaseName, String schemaName) { - StringBuilder stringBuilder = new StringBuilder(dataSourceId.toString()); - if (StringUtils.isNotBlank(databaseName)) { - stringBuilder.append("_").append(databaseName); - } - if (StringUtils.isNotBlank(schemaName)) { - stringBuilder.append("_").append(schemaName); - } - return stringBuilder.toString(); - } +// private String buildKey(Long dataSourceId, String databaseName, String schemaName) { +// StringBuilder stringBuilder = new StringBuilder(dataSourceId.toString()); +// if (StringUtils.isNotBlank(databaseName)) { +// stringBuilder.append("_").append(databaseName); +// } +// if (StringUtils.isNotBlank(schemaName)) { +// stringBuilder.append("_").append(schemaName); +// } +// return stringBuilder.toString(); +// } private List
    pinTable(List
    list, TablePageQueryParam param) { if (CollectionUtils.isEmpty(list)) { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 080743440..5f6e4cc0e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -60,6 +60,22 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { request.getPageSize()); } + /** + * 查询当前DB下的表列表 + * + * @param request + * @return + */ + @GetMapping("/table_list") + public ListResult tableList(@Valid TableBriefQueryRequest request) { + TablePageQueryParam queryParam = rdbWebConverter.tablePageRequest2param(request); + return tableService.queryTables(queryParam); + + } + + + + /** * 查询当前DB下的表columns diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleColumn.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleColumn.java new file mode 100644 index 000000000..cb1139bf7 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleColumn.java @@ -0,0 +1,30 @@ +package ai.chat2db.spi.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SimpleColumn { + + /** + * 列名 + */ + @JsonAlias({"COLUMN_NAME"}) + private String name; + + + @JsonAlias({"TYPE_NAME"}) + private String columnType; + + /** + * 注释 + */ + @JsonAlias({"REMARKS"}) + private String comment; +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleTable.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleTable.java new file mode 100644 index 000000000..2a5159d68 --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/SimpleTable.java @@ -0,0 +1,26 @@ +package ai.chat2db.spi.model; + +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class SimpleTable { + /** + * 表名 + */ + @JsonAlias({"TABLE_NAME"}) + private String name; + + /** + * 描述 + */ + @JsonAlias({"REMARKS"}) + + private String comment; +} From 0ad5c632f6d1543a2d294e288a34b27beb1b30da Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Wed, 11 Oct 2023 22:29:48 +0800 Subject: [PATCH 0882/1069] support table page query --- .../server/domain/core/impl/TableServiceImpl.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index 188d9ee0b..0836e52f7 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -139,7 +139,7 @@ public PageResult
    pageQuery(TablePageQueryParam param, TableSelector sele queryWrapper.eq(TableCacheVersionDO::getKey, getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); TableCacheVersionDO versionDO = tableCacheVersionMapper.selectOne(queryWrapper); if (param.isRefresh() || versionDO == null) { - addDBCache(param.getDataSourceId(),param.getDatabaseName(),param.getSchemaName(), versionDO); + versionDO = addDBCache(param.getDataSourceId(),param.getDatabaseName(),param.getSchemaName(), versionDO); } LambdaQueryWrapper query = new LambdaQueryWrapper<>(); query.eq(TableCacheDO::getVersion, versionDO.getVersion()); @@ -204,7 +204,7 @@ public ListResult queryTables(TablePageQueryParam param) { return ListResult.of(tables); } - private void addDBCache(Long dataSourceId,String databaseName,String schemaName, TableCacheVersionDO versionDO) { + private TableCacheVersionDO addDBCache(Long dataSourceId,String databaseName,String schemaName, TableCacheVersionDO versionDO) { String key = getTableKey(dataSourceId, databaseName, schemaName); if (versionDO == null) { versionDO = new TableCacheVersionDO(); @@ -228,8 +228,8 @@ private void addDBCache(Long dataSourceId,String databaseName,String schemaName, List cacheDOS = new ArrayList<>(); while (resultSet.next()) { TableCacheDO tableCacheDO = new TableCacheDO(); - tableCacheDO.setDatabaseName(resultSet.getString("TABLE_CAT")); - tableCacheDO.setSchemaName(resultSet.getString("TABLE_SCHEM")); + tableCacheDO.setDatabaseName(databaseName); + tableCacheDO.setSchemaName(schemaName); tableCacheDO.setTableName(resultSet.getString("TABLE_NAME")); tableCacheDO.setExtendInfo(resultSet.getString("REMARKS")); tableCacheDO.setDataSourceId(dataSourceId); @@ -261,6 +261,7 @@ private void addDBCache(Long dataSourceId,String databaseName,String schemaName, } catch (SQLException e) { throw new RuntimeException(e); } + return versionDO; } From 62f04ba71f26d0d28e430526ace349ca719e0add Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 12 Oct 2023 11:04:22 +0800 Subject: [PATCH 0883/1069] fixstartServerForSpawn --- chat2db-client/.umirc.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 55b34350d..ae741c1e4 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -49,16 +49,16 @@ export default defineConfig({ targets: { chrome: 80, }, - links: [{ - rel: 'manifest', - href: 'manifest.json', - }], + // links: [{ + // rel: 'manifest', + // href: 'manifest.json', + // }], headScripts: [ `if (localStorage.getItem('app-local-storage-versions') !== 'v2') { localStorage.clear(); localStorage.setItem('app-local-storage-versions', 'v2'); }`, - // `if (window.myAPI) { window.myAPI.startServerForSpawn() }`, + `if (window.myAPI) { window.myAPI.startServerForSpawn() }`, // `if ("serviceWorker" in navigator) { // window.addEventListener("load", function () { // navigator.serviceWorker From 3ae56100c215cf44a39c44bac933303dcca158b8 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 12 Oct 2023 11:22:53 +0800 Subject: [PATCH 0884/1069] feat: Optimized editor hints --- chat2db-client/package.json | 1 + .../src/blocks/SQLExecute/index.tsx | 3 +- .../components/Console/MonacoEditor/index.tsx | 170 ++++----- .../Console/NewMonacoEditor/index.less | 5 + .../Console/NewMonacoEditor/index.tsx | 157 ++++++++ .../src/components/Console/index.tsx | 35 +- .../src/components/ImportConnection/index.tsx | 64 ++++ .../src/constants/IntelliSense/index.ts | 4 + .../src/constants/IntelliSense/mysql.ts | 26 ++ .../src/constants/IntelliSense/oracle.ts | 7 + chat2db-client/src/hooks/useUpdateEffect.ts | 5 + .../src/pages/main/connection/index.less | 28 +- .../src/pages/main/connection/index.tsx | 350 +++++++++--------- .../src/pages/main/dashboard/index.tsx | 4 +- .../index.less | 0 .../index.tsx | 68 ++-- .../src/pages/main/workspace/index.tsx | 16 +- .../src/utils/IntelliSense/field.ts | 71 ++++ .../src/utils/IntelliSense/index.ts | 11 + .../src/utils/IntelliSense/keyword.ts | 65 ++++ .../src/utils/IntelliSense/table.ts | 47 +++ chat2db-client/src/utils/index.ts | 36 +- chat2db-client/yarn.lock | 7 + 23 files changed, 835 insertions(+), 345 deletions(-) create mode 100644 chat2db-client/src/components/Console/NewMonacoEditor/index.less create mode 100644 chat2db-client/src/components/Console/NewMonacoEditor/index.tsx create mode 100644 chat2db-client/src/components/ImportConnection/index.tsx create mode 100644 chat2db-client/src/constants/IntelliSense/index.ts create mode 100644 chat2db-client/src/constants/IntelliSense/mysql.ts create mode 100644 chat2db-client/src/constants/IntelliSense/oracle.ts rename chat2db-client/src/pages/main/workspace/components/{WorkspaceRightNew => WorkspaceRight}/index.less (100%) rename chat2db-client/src/pages/main/workspace/components/{WorkspaceRightNew => WorkspaceRight}/index.tsx (92%) create mode 100644 chat2db-client/src/utils/IntelliSense/field.ts create mode 100644 chat2db-client/src/utils/IntelliSense/index.ts create mode 100644 chat2db-client/src/utils/IntelliSense/keyword.ts create mode 100644 chat2db-client/src/utils/IntelliSense/table.ts diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 76cae2ef9..b304bb2df 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -39,6 +39,7 @@ "monaco-editor-esm-webpack-plugin": "^2.1.0", "monaco-editor-webpack-plugin": "^7.0.1", "node-machine-id": "^1.1.12", + "react-monaco-editor": "^0.54.0", "react-sortablejs": "^6.1.4", "sql-formatter": "^12.2.1", "styled-components": "^6.0.1", diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 2589d46b2..5f90e9676 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -25,7 +25,7 @@ interface IProps { databaseName: string; dataSourceId: number; type: DatabaseTypeCode; - schemaName: string; + schemaName?: string; consoleId: number; consoleName: string; initDDL: string; @@ -47,6 +47,7 @@ const SQLExecute = memo((props) => { const { doubleClickTreeNodeData, curTableList, curWorkspaceParams } = workspaceModel; const [tableLoading, setTableLoading] = useState(false); const controllerRef = useRef(); + useEffect(() => { if (!doubleClickTreeNodeData) { return; diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 97702fd82..a9ab98ff8 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -1,13 +1,10 @@ -import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react'; +import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef } from 'react'; import cs from 'classnames'; import { useTheme } from '@/hooks'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; -const { keywords: SQLKeys } = language; -const { keywords } = language; import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; import styles from './index.less'; -import { useUpdateEffect } from '@/hooks'; + export type IEditorIns = monaco.editor.IStandaloneCodeEditor; export type IEditorOptions = monaco.editor.IStandaloneEditorConstructionOptions; export type IEditorContentChangeEvent = monaco.editor.IModelContentChangedEvent; @@ -16,33 +13,108 @@ export type IAppendValue = { text: any; range?: IRangeType; }; + interface IProps { - id: string | number; + id: string; isActive?: boolean; language?: string; className?: string; options?: IEditorOptions; needDestroy?: boolean; addAction?: Array<{ id: string; label: string; action: (selectedText: string) => void }>; + defaultValue?: string; appendValue?: IAppendValue; // onChange?: (v: string, e?: IEditorContentChangeEvent) => void; didMount?: (editor: IEditorIns) => any; onSave?: (value: string) => void; // 快捷键保存的回调 - defaultValue?: string; onExecute?: (value: string) => void; // 快捷键执行的回调 - tables?: any[]; } export interface IExportRefFunction { getCurrentSelectContent: () => string; getAllContent: () => string; setValue: (text: any, range?: IRangeType) => void; - handleRegisterTigger: (hintData: IHintData) => void; } export interface IHintData { [keys: string]: string[]; } +// monaco.languages.registerCompletionItemProvider(`sql`, { +// triggerCharacters: [' ', '('], +// provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => { +// const { lineNumber, column } = position; +// const textBeforePointer = model.getValueInRange({ +// startLineNumber: lineNumber, +// startColumn: 0, +// endLineNumber: lineNumber, +// endColumn: column, +// }); +// const tokens = textBeforePointer.trim().split(/\s+/); +// const lastToken = tokens[tokens.length - 1]; // 获取最后一段非空字符串 +// const lastTokenBefore = tokens[tokens.length - 2]; // 获取最后一段非空字符串 + +// // 后面显示表名 +// if ( +// ['FROM', 'JOIN', 'UPDATE', 'DELETE'].includes(lastToken.toUpperCase()) || +// ['INSERT INTO', 'ALTER TABLE', 'DROP TABLE'].includes((lastTokenBefore + ' ' + lastToken).toUpperCase()) +// ) { +// return { +// // suggestions: [ +// // { +// // label: 'table1(datasource1)', +// // kind: monaco.languages.CompletionItemKind.Method, +// // insertText: 'table1', +// // detail: '
    ', +// // }, +// // ], +// suggestions: getTableSuggest(tableList), +// }; +// } + +// // // 后面显示字段名 +// // if (['SELECT'].includes(lastToken.toUpperCase())) { +// // return { +// // suggestions: [ +// // { +// // label: 'field1(table1)', +// // kind: monaco.languages.CompletionItemKind.Field, +// // insertText: 'field1', +// // detail: '', +// // }, +// // { +// // label: '*', +// // kind: monaco.languages.CompletionItemKind.Field, +// // insertText: '*', +// // detail: '', +// // }, +// // ], +// // }; +// // } + +// return { +// suggestions: [ +// ...getSQLKeywords(), +// ...getSQLFunctions(), +// ...getTableSuggest([ +// { +// tableName: 'table1', +// datasourceName: 'datasource1', +// }, +// ]), +// ...getFieldSuggest([ +// { +// tableName: 'table1', +// fields: [ +// { +// name: 'field1', +// }, +// ], +// }, +// ]), +// ], +// }; +// }, +// }); function MonacoEditor(props: IProps, ref: ForwardedRef) { const { @@ -124,8 +196,11 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { }); createAction(editorIns); + return () => { - if (props.needDestroy) editorRef.current && editorRef.current.dispose(); + if (props.needDestroy) { + editorRef.current && editorRef.current.dispose(); + } }; }, []); @@ -153,16 +228,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { monaco.editor.setTheme(appTheme.backgroundColor); }, [appTheme.backgroundColor, options?.theme]); - // useEffect(() => { - // const _ref = editorRef.current?.onDidChangeModelContent((e) => { - // const curVal = editorRef.current?.getValue(); - // props.onChange?.(curVal || '', e); - // }); - // return () => _ref && _ref.dispose(); - // }, [props.onChange]); - useImperativeHandle(ref, () => ({ - handleRegisterTigger, getCurrentSelectContent, getAllContent, setValue, @@ -199,72 +265,6 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { return value || ''; }; - const handleRegisterTigger = (hintData: IHintData) => { - // 获取 SQL 语法提示 - const getSQLSuggest = () => { - return SQLKeys.map((key: any) => ({ - label: key, - kind: monaco.languages.CompletionItemKind.Keyword, - insertText: key, - detail: '', - })); - }; - - // 获取一级数据 - const getFirstSuggest = () => { - return Object.keys(hintData).map((key) => ({ - label: key, - kind: monaco.languages.CompletionItemKind.Method, - insertText: `${key}`, - detail: '', - })); - }; - - // 获取二级数据 - // const getSecondSuggest = (keys: string) => { - // const secondNames = hintData[keys]; - // if (!secondNames) { - // return []; - // } - // return (secondNames || []).map((name: any) => ({ - // label: name, - // kind: monaco.languages.CompletionItemKind.Snippet, - // insertText: name, - // detail: '
    ', - // })); - // }; - const editorHintExamples = monaco.languages.registerCompletionItemProvider('sql', { - triggerCharacters: [' ', ...SQLKeys], - provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => { - let suggestions: any = []; - const { lineNumber, column } = position; - const textBeforePointer = model.getValueInRange({ - startLineNumber: lineNumber, - startColumn: 0, - endLineNumber: lineNumber, - endColumn: column, - }); - const tokens = textBeforePointer.trim().split(/\s+/); - const lastToken = tokens[tokens.length - 1]; // 获取最后一段非空字符串 - - if (lastToken.endsWith('.')) { - const tokenNoDot = lastToken.slice(0, lastToken.length - 1); - // suggestions = [...getSecondSuggest(tokenNoDot)]; - suggestions = []; - } else if (lastToken === '.') { - suggestions = []; - } else { - suggestions = [...getFirstSuggest(), ...getSQLSuggest()]; - } - return { - suggestions, - }; - }, - }); - - return editorHintExamples; - }; - const createAction = (editor: IEditorIns) => { // 用于控制切换该菜单键的显示 const shouldShowSqlRunnerAction = editor.createContextKey('shouldShowSqlRunnerAction', true); diff --git a/chat2db-client/src/components/Console/NewMonacoEditor/index.less b/chat2db-client/src/components/Console/NewMonacoEditor/index.less new file mode 100644 index 000000000..4f49bd28b --- /dev/null +++ b/chat2db-client/src/components/Console/NewMonacoEditor/index.less @@ -0,0 +1,5 @@ + +.monaco-editor .suggest-widget .suggest-icon.method { + background-image: url('../../../assets/logo/logo.png'); + background-size: contain; /* 如果需要,确保图标大小适合容器 */ +} \ No newline at end of file diff --git a/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx b/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx new file mode 100644 index 000000000..4ac44f3eb --- /dev/null +++ b/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx @@ -0,0 +1,157 @@ +import React, { useEffect, useState } from 'react'; +import MonacoEditor from 'react-monaco-editor'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { language } from 'monaco-editor/esm/vs/basic-languages/sql/sql'; +import styles from './index.less'; + +const { keywords: SQLKeys, builtinFunctions: FunctionKeys } = language; + +const getSQLKeywords = () => { + return SQLKeys.map((key: any) => ({ + label: key, + kind: monaco.languages.CompletionItemKind.Text, + insertText: key, + detail: '关键词', + })); +}; + +/** 数据库关键词 */ +let providerKeyword = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + const textUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + + // 简化的示例:你可以通过正则或其他方法检查是否输入了表名 + if (textUntilPosition.endsWith('FROM ')) { + return { suggestions: [] }; + } + + return { suggestions: [...getSQLKeywords()] }; + }, +}); + +/** 当前库下的表 */ +let providerTable = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + return { + suggestions: [ + { + label: 'table1', + kind: monaco.languages.CompletionItemKind.Function, + insertText: 'table1', + }, + ], + }; + }, +}); + +function SQLEditor({ id, dataSource, database }) { + const [suggestions, setSuggestions] = useState([]); + + // useEffect(() => { + // if (!monaco) return; + + // return () => { + // // 当组件卸载时,注销提示提供者 + // provider.dispose(); + // }; + // }, [suggestions]); + + useEffect(() => { + // + console.log('dataSource', dataSource); + providerKeyword.dispose(); + providerKeyword = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + const textUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + + // 简化的示例:你可以通过正则或其他方法检查是否输入了表名 + if (textUntilPosition.endsWith('FROM ')) { + return { suggestions: [] }; + } + + if (dataSource === 'MYSQL') { + return { suggestions: [...getSQLKeywords()] }; + } else { + return { + suggestions: [ + { + label: 'id', + kind: monaco.languages.CompletionItemKind.Property, + insertText: 'id', + }, + ], + }; + } + }, + }); + if (dataSource === 'MYSQL') { + providerTable.dispose(); + providerTable = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: () => { + return { + suggestions: [ + { + label: { + label: 'table_mysql', + detail: '(detail)', + description: 'description', + }, + kind: monaco.languages.CompletionItemKind.Function, + insertText: 'table_mysql', + }, + ], + }; + }, + }); + } + }, [dataSource]); + + useEffect(() => { + // 当数据源或数据库变化时,获取相应的表的列提示 + if (dataSource && database) { + // 模拟 API 调用来获取表的列提示 + // fetch(`/api/columns?source=${dataSource}&database=${database}`) + // .then((response) => response.json()) + // .then((columns) => { + // const newSuggestions = columns.map((column) => ({ + // label: column, + // kind: monaco.languages.CompletionItemKind.Property, + // insertText: column, + // })); + // setSuggestions(newSuggestions); + // }); + + Promise.resolve([ + { + label: 'id', + kind: monaco.languages.CompletionItemKind.Property, + insertText: 'id', + }, + ]); + } + }, [dataSource, database]); + + return ( + + ); +} + +export default SQLEditor; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index bc9a26cea..b48079fb2 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -18,6 +18,7 @@ import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; import { AiSqlSourceType } from '@/typings/ai'; import i18n from '@/i18n'; import configService from '@/service/config'; +// import NewEditor from './NewMonacoEditor'; import styles from './index.less'; enum IPromptType { @@ -42,7 +43,7 @@ export type IAppendValue = { }; interface IProps { - /** 是谁在调用我 */ + /** 调用来源 */ source: 'workspace'; /** 是否是活跃的console,用于快捷键 */ isActive?: boolean; @@ -96,17 +97,15 @@ function Console(props: IProps) { const [aiContent, setAiContent] = useState(''); const [isAiDrawerOpen, setIsAiDrawerOpen] = useState(false); const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); - const monacoHint = useRef(); - const [modal, contextHolder] = Modal.useModal(); const [popularizeModal, setPopularizeModal] = useState(false); const [modalProps, setModalProps] = useState({}); const timerRef = useRef(); const aiFetchIntervalRef = useRef(); /** - * 当前选择的AI类型是Chat2DBAi + * 当前选择的AI类型是Chat2DBAI */ - const isChat2DBAi = useMemo( + const isChat2DBAI = useMemo( () => aiModel.aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI, [aiModel.aiConfig?.aiSqlSource], ); @@ -117,15 +116,6 @@ function Console(props: IProps) { } }, [appendValue]); - useEffect(() => { - monacoHint.current?.dispose(); - const myEditorHintData: any = {}; - props.tables?.map((item: any) => { - myEditorHintData[item.name] = []; - }); - monacoHint.current = editorRef?.current?.handleRegisterTigger(myEditorHintData); - }, [props.tables]); - useEffect(() => { if (source !== 'workspace') { return; @@ -215,7 +205,6 @@ function Console(props: IProps) { } catch (e) { setIsLoading(false); } - }; const handleAIChatInEditor = async (content: string, promptType: IPromptType) => { @@ -224,9 +213,8 @@ function Console(props: IProps) { }; const handleAiChat = async (content: string, promptType: IPromptType, aiConfig?: IAiConfig) => { - const { apiKey, aiSqlSource } = aiConfig || props.aiModel?.aiConfig || {}; - const isChat2DBAi = aiSqlSource === AiSqlSourceType.CHAT2DBAI; - if (!apiKey && isChat2DBAi) { + const { apiKey } = aiConfig || props.aiModel?.aiConfig || {}; + if (!apiKey && isChat2DBAI) { handleApiKeyEmptyOrGetQrCode(true); return; } @@ -256,7 +244,7 @@ function Console(props: IProps) { if (isEOF) { closeEventSource(); setIsLoading(false); - if (isChat2DBAi) { + if (isChat2DBAI) { dispatch({ type: 'ai/fetchRemainingUse', payload: { @@ -375,7 +363,7 @@ function Console(props: IProps) { ]; const handleClickRemainBtn = async () => { - if (!isChat2DBAi) return; + if (!isChat2DBAI) return; // chat2dbAi模型下,没有key,就需要登录 if (!aiModel.aiConfig?.apiKey) { @@ -412,7 +400,7 @@ function Console(props: IProps) { sql = editorRef?.current?.getAllContent() || ''; setValueType = 'cover'; } - formatSql(sql, executeParams.type!).then(res => { + formatSql(sql, executeParams.type!).then((res) => { editorRef?.current?.setValue(res, setValueType); }); }; @@ -455,9 +443,10 @@ function Console(props: IProps) { onSave={saveConsole} onExecute={executeSQL} options={props.editorOptions} - tables={props.tables} - // onChange={} /> + + {/* */} + {/* {modelConfig.content} */} setIsAiDrawerOpen(false)}> diff --git a/chat2db-client/src/components/ImportConnection/index.tsx b/chat2db-client/src/components/ImportConnection/index.tsx new file mode 100644 index 000000000..25b746aee --- /dev/null +++ b/chat2db-client/src/components/ImportConnection/index.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Modal, Upload, Button, message } from 'antd'; +import { UploadOutlined } from '@ant-design/icons'; + +interface FileUploadModalProps { + open: boolean; + onClose: () => void; + onUploaded?: (file: File) => void; + onConfirm?: () => void; +} + +const FileUploadModal: React.FC = ({ open, onClose, onUploaded, onConfirm }) => { + const beforeUpload = (file: File) => { + const fileName = file.name.toLowerCase(); + if (fileName.includes('navicat')) { + message.success('Navicat file detected'); + onUploaded && onUploaded(file); + } else if (fileName.includes('dbeaver')) { + message.success('DBeaver file detected'); + onUploaded && onUploaded(file); + } else if (fileName.includes('datagrip')) { + message.success('DataGrip file detected'); + onUploaded && onUploaded(file); + } else { + message.error('File type not recognized'); + return false; + } + return false; // Prevent auto-upload for the sake of this demo + }; + + const renderSelect = () => { + // 下拉框 选择Navciat、dbever、datagrip + }; + return ( + + Cancel + , + , + ]} + > + { + console.log(info); + }} + > + + + + ); +}; + +export default FileUploadModal; diff --git a/chat2db-client/src/constants/IntelliSense/index.ts b/chat2db-client/src/constants/IntelliSense/index.ts new file mode 100644 index 000000000..86131f4a2 --- /dev/null +++ b/chat2db-client/src/constants/IntelliSense/index.ts @@ -0,0 +1,4 @@ +import mysql from './mysql'; +import oracle from './oracle'; + +export default { mysql, oracle }; diff --git a/chat2db-client/src/constants/IntelliSense/mysql.ts b/chat2db-client/src/constants/IntelliSense/mysql.ts new file mode 100644 index 000000000..3773ccde8 --- /dev/null +++ b/chat2db-client/src/constants/IntelliSense/mysql.ts @@ -0,0 +1,26 @@ +import { DatabaseTypeCode } from '../common'; + +export default { + type: DatabaseTypeCode.MYSQL, + keywords: [ + 'SELECT', + 'FROM', + 'WHERE', + 'LIMIT', + 'AND', + 'OR', + 'NOT', + 'BETWEEN', + 'LIKE', + 'IN', + 'IS', + 'NULL', + 'ORDER BY', + 'GROUP BY', + 'HAVING', + 'LIMIT', + 'ASC', + 'DESC', + ], + functions: ['AVG', 'COUNT', 'FIRST', 'LAST', 'MAX', 'MIN', 'SUM', 'MID', 'LEN', 'ROUND', 'NOW', 'FORMAT'], +}; diff --git a/chat2db-client/src/constants/IntelliSense/oracle.ts b/chat2db-client/src/constants/IntelliSense/oracle.ts new file mode 100644 index 000000000..d6902d540 --- /dev/null +++ b/chat2db-client/src/constants/IntelliSense/oracle.ts @@ -0,0 +1,7 @@ +import { DatabaseTypeCode } from '../common'; + +export default { + type: DatabaseTypeCode.ORACLE, + keywords: ['Oracle keyword'], + functions: ['Oracle function'], +}; diff --git a/chat2db-client/src/hooks/useUpdateEffect.ts b/chat2db-client/src/hooks/useUpdateEffect.ts index 85af8aa4e..63f9ee00c 100644 --- a/chat2db-client/src/hooks/useUpdateEffect.ts +++ b/chat2db-client/src/hooks/useUpdateEffect.ts @@ -1,5 +1,10 @@ import { useRef, useEffect } from 'react'; +/** + * 第一次Effect更新不执行 + * @param fn + * @param arr + */ export function useUpdateEffect(fn: Function, arr: any[]) { const first = useRef(true); useEffect(() => { diff --git a/chat2db-client/src/pages/main/connection/index.less b/chat2db-client/src/pages/main/connection/index.less index 8b8f99058..b6553dfb6 100644 --- a/chat2db-client/src/pages/main/connection/index.less +++ b/chat2db-client/src/pages/main/connection/index.less @@ -57,9 +57,11 @@ width: 0; display: flex; align-items: center; + .name { .f-single-line(); } + .envTag { width: 8px; height: 8px; @@ -67,6 +69,7 @@ background-color: var(--color-primary); margin-right: 8px; } + .databaseTypeIcon { margin-right: 6px; } @@ -80,6 +83,7 @@ &:hover { background-color: var(--color-hover-bg); + .moreButton { display: block; } @@ -117,17 +121,25 @@ } .dataBaseList { - display: flex; - justify-content: space-between; - flex-wrap: wrap; - max-width: 800px; + // display: flex; + // justify-content: space-between; + // flex-wrap: wrap; + // max-width: 800px; + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px; +} +@media (max-width: 1000px) { + .dataBaseList { + grid-template-columns: repeat(2, 1fr); + } } + .databaseItem { - flex-grow: 1; + min-width: 220px; border-radius: 4px; height: 50px; - width: 210px; margin: 10px 20px; padding: 0px 16px; border-radius: 8px; @@ -150,6 +162,7 @@ .databaseItemRight { display: none; + i { font-size: 16px; } @@ -163,6 +176,7 @@ .databaseItemRight { display: block; + i { color: var(--color-primary); } @@ -224,4 +238,4 @@ cursor: pointer; color: var(--color-primary); } -} +} \ No newline at end of file diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 176c045d3..6000f309b 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -1,18 +1,18 @@ -import React, { Fragment, memo, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState } from 'react'; import classnames from 'classnames'; import i18n from '@/i18n'; import ConnectionEdit from '@/components/ConnectionEdit'; import Iconfont from '@/components/Iconfont'; import RefreshLoadingButton from '@/components/RefreshLoadingButton'; import connectionService from '@/service/connection'; -import { DatabaseTypeCode, databaseMap, databaseTypeList, ConnectionKind } from '@/constants'; +import { databaseMap, databaseTypeList, ConnectionKind } from '@/constants'; import { IDatabase, IConnectionDetails, IConnectionEnv } from '@/typings'; -import { Button, Dropdown, Modal, Tag } from 'antd'; -import styles from './index.less'; +import { Button, Dropdown } from 'antd'; import { connect, history, Dispatch } from 'umi'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; -import { deepClone } from '@/utils'; +import FileUploadModal from '@/components/ImportConnection'; +import styles from './index.less'; interface IMenu { key: number; @@ -27,12 +27,12 @@ interface IAllMenuList { list: IMenu[]; name: string; loading: boolean; - }, + }; [ConnectionKind.Shared]: { list: IMenu[]; name: string; loading: boolean; - }, + }; } interface IProps { @@ -46,6 +46,7 @@ function Connections(props: IProps) { const volatileRef = useRef(); const [curConnection, setCurConnection] = useState>({}); const [allMenuList, setAllMenuList] = useState(); + const [isFileUploadModalOpen, setIsFileUploadModalOpen] = useState(false); useEffect(() => { const list: IAllMenuList = { @@ -58,24 +59,24 @@ function Connections(props: IProps) { list: [], name: i18n('connection.label.shared'), loading: false, - } - } - connectionList.forEach(t => { + }, + }; + 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) + list[ConnectionKind.Shared].list.push(menu); } else { - list[ConnectionKind.Private].list.push(menu) + list[ConnectionKind.Private].list.push(menu); } }); setAllMenuList(list); - }, [connectionList]) + }, [connectionList]); function handleCreateConnections(database: IDatabase) { setCurConnection({ @@ -101,203 +102,214 @@ function Connections(props: IProps) { pageSize: 999, refresh: true, kind, - } + }; if (allMenuList) { setAllMenuList({ ...allMenuList, [kind]: { ...allMenuList[kind], loading: true, - } - }) + }, + }); } - connectionService.getList(p).then(res => { + connectionService.getList(p).then((res) => { if (allMenuList) { setAllMenuList({ ...allMenuList, [kind]: { ...allMenuList[kind], - list: res.data.map(t => { + 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 => { - 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} - - {/* + {allMenuList && + Object.keys(allMenuList).map((t) => { + 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); + {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', + { + 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(); + ], }} > - -
    -
    -
    - ); - })} - - ); - } - })} +
    { + e.stopPropagation(); + }} + > + +
    + +
    + ); + })} + + ); + } + })}
    ); }; return ( -
    -
    -
    {i18n('connection.title.connections')}
    - {renderMenu()} - {curConnection && !!Object.keys(curConnection).length && ( - - )} -
    -
    - {curConnection && Object.keys(curConnection).length ? ( -
    - { - { - setCurConnection({}); - }} - submitCallback={() => { - dispatch({ - type: 'connection/fetchConnectionList', - callback: (res: any) => { - setCurConnection(res.data[res.data?.length - 1]); - }, - }); - }} - /> - } -
    - ) : ( -
    - {databaseTypeList.map((t) => { - return ( -
    -
    -
    -
    - + <> +
    +
    +
    {i18n('connection.title.connections')}
    + {renderMenu()} + {curConnection && !!Object.keys(curConnection).length && ( + + )} +
    +
    + {curConnection && Object.keys(curConnection).length ? ( +
    + { + { + setCurConnection({}); + }} + submitCallback={() => { + dispatch({ + type: 'connection/fetchConnectionList', + callback: (res: any) => { + setCurConnection(res.data[res.data?.length - 1]); + }, + }); + }} + /> + } +
    + ) : ( +
    + {databaseTypeList.map((t) => { + return ( +
    +
    +
    +
    + +
    + {t.name} +
    +
    +
    - {t.name} -
    -
    -
    -
    - ); - })} - {Array.from({ length: 20 }).map((t, index) => { - return
    ; - })} -
    - )} + ); + })} + +
    + )} +
    -
    + { + setIsFileUploadModalOpen(false); + }} + /> + ); } diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index ccf330c55..3485b7aae 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -79,8 +79,8 @@ function Chart(props: IProps) { }; const initCreateChart = async (dashboard?: IDashboardItem) => { - if(!dashboard) return; - + if (!dashboard) return; + let chartId = await createChart({}); const newDashboard = { ...dashboard, diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less similarity index 100% rename from chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.less rename to chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx similarity index 92% rename from chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx rename to chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx index df7ea9236..edc6737e1 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRightNew/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useEffect, useState, useMemo, Fragment } from 'react'; +import React, { memo, useEffect, useState, useMemo, Fragment, useRef } from 'react'; import { connect } from 'umi'; import styles from './index.less'; import classnames from 'classnames'; @@ -17,6 +17,8 @@ import { handleLocalStorageSavedConsole } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; import { IWorkspaceTab } from '@/typings'; +import { registerIntelliSenseField, registerIntelliSenseKeyword, registerIntelliSenseTable } from '@/utils/IntelliSense'; + interface IProps { className?: string; workspaceModel: IWorkspaceModelState; @@ -34,6 +36,8 @@ const WorkspaceRight = memo((props: IProps) => { const { curWorkspaceParams, doubleClickTreeNodeData, createTabIntro, openConsoleList, createConsoleIntro } = workspaceModel; + const tableList = useRef>([]); + // 根据保存的console列表生成tab列表 useEffect(() => { const newTabList = openConsoleList?.map((t) => { @@ -290,39 +294,33 @@ const WorkspaceRight = memo((props: IProps) => { } }, [activeConsoleId]); - // useEffect(() => { - // openConsoleListRef.current = openConsoleList; - // const newActiveConsoleId = curConsoleId || activeConsoleId || Number(localStorage.getItem('active-console-id') || 0); - // // 用完之后就清掉curConsoleId - // if (!openConsoleList?.length) { - // setActiveConsoleId(undefined); - // } else if (!newActiveConsoleId) { - // setActiveConsoleId(openConsoleList[0].id); - // } else { - // // 如果你指定了让我打开哪个那我就打开哪个 - // if (curConsoleId) { - // setActiveConsoleId(curConsoleId); - // dispatch({ - // type: 'workspace/setCurConsoleId', - // payload: null, - // }); - // return - // } - - // let flag = false; - // openConsoleList?.forEach((t) => { - // if (t.id === newActiveConsoleId) { - // flag = true; - // } - // }); - // if (flag) { - // setActiveConsoleId(newActiveConsoleId); - // } else { - // // 如果发现当前列表里并没有newActiveConsoleId - // setActiveConsoleId(openConsoleList?.[openConsoleList?.length - 1].id); - // } - // } - // }, [openConsoleList]); + // 更新关键字提示 + useEffect(() => { + if (curWorkspaceParams.databaseType) { + registerIntelliSenseKeyword(curWorkspaceParams.databaseType); + } + }, [curWorkspaceParams.databaseType]); + + // 更新表名提示 + useEffect(() => { + const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; + if (databaseName) { + fetch( + `/api/rdb/table/table_list?dataSourceId=${dataSourceId}&databaseName=${databaseName}`, + // `/api/rdb/table/table_list?dataSourceId=${dataSourceId}&databaseName=${databaseName}&schemaName=${schemaName}`, + {}, + ) + .then((res) => { + return res.json(); + }) + .then((data) => { + console.log('databaseName', data.data); + tableList.current = (data.data || []).map((item: any) => item.name); + registerIntelliSenseTable(data.data, databaseName, databaseType); + registerIntelliSenseField(tableList.current, dataSourceId, databaseName, schemaName); + }); + } + }, [curWorkspaceParams.databaseType, curWorkspaceParams.databaseName, curWorkspaceParams.schemaName]); function createConsole(params: { doubleClickTreeNodeData: any; @@ -514,8 +512,8 @@ const WorkspaceRight = memo((props: IProps) => { isActive={activeConsoleId === t.id} data={{ initDDL: uniqueData?.ddl, - databaseName: curWorkspaceParams.databaseName!, dataSourceId: curWorkspaceParams.dataSourceId!, + databaseName: curWorkspaceParams.databaseName!, type: curWorkspaceParams.databaseType!, schemaName: curWorkspaceParams?.schemaName, consoleId: t.id as number, diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index a58cc193e..e583957da 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,15 +1,17 @@ import React, { memo, useRef, useEffect } from 'react'; import { connect } from 'umi'; -import styles from './index.less'; import DraggableContainer from '@/components/DraggableContainer'; -import WorkspaceLeft from './components/WorkspaceLeft'; -import WorkspaceRightNew from './components/WorkspaceRightNew'; import WorkspaceHeader from './components/WorkspaceHeader'; +import WorkspaceLeft from './components/WorkspaceLeft'; +import WorkspaceRight from './components/WorkspaceRight'; +import LoadingContent from '@/components/Loading/LoadingContent'; + import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; -import LoadingContent from '@/components/Loading/LoadingContent'; import { ConsoleOpenedStatus } from '@/constants'; +import styles from './index.less'; + interface IProps { className?: string; workspaceModel: IWorkspaceModelType['state']; @@ -34,7 +36,7 @@ const workspacePage = memo((props) => { curWorkspaceParams?.dataSourceId && (curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName || - (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName == null)); + (curWorkspaceParams?.databaseName === null && curWorkspaceParams?.schemaName === null)); useEffect(() => { clearData(); @@ -66,7 +68,7 @@ const workspacePage = memo((props) => { } function getConsoleList() { - let p: any = { + const p = { pageNo: 1, pageSize: 999, tabOpened: ConsoleOpenedStatus.IS_OPEN, @@ -94,7 +96,7 @@ const workspacePage = memo((props) => {
    - +
    diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts new file mode 100644 index 000000000..6060d7910 --- /dev/null +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -0,0 +1,71 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +let fieldList: Record = {}; + +/** 当前库下的表 */ +let intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + return { + suggestions: [], + }; + }, +}); + +const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseName, schemaName) => { + intelliSenseField.dispose(); + fieldList = {}; + intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { + triggerCharacters: [' ', '.'], + provideCompletionItems: async (model, position) => { + console.log('registerIntelliSenseField start'); + // 获取到当前行文本 + const textUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + + // 查找最后一个单词,这里通过正则获取空格或句号之前的最后一个单词 + const match = textUntilPosition.match(/([\w]+)[\s\.]$/); + + if (!match) { + return; // 如果没有匹配到,直接返回 + } + + const word = match[1]; // 获取匹配到的单词 + + if (word && tableList.includes(word) && !fieldList[word]) { + console.log('registerIntelliSenseField start word'); + const response = await fetch( + `/api/rdb/table/column_list?dataSourceId=${dataSourceId}&databaseName=${databaseName}&tableName=${word}`, + {}, + ); + const data = await response.json(); + + fieldList[word] = data.data; + } + + const suggestions = Object.keys(fieldList).reduce((acc, cur) => { + const arr = fieldList[cur].map((fieldObj) => ({ + label: { + label: fieldObj.name, + detail: `(${fieldObj.tableName})`, + description: '字段名', + }, + kind: monaco.languages.CompletionItemKind.Field, + insertText: fieldObj.name, + })); + + return [...acc, ...arr]; + }, []); + console.log('field suggestions', suggestions); + + return { + suggestions, + }; + }, + }); +}; + +export { intelliSenseField, registerIntelliSenseField }; diff --git a/chat2db-client/src/utils/IntelliSense/index.ts b/chat2db-client/src/utils/IntelliSense/index.ts new file mode 100644 index 000000000..1436d8783 --- /dev/null +++ b/chat2db-client/src/utils/IntelliSense/index.ts @@ -0,0 +1,11 @@ +import { intelliSenseKeyword, registerIntelliSenseKeyword } from './keyword'; +import { intelliSenseTable, registerIntelliSenseTable } from './table'; +import { intelliSenseField, registerIntelliSenseField } from './field'; +export { + intelliSenseKeyword, + registerIntelliSenseKeyword, + intelliSenseTable, + registerIntelliSenseTable, + intelliSenseField, + registerIntelliSenseField, +}; diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts new file mode 100644 index 000000000..3e63fbdd9 --- /dev/null +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -0,0 +1,65 @@ +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { DatabaseTypeCode } from '@/constants'; +import intelliSense from '@/constants/IntelliSense'; + +/** 关键词 */ +const getSQLKeywords = (keywords: string[]) => { + return keywords.map((key: any) => ({ + label: { + label: key, + detail: '', + description: '关键词', + }, + kind: monaco.languages.CompletionItemKind.Text, + insertText: key, + })); +}; + +/** 函数 */ +const getSQLFunctions = (functions: string[]) => { + return functions.map((key: any) => ({ + label: { + label: key, + detail: '', + description: '函数', + }, + kind: monaco.languages.CompletionItemKind.Method, + insertText: key, + })); +}; + +let intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: () => { + return { suggestions: [] }; + }, +}); + +const registerIntelliSenseKeyword = (databaseCode?: DatabaseTypeCode) => { + intelliSenseKeyword.dispose(); + intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { + triggerCharacters: [' ', '('], + provideCompletionItems: (model, position) => { + if (databaseCode === DatabaseTypeCode.POSTGRESQL) { + const intelliSensePostgreSQL = Object.values(intelliSense).find((v) => v.type === DatabaseTypeCode.ORACLE); + if (!intelliSensePostgreSQL) return { suggestions: [] }; + return { + suggestions: [ + ...getSQLKeywords(intelliSensePostgreSQL?.keywords), + ...getSQLFunctions(intelliSensePostgreSQL?.functions), + ], + }; + } else { + const intelliSenseMySQL = Object.values(intelliSense).find((v) => v.type === DatabaseTypeCode.MYSQL); + if (!intelliSenseMySQL) return { suggestions: [] }; + return { + suggestions: [ + ...getSQLKeywords(intelliSenseMySQL?.keywords), + ...getSQLFunctions(intelliSenseMySQL?.functions), + ], + }; + } + }, + }); +}; + +export { intelliSenseKeyword, registerIntelliSenseKeyword }; diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts new file mode 100644 index 000000000..4c6f545a5 --- /dev/null +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -0,0 +1,47 @@ +import { DatabaseTypeCode } from '@/constants'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +/** 当前库下的表 */ +let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + return { + suggestions: [], + }; + }, +}); + +const registerIntelliSenseTable = ( + tableList: Array<{ name: string; comment: string }>, + databaseName?: string, + databaseCode?: DatabaseTypeCode, +) => { + intelliSenseTable.dispose(); + intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { + provideCompletionItems: (model, position) => { + const handleInsertText = (text) => { + if (databaseCode === DatabaseTypeCode.POSTGRESQL) { + return `"${text}"`; + } else { + return `\`${text}\``; + } + }; + return { + suggestions: (tableList || []).map((tableName) => { + return { + label: { + label: tableName.name, + detail: databaseName ? `(${databaseName})` : null, + description: '表名', + }, + kind: monaco.languages.CompletionItemKind.Variable, + insertText: handleInsertText(tableName.name), + // range: monaco.Range.fromPositions(position), + documentation: tableName.comment, + }; + }), + }; + }, + }); +}; + +export { intelliSenseTable, registerIntelliSenseTable }; diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index 87b17ce32..d57df5d57 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -214,8 +214,8 @@ export function getApplicationMessage() { env, versions, buildTime, - userAgent - } + userAgent, + }; } // os is mac or windows @@ -225,11 +225,15 @@ export function OSnow(): { } { const agent = navigator.userAgent.toLowerCase(); const isMac = /macintosh|mac os x/i.test(navigator.userAgent); - const isWin = agent.indexOf("win32") >= 0 || agent.indexOf("wow32") >= 0 || agent.indexOf("win64") >= 0 || agent.indexOf("wow64") >= 0 + const isWin = + agent.indexOf('win32') >= 0 || + agent.indexOf('wow32') >= 0 || + agent.indexOf('win64') >= 0 || + agent.indexOf('wow64') >= 0; return { isMac, - isWin - } + isWin, + }; } // 格式化sql @@ -238,20 +242,20 @@ export function formatSql(sql: string, dbType: DatabaseTypeCode) { let formatRes = ''; try { formatRes = format(sql || ''); - } - catch {} + } catch {} // 如果格式化失败,直接返回原始sql if (!formatRes) { - sqlServer.sqlFormat({ - sql, - dbType, - }).then((res) => { - formatRes = res; - r(formatRes); - }) + sqlServer + .sqlFormat({ + sql, + dbType, + }) + .then((res) => { + formatRes = res; + r(formatRes); + }); } else { r(formatRes); } - }) + }); } - diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 221876e00..9c7f9c872 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -8768,6 +8768,13 @@ react-merge-refs@^1.1.0: resolved "https://registry.npmmirror.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== +react-monaco-editor@^0.54.0: + version "0.54.0" + resolved "https://registry.npmmirror.com/react-monaco-editor/-/react-monaco-editor-0.54.0.tgz#ec9293249a991b08264be723c1ec0ca3a6d480d8" + integrity sha512-9JwO69851mfpuhYLHlKbae7omQWJ/2ICE2lbL0VHyNyZR8rCOH7440u+zAtDgiOMpLwmYdY1sEZCdRefywX6GQ== + dependencies: + prop-types "^15.8.1" + react-redux@^8.0.5: version "8.1.1" resolved "https://registry.npmmirror.com/react-redux/-/react-redux-8.1.1.tgz#8e740f3fd864a4cd0de5ba9cdc8ad39cc9e7c81a" From a1803e9c847a2f0eb1b84630e4953ccd9493b67d Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 12 Oct 2023 14:52:12 +0800 Subject: [PATCH 0885/1069] feat: Optimize code editor intelli sense --- .../src/components/TabsNew/index.tsx | 1 - .../components/WorkspaceRight/index.tsx | 24 +-- chat2db-client/src/service/sql.ts | 173 ++++++++++++------ .../src/utils/IntelliSense/field.ts | 34 ++-- .../src/utils/IntelliSense/table.ts | 5 +- 5 files changed, 149 insertions(+), 88 deletions(-) diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 9c5db7009..fd1af010e 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -37,7 +37,6 @@ export default memo((props) => { const [internalTabs, setInternalTabs] = useState([]); const [internalActiveTab, setInternalActiveTab] = useState(); const [editingTab, setEditingTab] = useState(); - console.log('items', items); useEffect(() => { if (activeKey !== null && activeKey !== undefined) { 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 edc6737e1..e7888830b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -17,7 +17,11 @@ import { handleLocalStorageSavedConsole } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; import { IWorkspaceTab } from '@/typings'; -import { registerIntelliSenseField, registerIntelliSenseKeyword, registerIntelliSenseTable } from '@/utils/IntelliSense'; +import { + registerIntelliSenseField, + registerIntelliSenseKeyword, + registerIntelliSenseTable, +} from '@/utils/IntelliSense'; interface IProps { className?: string; @@ -305,18 +309,16 @@ const WorkspaceRight = memo((props: IProps) => { useEffect(() => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; if (databaseName) { - fetch( - `/api/rdb/table/table_list?dataSourceId=${dataSourceId}&databaseName=${databaseName}`, - // `/api/rdb/table/table_list?dataSourceId=${dataSourceId}&databaseName=${databaseName}&schemaName=${schemaName}`, - {}, - ) - .then((res) => { - return res.json(); + sqlService + .getAllTableList({ + dataSourceId, + databaseName, + schemaName, }) .then((data) => { - console.log('databaseName', data.data); - tableList.current = (data.data || []).map((item: any) => item.name); - registerIntelliSenseTable(data.data, databaseName, databaseType); + console.log('databaseName', data); + tableList.current = (data || []).map((item: any) => item.name); + registerIntelliSenseTable(data, databaseName, databaseType); registerIntelliSenseField(tableList.current, dataSourceId, databaseName, schemaName); }); } diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 3aae6fc1d..91072d859 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,5 +1,13 @@ import createRequest from './base'; -import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseSupportField, IEditTableInfo } from '@/typings'; +import { + IPageResponse, + IPageParams, + IUniversalTableParams, + IManageResultData, + IRoutines, + IDatabaseSupportField, + IEditTableInfo, +} from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; @@ -133,71 +141,112 @@ export interface IExportParams extends IExecuteSqlParams { const getViewList = createRequest>('/api/rdb/view/list', { method: 'get' }); /** 获取函数列表 */ -const getFunctionList = createRequest>('/api/rdb/function/list', { method: 'get' }); +const getFunctionList = createRequest>('/api/rdb/function/list', { + method: 'get', +}); /** 获取触发器列表 */ -const getTriggerList = createRequest>('/api/rdb/trigger/list', { method: 'get' }); +const getTriggerList = createRequest>('/api/rdb/trigger/list', { + method: 'get', +}); /** 获取过程列表 */ -const getProcedureList = createRequest>('/api/rdb/procedure/list', { method: 'get' }); +const getProcedureList = createRequest>('/api/rdb/procedure/list', { + method: 'get', +}); /** 获取视图列列表 */ -const getViewColumnList = createRequest>('/api/rdb/view/column_list', { method: 'get' }); +const getViewColumnList = createRequest>('/api/rdb/view/column_list', { + method: 'get', +}); /** 获取视图详情 */ -const getViewDetail = createRequest<{ - dataSourceId: number; - databaseName: string; - schemaName?: string; - tableName: string -}, {ddl: string}>('/api/rdb/view/detail', { method: 'get' }); +const getViewDetail = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string; + tableName: string; + }, + { ddl: string } +>('/api/rdb/view/detail', { method: 'get' }); /** 获取触发器详情 */ -const getTriggerDetail = createRequest<{ - dataSourceId: number; - databaseName: string; - schemaName?: string; - triggerName: string -}, {triggerBody: string}>('/api/rdb/trigger/detail', { method: 'get' }); +const getTriggerDetail = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string; + triggerName: string; + }, + { triggerBody: string } +>('/api/rdb/trigger/detail', { method: 'get' }); /** 获取函数详情 */ -const getFunctionDetail = createRequest<{ - dataSourceId: number; - databaseName: string; - schemaName?: string; - functionName: string -}, {functionBody: string}>('/api/rdb/function/detail', { method: 'get' }); +const getFunctionDetail = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string; + functionName: string; + }, + { functionBody: string } +>('/api/rdb/function/detail', { method: 'get' }); /** 获取过程详情 */ -const getProcedureDetail = createRequest<{ - dataSourceId: number; - databaseName: string; - schemaName?: string; - procedureName: string -}, { procedureBody: string }>('/api/rdb/procedure/detail', { method: 'get' }); +const getProcedureDetail = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string; + procedureName: string; + }, + { procedureBody: string } +>('/api/rdb/procedure/detail', { method: 'get' }); /** 格式化sql */ -const sqlFormat = createRequest<{ - sql: string; - dbType: DatabaseTypeCode; -}, string>('/api/sql/format', { method: 'get' }); - -/** 数据库支持的数据类型 */ -const getDatabaseFieldTypeList = createRequest<{ - dataSourceId: number; - databaseName: string; -}, IDatabaseSupportField>('/api/rdb/table/table_meta', { method: 'get' }); - -/** 获取表的详情 */ -const getTableDetails = createRequest<{ - dataSourceId: number; - databaseName: string; - schemaName?: string | null; - tableName: string; - refresh: boolean; -}, IEditTableInfo>('/api/rdb/table/query', { method: 'get' }); - -export interface IModifyTableSqlParams { +const sqlFormat = createRequest< + { + sql: string; + dbType: DatabaseTypeCode; + }, + string +>('/api/sql/format', { method: 'get' }); + +/** 数据库支持的数据类型 */ +const getDatabaseFieldTypeList = createRequest< + { + dataSourceId: number; + databaseName: string; + }, + IDatabaseSupportField +>('/api/rdb/table/table_meta', { method: 'get' }); + +/** 获取表的详情 */ +const getTableDetails = createRequest< + { + dataSourceId: number; + databaseName: string; + schemaName?: string | null; + tableName: string; + refresh: boolean; + }, + IEditTableInfo +>('/api/rdb/table/query', { method: 'get' }); + +/** 获取库的所有表 */ +const getAllTableList = createRequest< + { dataSourceId: number; databaseName: string; schemaName?: string | null }, + Array<{ name: string; comment: string }> +>('/api/rdb/table/table_list', { method: 'get' }); + +/** 获取表的所有字段 */ +const getAllFieldByTable = createRequest< + { dataSourceId: number; databaseName: string; schemaName?: string | null; tableName: string }, + Array<{ name: string; tableName: string }> +>('/api/rdb/table/column_list', { method: 'get' }); + +export interface IModifyTableSqlParams { dataSourceId: number; databaseName: string; schemaName?: string | null; @@ -207,16 +256,24 @@ export interface IModifyTableSqlParams { refresh: boolean; } -/** 获取修改表的sql */ -const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { method: 'post' }); +/** 获取修改表的sql */ +const getModifyTableSql = createRequest('/api/rdb/table/modify/sql', { + method: 'post', +}); -/** 执行编辑表的sql, 专为编辑表而生 */ -const executeDDL = createRequest('/api/rdb/dml/execute_ddl', { method: 'post' }); +/** 执行编辑表的sql, 专为编辑表而生 */ +const executeDDL = createRequest( + '/api/rdb/dml/execute_ddl', + { method: 'post' }, +); // 执行修改表数据的sql -const executeUpdateDataSql = createRequest('/api/rdb/dml/execute_update', { method: 'post' }); +const executeUpdateDataSql = createRequest( + '/api/rdb/dml/execute_update', + { method: 'post' }, +); -/** 获取修改表数据的接口 */ +/** 获取修改表数据的接口 */ const getExecuteUpdateSql = createRequest('/api/rdb/dml/get_update_sql', { method: 'post' }); export default { @@ -253,6 +310,6 @@ export default { deleteTablePin, getDMLCount, // exportResultTable + getAllTableList, + getAllFieldByTable }; - - diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index 6060d7910..ab71ed586 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -1,6 +1,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import sqlService from '@/service/sql'; -let fieldList: Record = {}; +let fieldList: Record> = {}; /** 当前库下的表 */ let intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { @@ -15,9 +16,8 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa intelliSenseField.dispose(); fieldList = {}; intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { - triggerCharacters: [' ', '.'], + triggerCharacters: [' ', '.', '`', "'", '"'], provideCompletionItems: async (model, position) => { - console.log('registerIntelliSenseField start'); // 获取到当前行文本 const textUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, @@ -26,24 +26,26 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa endColumn: position.column, }); - // 查找最后一个单词,这里通过正则获取空格或句号之前的最后一个单词 - const match = textUntilPosition.match(/([\w]+)[\s\.]$/); + const match = textUntilPosition.match(/(\b\w+\b)[^\w]*$/); + let word; + if (match) { + word = match[1]; + console.log(word); // 输出: text + } - if (!match) { + console.log('registerIntelliSenseField start', textUntilPosition, word); + if (!word) { return; // 如果没有匹配到,直接返回 } - - const word = match[1]; // 获取匹配到的单词 - if (word && tableList.includes(word) && !fieldList[word]) { console.log('registerIntelliSenseField start word'); - const response = await fetch( - `/api/rdb/table/column_list?dataSourceId=${dataSourceId}&databaseName=${databaseName}&tableName=${word}`, - {}, - ); - const data = await response.json(); - - fieldList[word] = data.data; + const data = await sqlService.getAllFieldByTable({ + dataSourceId, + databaseName, + schemaName, + tableName: word, + }); + fieldList[word] = data; } const suggestions = Object.keys(fieldList).reduce((acc, cur) => { diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 4c6f545a5..42f117a94 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -17,12 +17,13 @@ const registerIntelliSenseTable = ( ) => { intelliSenseTable.dispose(); intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { + triggerCharacters: [' '], provideCompletionItems: (model, position) => { const handleInsertText = (text) => { if (databaseCode === DatabaseTypeCode.POSTGRESQL) { - return `"${text}"`; + return `${text}`; } else { - return `\`${text}\``; + return `${text}`; } }; return { From 6834e38c7f17047ffa559e474504187a310a1dee Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 12 Oct 2023 15:37:04 +0800 Subject: [PATCH 0886/1069] feat: Supplement keywords and functions from different databases --- .../src/constants/IntelliSense/index.ts | 4 +- .../src/constants/IntelliSense/mysql.ts | 760 +++++++++++++++++- .../src/constants/IntelliSense/oracle.ts | 246 +++++- .../src/constants/IntelliSense/pgsql.ts | 722 +++++++++++++++++ .../src/constants/IntelliSense/redis.ts | 205 +++++ .../src/utils/IntelliSense/keyword.ts | 9 +- 6 files changed, 1924 insertions(+), 22 deletions(-) create mode 100644 chat2db-client/src/constants/IntelliSense/pgsql.ts create mode 100644 chat2db-client/src/constants/IntelliSense/redis.ts diff --git a/chat2db-client/src/constants/IntelliSense/index.ts b/chat2db-client/src/constants/IntelliSense/index.ts index 86131f4a2..ac945c7cb 100644 --- a/chat2db-client/src/constants/IntelliSense/index.ts +++ b/chat2db-client/src/constants/IntelliSense/index.ts @@ -1,4 +1,6 @@ import mysql from './mysql'; import oracle from './oracle'; +import postgresql from './pgsql'; +import redis from './redis'; -export default { mysql, oracle }; +export default { mysql, oracle, postgresql, redis }; diff --git a/chat2db-client/src/constants/IntelliSense/mysql.ts b/chat2db-client/src/constants/IntelliSense/mysql.ts index 3773ccde8..c75ae18cc 100644 --- a/chat2db-client/src/constants/IntelliSense/mysql.ts +++ b/chat2db-client/src/constants/IntelliSense/mysql.ts @@ -3,24 +3,756 @@ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.MYSQL, keywords: [ - 'SELECT', - 'FROM', - 'WHERE', - 'LIMIT', + '*', + 'ACCESSIBLE', + 'ADD', + 'ALL', + 'ALTER', + 'ANALYZE', 'AND', - 'OR', - 'NOT', + 'AS', + 'ASC', + 'ASENSITIVE', + 'BEFORE', 'BETWEEN', - 'LIKE', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOTH', + 'BY', + 'CALL', + 'CASCADE', + 'CASE', + 'CHANGE', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'COLLATE', + 'COLUMN', + 'CONDITION', + 'CONSTRAINT', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CUBE', + 'CUME_DIST', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DATABASES', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_SECOND', + 'DEC', + 'DECIMAL', + 'DECLARE', + 'DEFAULT', + 'DELAYED', + 'DELETE', + 'DENSE_RANK', + 'DESC', + 'DESCRIBE', + 'DETERMINISTIC', + 'DISTINCT', + 'DISTINCTROW', + 'DIV', + 'DOUBLE', + 'DROP', + 'DUAL', + 'EACH', + 'ELSE', + 'ELSEIF', + 'EMPTY', + 'ENCLOSED', + 'ESCAPED', + 'EXCEPT', + 'EXISTS', + 'EXIT', + 'EXPLAIN', + 'FALSE', + 'FETCH', + 'FIRST_VALUE', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'FOR', + 'FORCE', + 'FOREIGN', + 'FROM', + 'FULLTEXT', + 'FUNCTION', + 'GENERATED', + 'GET', + 'GRANT', + 'GROUP', + 'GROUPING', + 'GROUPS', + 'HAVING', + 'HIGH_PRIORITY', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_SECOND', + 'IF', + 'IGNORE', 'IN', + 'INDEX', + 'INFILE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INT', + 'INT1', + 'INT2', + 'INT3', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'INTO', + 'IO_AFTER_GTIDS', + 'IO_BEFORE_GTIDS', 'IS', - 'NULL', - 'ORDER BY', - 'GROUP BY', - 'HAVING', + 'ITERATE', + 'JOIN', + 'JSON_TABLE', + 'KEY', + 'KEYS', + 'KILL', + 'LAG', + 'LAST_VALUE', + 'LATERAL', + 'LEAD', + 'LEADING', + 'LEAVE', + 'LEFT', + 'LIKE', 'LIMIT', - 'ASC', - 'DESC', + 'LINEAR', + 'LINES', + 'LOAD', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCK', + 'LONG', + 'LONGBLOB', + 'LONGTEXT', + 'LOOP', + 'LOW_PRIORITY', + 'MASTER_BIND', + 'MASTER_SSL_VERIFY_SERVER_CERT', + 'MATCH', + 'MAXVALUE', + 'MEDIUMBLOB', + 'MEDIUMINT', + 'MEDIUMTEXT', + 'MIDDLEINT', + 'MINUTE_MICROSECOND', + 'MINUTE_SECOND', + 'MOD', + 'MODIFIES', + 'NATURAL', + 'NOT', + 'NO_WRITE_TO_BINLOG', + 'NTH_VALUE', + 'NTILE', + 'NULL', + 'NUMERIC', + 'OF', + 'ON', + 'OPTIMIZE', + 'OPTIMIZER_COSTS', + 'OPTION', + 'OPTIONALLY', + 'OR', + 'ORDER', + 'OUT', + 'OUTER', + 'OUTFILE', + 'OVER', + 'PARTITION', + 'PERCENT_RANK', + 'PRECISION', + 'PRIMARY', + 'PROCEDURE', + 'PURGE', + 'RANGE', + 'RANK', + 'READ', + 'READS', + 'READ_WRITE', + 'REAL', + 'RECURSIVE', + 'REFERENCES', + 'REGEXP', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'REPLACE', + 'REQUIRE', + 'RESIGNAL', + 'RESTRICT', + 'RETURN', + 'REVOKE', + 'RIGHT', + 'RLIKE', + 'ROW', + 'ROWS', + 'ROW_NUMBER', + 'SCHEMA', + 'SCHEMAS', + 'SECOND_MICROSECOND', + 'SELECT', + 'SENSITIVE', + 'SEPARATOR', + 'SET', + 'SHOW', + 'SIGNAL', + 'SMALLINT', + 'SPATIAL', + 'SPECIFIC', + 'SQL', + 'SQLEXCEPTION', + 'SQLSTATE', + 'SQLWARNING', + 'SQL_BIG_RESULT', + 'SQL_CALC_FOUND_ROWS', + 'SQL_SMALL_RESULT', + 'SSL', + 'STARTING', + 'STORED', + 'STRAIGHT_JOIN', + 'SYSTEM', + 'TABLE', + 'TERMINATED', + 'THEN', + 'TINYBLOB', + 'TINYINT', + 'TINYTEXT', + 'TO', + 'TRAILING', + 'TRIGGER', + 'TRUE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNLOCK', + 'UNSIGNED', + 'UPDATE', + 'USAGE', + 'USE', + 'USING', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'VALUES', + 'VARBINARY', + 'VARCHAR', + 'VARCHARACTER', + 'VARYING', + 'VIRTUAL', + 'WHEN', + 'WHERE', + 'WHILE', + 'WINDOW', + 'WITH', + 'WRITE', + 'XOR', + 'YEAR_MONTH', + 'ZEROFILL', + ], + functions: [ + 'ABS', + 'ACOS', + 'ADDDATE', + 'ADDTIME', + 'AES_DECRYPT', + 'AES_ENCRYPT', + 'ANY_VALUE', + 'Area', + 'AsBinary', + 'AsWKB', + 'ASCII', + 'ASIN', + 'AsText', + 'AsWKT', + 'ASYMMETRIC_DECRYPT', + 'ASYMMETRIC_DERIVE', + 'ASYMMETRIC_ENCRYPT', + 'ASYMMETRIC_SIGN', + 'ASYMMETRIC_VERIFY', + 'ATAN', + 'ATAN2', + 'ATAN', + 'AVG', + 'BENCHMARK', + 'BIN', + 'BIT_AND', + 'BIT_COUNT', + 'BIT_LENGTH', + 'BIT_OR', + 'BIT_XOR', + 'Buffer', + 'CAST', + 'CEIL', + 'CEILING', + 'Centroid', + 'CHAR', + 'CHAR_LENGTH', + 'CHARACTER_LENGTH', + 'CHARSET', + 'COALESCE', + 'COERCIBILITY', + 'COLLATION', + 'COMPRESS', + 'CONCAT', + 'CONCAT_WS', + 'CONNECTION_ID', + 'Contains', + 'CONV', + 'CONVERT', + 'CONVERT_TZ', + 'ConvexHull', + 'COS', + 'COT', + 'COUNT', + 'CRC32', + 'CREATE_ASYMMETRIC_PRIV_KEY', + 'CREATE_ASYMMETRIC_PUB_KEY', + 'CREATE_DH_PARAMETERS', + 'CREATE_DIGEST', + 'Crosses', + 'CUME_DIST', + 'CURDATE', + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURTIME', + 'DATABASE', + 'DATE', + 'DATE_ADD', + 'DATE_FORMAT', + 'DATE_SUB', + 'DATEDIFF', + 'DAY', + 'DAYNAME', + 'DAYOFMONTH', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DECODE', + 'DEFAULT', + 'DEGREES', + 'DES_DECRYPT', + 'DES_ENCRYPT', + 'DENSE_RANK', + 'Dimension', + 'Disjoint', + 'Distance', + 'ELT', + 'ENCODE', + 'ENCRYPT', + 'EndPoint', + 'Envelope', + 'Equals', + 'EXP', + 'EXPORT_SET', + 'ExteriorRing', + 'EXTRACT', + 'ExtractValue', + 'FIELD', + 'FIND_IN_SET', + 'FIRST_VALUE', + 'FLOOR', + 'FORMAT', + 'FORMAT_BYTES', + 'FORMAT_PICO_TIME', + 'FOUND_ROWS', + 'FROM_BASE64', + 'FROM_DAYS', + 'FROM_UNIXTIME', + 'GEN_RANGE', + 'GEN_RND_EMAIL', + 'GEN_RND_PAN', + 'GEN_RND_SSN', + 'GEN_RND_US_PHONE', + 'GeomCollection', + 'GeomCollFromText', + 'GeometryCollectionFromText', + 'GeomCollFromWKB', + 'GeometryCollectionFromWKB', + 'GeometryCollection', + 'GeometryN', + 'GeometryType', + 'GeomFromText', + 'GeometryFromText', + 'GeomFromWKB', + 'GeometryFromWKB', + 'GET_FORMAT', + 'GET_LOCK', + 'GLength', + 'GREATEST', + 'GROUP_CONCAT', + 'GROUPING', + 'GTID_SUBSET', + 'GTID_SUBTRACT', + 'HEX', + 'HOUR', + 'ICU_VERSION', + 'IF', + 'IFNULL', + 'INET_ATON', + 'INET_NTOA', + 'INET6_ATON', + 'INET6_NTOA', + 'INSERT', + 'INSTR', + 'InteriorRingN', + 'Intersects', + 'INTERVAL', + 'IS_FREE_LOCK', + 'IS_IPV4', + 'IS_IPV4_COMPAT', + 'IS_IPV4_MAPPED', + 'IS_IPV6', + 'IS_USED_LOCK', + 'IS_UUID', + 'IsClosed', + 'IsEmpty', + 'ISNULL', + 'IsSimple', + 'JSON_APPEND', + 'JSON_ARRAY', + 'JSON_ARRAY_APPEND', + 'JSON_ARRAY_INSERT', + 'JSON_ARRAYAGG', + 'JSON_CONTAINS', + 'JSON_CONTAINS_PATH', + 'JSON_DEPTH', + 'JSON_EXTRACT', + 'JSON_INSERT', + 'JSON_KEYS', + 'JSON_LENGTH', + 'JSON_MERGE', + 'JSON_MERGE_PATCH', + 'JSON_MERGE_PRESERVE', + 'JSON_OBJECT', + 'JSON_OBJECTAGG', + 'JSON_OVERLAPS', + 'JSON_PRETTY', + 'JSON_QUOTE', + 'JSON_REMOVE', + 'JSON_REPLACE', + 'JSON_SCHEMA_VALID', + 'JSON_SCHEMA_VALIDATION_REPORT', + 'JSON_SEARCH', + 'JSON_SET', + 'JSON_STORAGE_FREE', + 'JSON_STORAGE_SIZE', + 'JSON_TABLE', + 'JSON_TYPE', + 'JSON_UNQUOTE', + 'JSON_VALID', + 'LAG', + 'LAST_DAY', + 'LAST_INSERT_ID', + 'LAST_VALUE', + 'LCASE', + 'LEAD', + 'LEAST', + 'LEFT', + 'LENGTH', + 'LineFromText', + 'LineStringFromText', + 'LineFromWKB', + 'LineStringFromWKB', + 'LineString', + 'LN', + 'LOAD_FILE', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCATE', + 'LOG', + 'LOG10', + 'LOG2', + 'LOWER', + 'LPAD', + 'LTRIM', + 'MAKE_SET', + 'MAKEDATE', + 'MAKETIME', + 'MASK_INNER', + 'MASK_OUTER', + 'MASK_PAN', + 'MASK_PAN_RELAXED', + 'MASK_SSN', + 'MASTER_POS_WAIT', + 'MAX', + 'MBRContains', + 'MBRCoveredBy', + 'MBRCovers', + 'MBRDisjoint', + 'MBREqual', + 'MBREquals', + 'MBRIntersects', + 'MBROverlaps', + 'MBRTouches', + 'MBRWithin', + 'MD5', + 'MEMBER OF', + 'MICROSECOND', + 'MID', + 'MIN', + 'MINUTE', + 'MLineFromText', + 'MultiLineStringFromText', + 'MLineFromWKB', + 'MultiLineStringFromWKB', + 'MOD', + 'MONTH', + 'MONTHNAME', + 'MPointFromText', + 'MultiPointFromText', + 'MPointFromWKB', + 'MultiPointFromWKB', + 'MPolyFromText', + 'MultiPolygonFromText', + 'MPolyFromWKB', + 'MultiPolygonFromWKB', + 'MultiLineString', + 'MultiPoint', + 'MultiPolygon', + 'NAME_CONST', + 'NOT IN', + 'NOW', + 'NTH_VALUE', + 'NTILE', + 'NULLIF', + 'NumGeometries', + 'NumInteriorRings', + 'NumPoints', + 'OCT', + 'OCTET_LENGTH', + 'OLD_PASSWORD', + 'ORD', + 'Overlaps', + 'PASSWORD', + 'PERCENT_RANK', + 'PERIOD_ADD', + 'PERIOD_DIFF', + 'PI', + 'Point', + 'PointFromText', + 'PointFromWKB', + 'PointN', + 'PolyFromText', + 'PolygonFromText', + 'PolyFromWKB', + 'PolygonFromWKB', + 'Polygon', + 'POSITION', + 'POW', + 'POWER', + 'PS_CURRENT_THREAD_ID', + 'PS_THREAD_ID', + 'PROCEDURE ANALYSE', + 'QUARTER', + 'QUOTE', + 'RADIANS', + 'RAND', + 'RANDOM_BYTES', + 'RANK', + 'REGEXP_INSTR', + 'REGEXP_LIKE', + 'REGEXP_REPLACE', + 'REGEXP_REPLACE', + 'RELEASE_ALL_LOCKS', + 'RELEASE_LOCK', + 'REPEAT', + 'REPLACE', + 'REVERSE', + 'RIGHT', + 'ROLES_GRAPHML', + 'ROUND', + 'ROW_COUNT', + 'ROW_NUMBER', + 'RPAD', + 'RTRIM', + 'SCHEMA', + 'SEC_TO_TIME', + 'SECOND', + 'SESSION_USER', + 'SHA1', + 'SHA', + 'SHA2', + 'SIGN', + 'SIN', + 'SLEEP', + 'SOUNDEX', + 'SOURCE_POS_WAIT', + 'SPACE', + 'SQRT', + 'SRID', + 'ST_Area', + 'ST_AsBinary', + 'ST_AsWKB', + 'ST_AsGeoJSON', + 'ST_AsText', + 'ST_AsWKT', + 'ST_Buffer', + 'ST_Buffer_Strategy', + 'ST_Centroid', + 'ST_Collect', + 'ST_Contains', + 'ST_ConvexHull', + 'ST_Crosses', + 'ST_Difference', + 'ST_Dimension', + 'ST_Disjoint', + 'ST_Distance', + 'ST_Distance_Sphere', + 'ST_EndPoint', + 'ST_Envelope', + 'ST_Equals', + 'ST_ExteriorRing', + 'ST_FrechetDistance', + 'ST_GeoHash', + 'ST_GeomCollFromText', + 'ST_GeometryCollectionFromText', + 'ST_GeomCollFromTxt', + 'ST_GeomCollFromWKB', + 'ST_GeometryCollectionFromWKB', + 'ST_GeometryN', + 'ST_GeometryType', + 'ST_GeomFromGeoJSON', + 'ST_GeomFromText', + 'ST_GeometryFromText', + 'ST_GeomFromWKB', + 'ST_GeometryFromWKB', + 'ST_HausdorffDistance', + 'ST_InteriorRingN', + 'ST_Intersection', + 'ST_Intersects', + 'ST_IsClosed', + 'ST_IsEmpty', + 'ST_IsSimple', + 'ST_IsValid', + 'ST_LatFromGeoHash', + 'ST_Length', + 'ST_LineFromText', + 'ST_LineStringFromText', + 'ST_LineFromWKB', + 'ST_LineStringFromWKB', + 'ST_LineInterpolatePoint', + 'ST_LineInterpolatePoints', + 'ST_LongFromGeoHash', + 'ST_Longitude', + 'ST_MakeEnvelope', + 'ST_MLineFromText', + 'ST_MultiLineStringFromText', + 'ST_MLineFromWKB', + 'ST_MultiLineStringFromWKB', + 'ST_MPointFromText', + 'ST_MultiPointFromText', + 'ST_MPointFromWKB', + 'ST_MultiPointFromWKB', + 'ST_MPolyFromText', + 'ST_MultiPolygonFromText', + 'ST_MPolyFromWKB', + 'ST_MultiPolygonFromWKB', + 'ST_NumGeometries', + 'ST_NumInteriorRing', + 'ST_NumInteriorRings', + 'ST_NumPoints', + 'ST_Overlaps', + 'ST_PointAtDistance', + 'ST_PointFromGeoHash', + 'ST_PointFromText', + 'ST_PointFromWKB', + 'ST_PointN', + 'ST_PolyFromText', + 'ST_PolygonFromText', + 'ST_PolyFromWKB', + 'ST_PolygonFromWKB', + 'ST_Simplify', + 'ST_SRID', + 'ST_StartPoint', + 'ST_SwapXY', + 'ST_SymDifference', + 'ST_Touches', + 'ST_Transform', + 'ST_Union', + 'ST_Validate', + 'ST_Within', + 'ST_X', + 'ST_Y', + 'StartPoint', + 'STATEMENT_DIGEST', + 'STATEMENT_DIGEST_TEXT', + 'STD', + 'STDDEV', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'STR_TO_DATE', + 'STRCMP', + 'SUBDATE', + 'SUBSTR', + 'SUBSTRING', + 'SUBSTRING_INDEX', + 'SUBTIME', + 'SUM', + 'SYSDATE', + 'SYSTEM_USER', + 'TAN', + 'TIME', + 'TIME_FORMAT', + 'TIME_TO_SEC', + 'TIMEDIFF', + 'TIMESTAMP', + 'TIMESTAMPADD', + 'TIMESTAMPDIFF', + 'TO_BASE64', + 'TO_DAYS', + 'TO_SECONDS', + 'Touches', + 'TRIM', + 'TRUNCATE', + 'UCASE', + 'UNCOMPRESS', + 'UNCOMPRESSED_LENGTH', + 'UNHEX', + 'UNIX_TIMESTAMP', + 'UpdateXML', + 'UPPER', + 'USER', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'UUID', + 'UUID_SHORT', + 'UUID_TO_BIN', + 'VALIDATE_PASSWORD_STRENGTH', + 'VALUES', + 'VAR_POP', + 'VAR_SAMP', + 'VARIANCE', + 'VERSION', + 'WAIT_FOR_EXECUTED_GTID_SET', + 'WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS', + 'WEEK', + 'WEEKDAY', + 'WEEKOFYEAR', + 'WEIGHT_STRING', + 'Within', + 'X', + 'Y', + 'YEAR', + 'YEARWEEK', ], - functions: ['AVG', 'COUNT', 'FIRST', 'LAST', 'MAX', 'MIN', 'SUM', 'MID', 'LEN', 'ROUND', 'NOW', 'FORMAT'], }; diff --git a/chat2db-client/src/constants/IntelliSense/oracle.ts b/chat2db-client/src/constants/IntelliSense/oracle.ts index d6902d540..bd3d1ff23 100644 --- a/chat2db-client/src/constants/IntelliSense/oracle.ts +++ b/chat2db-client/src/constants/IntelliSense/oracle.ts @@ -2,6 +2,248 @@ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.ORACLE, - keywords: ['Oracle keyword'], - functions: ['Oracle function'], + keywords: [ + 'ABS', + 'ACOS', + 'ADD_MONTHS', + 'ASCII', + 'ASIN', + 'ATAN', + 'ATAN2', + 'AVG', + 'BFILENAME', + 'BIN_TO_NUM', + 'BITAND', + 'CARDINALITY', + 'CAST', + 'CEIL', + 'CHARTOROWID', + 'CHR', + 'COALESCE', + 'COMPOSE', + 'CON_DBID_TO_ID', + 'CON_GUID_TO_ID', + 'CON_ID_TO_CONTRACT', + 'CON_NAME_TO_CONTRACT', + 'CONCAT', + 'CONVERT', + 'CORR', + 'COS', + 'COSH', + 'COUNT', + 'COVAR_POP', + 'COVAR_SAMP', + 'CUME_DIST', + 'CURRENT_DATE', + 'CURRENT_TIMESTAMP', + 'DBTIMEZONE', + 'DECODE', + 'DECOMPOSE', + 'DENSE_RANK', + 'DUMP', + 'EMPTY_BLOB', + 'EMPTY_CLOB', + 'EXISTSNODE', + 'EXP', + 'EXTRACT', + 'EXTRACTVALUE', + 'FIRST_VALUE', + 'FLOOR', + 'FROM_TZ', + 'GREATEST', + 'GROUP_ID', + 'GROUPING', + 'GROUPING_ID', + 'HEXTORAW', + 'INITCAP', + 'INSTR', + 'INSTR2', + 'INSTR4', + 'INSTRB', + 'INSTRC', + 'LAG', + 'LAST_DAY', + 'LAST_VALUE', + 'LEAD', + 'LEAST', + 'LENGTH', + 'LENGTH2', + 'LENGTH4', + 'LENGTHB', + 'LENGTHC', + 'LISTAGG', + 'LN', + 'LNNVL', + 'LOCALTIMESTAMP', + 'LOG', + 'LOWER', + 'LPAD', + 'LTRIM', + 'MAX', + 'MEDIAN', + 'MIN', + 'MOD', + 'MONTHS_BETWEEN', + 'NANVL', + 'NCHR', + 'NEW_TIME', + 'NEXT_DAY', + 'NTH_VALUE', + 'NULLIF', + 'NUMTODSINTERVAL', + 'NUMTOYMINTERVAL', + 'NVL', + 'NVL2', + 'POWER', + 'RANK', + 'RAWTOHEX', + 'REGEXP_COUNT', + 'REGEXP_INSTR', + 'REGEXP_REPLACE', + 'REGEXP_SUBSTR', + 'REGR_AVGX', + 'REGR_AVGY', + 'REGR_COUNT', + 'REGR_INTERCEPT', + 'REGR_R2', + 'REGR_SLOPE', + 'REGR_SXX', + 'REGR_SXY', + 'REGR_SYY', + 'REPLACE', + 'ROUND', + 'ROW_NUMBER', + 'ROWIDTOCHAR', + 'ROWIDTONCHAR', + 'RPAD', + 'RTRIM', + 'SESSIONTIMEZONE', + 'SIGN', + 'SIN', + 'SINH', + 'SOUNDEX', + 'SQRT', + 'STDDEV', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'SUBSTR', + 'SUM', + 'SYS_CONNECT_BY_PATH', + 'SYS_CONTEXT', + 'SYS_DBURIGEN', + 'SYS_EXTRACT_UTC', + 'SYS_GUID', + 'SYS_XMLAGG', + 'SYS_XMLGEN', + 'TAN', + 'TANH', + 'TO_CHAR', + 'TO_CLOB', + 'TO_DATE', + 'TO_DSINTERVAL', + 'TO_LOB', + 'TO_MULTI_BYTE', + 'TO_NCLOB', + 'TO_NUMBER', + 'TO_SINGLE_BYTE', + 'TO_YMINTERVAL', + 'TRANSLATE', + 'TRIM', + 'TRUNC', + 'TZ_OFFSET', + 'UID', + 'UNISTR', + 'UPPER', + 'USER', + 'USERENV', + 'VAR_POP', + 'VAR_SAMP', + 'VARIANCE', + 'VSIZE', + 'WIDTH_BUCKET', + 'XMLAGG', + 'XMLCOLATTVAL', + 'XMLELEMENT', + 'XMLFOREST', + 'XMLSEQUENCE', + 'XMLTRANSFORM', + ], + + keywords: [ + 'CONNECT BY', + 'START WITH', + 'ORDER SIBLINGS BY', + 'SIBLINGS', + 'PRIOR', + 'NOCYCLE', + 'LEVEL', + 'GROUP BY', + 'GROUPING SETS', + 'CUBE', + 'ROLLUP', + 'PIVOT', + 'UNPIVOT', + 'MODEL', + 'DIMENSION', + 'MEASURES', + 'RULES', + 'ITERATE', + 'INCREMENT', + 'PRESENTV', + 'ABSENTV', + 'SQLQUERY', + 'ARRAY', + 'VARRAY', + 'ASSOCIATIVE ARRAY', + 'RECORD', + 'OBJECT', + 'TABLE', + 'VIEW', + 'MATERIALIZED VIEW', + 'CONTEXT', + 'DIRECTORY', + 'EDITION', + 'Profile', + 'ROLE', + 'SEQUENCE', + 'SYNONYM', + 'INDEXTYPE', + 'LIBRARY', + 'OPERATOR', + 'PROCEDURE', + 'FUNCTION', + 'PACKAGE', + 'TYPE', + 'TRIGGER', + 'LINK', + 'VIEW LOG', + 'CONSTRAINT', + 'CLUSTER', + 'COMMENT', + 'AUDIT', + 'NOAUDIT', + 'GRANT', + 'REVOKE', + 'FLASHBACK', + 'PURGE', + 'MERGE', + 'ALTER SESSION', + 'COMMIT', + 'ROLLBACK', + 'SAVEPOINT', + 'SET TRANSACTION', + 'LOCK', + 'ROW SHARE', + 'ROW EXCLUSIVE', + 'SHARE UPDATE', + 'SHARE', + 'EXCLUSIVE', + 'NOWAIT', + 'SKIP LOCKED', + 'PL/SQL', + 'AUTONOMOUS_TRANSACTION', + 'PRAGMA', + 'EXCEPTION', + 'SERIALLY_REUSABLE', + ], }; diff --git a/chat2db-client/src/constants/IntelliSense/pgsql.ts b/chat2db-client/src/constants/IntelliSense/pgsql.ts new file mode 100644 index 000000000..9806a9026 --- /dev/null +++ b/chat2db-client/src/constants/IntelliSense/pgsql.ts @@ -0,0 +1,722 @@ +import { DatabaseTypeCode } from '../common'; + +export default { + type: DatabaseTypeCode.POSTGRESQL, + keywords: [ + '*', + 'ALL', + 'ANALYSE', + 'ANALYZE', + 'AND', + 'ANY', + 'ARRAY', + 'AS', + 'ASC', + 'ASYMMETRIC', + 'AUTHORIZATION', + 'BINARY', + 'BOTH', + 'CASE', + 'CAST', + 'CHECK', + 'COLLATE', + 'COLLATION', + 'COLUMN', + 'CONCURRENTLY', + 'CONSTRAINT', + 'CREATE', + 'CROSS', + 'CURRENT_CATALOG', + 'CURRENT_DATE', + 'CURRENT_ROLE', + 'CURRENT_SCHEMA', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'DEFAULT', + 'DEFERRABLE', + 'DESC', + 'DISTINCT', + 'DO', + 'ELSE', + 'END', + 'EXCEPT', + 'FALSE', + 'FETCH', + 'FOR', + 'FOREIGN', + 'FREEZE', + 'FROM', + 'FULL', + 'GRANT', + 'GROUP', + 'HAVING', + 'ILIKE', + 'IN', + 'INITIALLY', + 'INNER', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'JOIN', + 'LATERAL', + 'LEADING', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'NATURAL', + 'NOT', + 'NOTNULL', + 'NULL', + 'OFFSET', + 'ON', + 'ONLY', + 'OR', + 'ORDER', + 'OUTER', + 'OVERLAPS', + 'PLACING', + 'PRIMARY', + 'REFERENCES', + 'RETURNING', + 'RIGHT', + 'SELECT', + 'SESSION_USER', + 'SIMILAR', + 'SOME', + 'SYMMETRIC', + 'TABLE', + 'TABLESAMPLE', + 'THEN', + 'TO', + 'TRAILING', + 'TRUE', + 'UNION', + 'UNIQUE', + 'USER', + 'USING', + 'VARIADIC', + 'VERBOSE', + 'WHEN', + 'WHERE', + 'WINDOW', + 'WITH', + ], + functions: [ + 'abbrev', + 'abs', + 'acldefault', + 'aclexplode', + 'acos', + 'acosd', + 'acosh', + 'age', + 'any', + 'area', + 'array_agg', + 'array_append', + 'array_cat', + 'array_dims', + 'array_fill', + 'array_length', + 'array_lower', + 'array_ndims', + 'array_position', + 'array_positions', + 'array_prepend', + 'array_remove', + 'array_replace', + 'array_to_json', + 'array_to_string', + 'array_to_tsvector', + 'array_upper', + 'ascii', + 'asin', + 'asind', + 'asinh', + 'atan', + 'atan2', + 'atan2d', + 'atand', + 'atanh', + 'avg', + 'bit', + 'bit_and', + 'bit_count', + 'bit_length', + 'bit_or', + 'bit_xor', + 'bool_and', + 'bool_or', + 'bound_box', + 'box', + 'brin_desummarize_range', + 'brin_summarize_new_values', + 'brin_summarize_range', + 'broadcast', + 'btrim', + 'cardinality', + 'cbrt', + 'ceil', + 'ceiling', + 'center', + 'char_length', + 'character_length', + 'chr', + 'circle', + 'clock_timestamp', + 'coalesce', + 'col_description', + 'concat', + 'concat_ws', + 'convert', + 'convert_from', + 'convert_to', + 'corr', + 'cos', + 'cosd', + 'cosh', + 'cot', + 'cotd', + 'count', + 'covar_pop', + 'covar_samp', + 'cume_dist', + 'current_catalog', + 'current_database', + 'current_date', + 'current_query', + 'current_role', + 'current_schema', + 'current_schemas', + 'current_setting', + 'current_time', + 'current_timestamp', + 'current_user', + 'currval', + 'cursor_to_xml', + 'cursor_to_xmlschema', + 'date_bin', + 'date_part', + 'date_trunc', + 'database_to_xml', + 'database_to_xml_and_xmlschema', + 'database_to_xmlschema', + 'decode', + 'degrees', + 'dense_rank', + 'diagonal', + 'diameter', + 'div', + 'encode', + 'enum_first', + 'enum_last', + 'enum_range', + 'every', + 'exp', + 'extract', + 'factorial', + 'family', + 'first_value', + 'floor', + 'format', + 'format_type', + 'gcd', + 'gen_random_uuid', + 'generate_series', + 'generate_subscripts', + 'get_bit', + 'get_byte', + 'get_current_ts_config', + 'gin_clean_pending_list', + 'greatest', + 'grouping', + 'has_any_column_privilege', + 'has_column_privilege', + 'has_database_privilege', + 'has_foreign_data_wrapper_privilege', + 'has_function_privilege', + 'has_language_privilege', + 'has_schema_privilege', + 'has_sequence_privilege', + 'has_server_privilege', + 'has_table_privilege', + 'has_tablespace_privilege', + 'has_type_privilege', + 'height', + 'host', + 'hostmask', + 'inet_client_addr', + 'inet_client_port', + 'inet_merge', + 'inet_same_family', + 'inet_server_addr', + 'inet_server_port', + 'initcap', + 'isclosed', + 'isempty', + 'isfinite', + 'isopen', + 'json_agg', + 'json_array_elements', + 'json_array_elements_text', + 'json_array_length', + 'json_build_array', + 'json_build_object', + 'json_each', + 'json_each_text', + 'json_extract_path', + 'json_extract_path_text', + 'json_object', + 'json_object_agg', + 'json_object_keys', + 'json_populate_record', + 'json_populate_recordset', + 'json_strip_nulls', + 'json_to_record', + 'json_to_recordset', + 'json_to_tsvector', + 'json_typeof', + 'jsonb_agg', + 'jsonb_array_elements', + 'jsonb_array_elements_text', + 'jsonb_array_length', + 'jsonb_build_array', + 'jsonb_build_object', + 'jsonb_each', + 'jsonb_each_text', + 'jsonb_extract_path', + 'jsonb_extract_path_text', + 'jsonb_insert', + 'jsonb_object', + 'jsonb_object_agg', + 'jsonb_object_keys', + 'jsonb_path_exists', + 'jsonb_path_match', + 'jsonb_path_query', + 'jsonb_path_query_array', + 'jsonb_path_exists_tz', + 'jsonb_path_query_first', + 'jsonb_path_query_array_tz', + 'jsonb_path_query_first_tz', + 'jsonb_path_query_tz', + 'jsonb_path_match_tz', + 'jsonb_populate_record', + 'jsonb_populate_recordset', + 'jsonb_pretty', + 'jsonb_set', + 'jsonb_set_lax', + 'jsonb_strip_nulls', + 'jsonb_to_record', + 'jsonb_to_recordset', + 'jsonb_to_tsvector', + 'jsonb_typeof', + 'justify_days', + 'justify_hours', + 'justify_interval', + 'lag', + 'last_value', + 'lastval', + 'lcm', + 'lead', + 'least', + 'left', + 'length', + 'line', + 'ln', + 'localtime', + 'localtimestamp', + 'log', + 'log10', + 'lower', + 'lower_inc', + 'lower_inf', + 'lpad', + 'lseg', + 'ltrim', + 'macaddr8_set7bit', + 'make_date', + 'make_interval', + 'make_time', + 'make_timestamp', + 'make_timestamptz', + 'makeaclitem', + 'masklen', + 'max', + 'md5', + 'min', + 'min_scale', + 'mod', + 'mode', + 'multirange', + 'netmask', + 'network', + 'nextval', + 'normalize', + 'now', + 'npoints', + 'nth_value', + 'ntile', + 'nullif', + 'num_nonnulls', + 'num_nulls', + 'numnode', + 'obj_description', + 'octet_length', + 'overlay', + 'parse_ident', + 'path', + 'pclose', + 'percent_rank', + 'percentile_cont', + 'percentile_disc', + 'pg_advisory_lock', + 'pg_advisory_lock_shared', + 'pg_advisory_unlock', + 'pg_advisory_unlock_all', + 'pg_advisory_unlock_shared', + 'pg_advisory_xact_lock', + 'pg_advisory_xact_lock_shared', + 'pg_backend_pid', + 'pg_backup_start_time', + 'pg_blocking_pids', + 'pg_cancel_backend', + 'pg_client_encoding', + 'pg_collation_actual_version', + 'pg_collation_is_visible', + 'pg_column_compression', + 'pg_column_size', + 'pg_conf_load_time', + 'pg_control_checkpoint', + 'pg_control_init', + 'pg_control_recovery', + 'pg_control_system', + 'pg_conversion_is_visible', + 'pg_copy_logical_replication_slot', + 'pg_copy_physical_replication_slot', + 'pg_create_logical_replication_slot', + 'pg_create_physical_replication_slot', + 'pg_create_restore_point', + 'pg_current_logfile', + 'pg_current_snapshot', + 'pg_current_wal_flush_lsn', + 'pg_current_wal_insert_lsn', + 'pg_current_wal_lsn', + 'pg_current_xact_id', + 'pg_current_xact_id_if_assigned', + 'pg_current_xlog_flush_location', + 'pg_current_xlog_insert_location', + 'pg_current_xlog_location', + 'pg_database_size', + 'pg_describe_object', + 'pg_drop_replication_slot', + 'pg_event_trigger_ddl_commands', + 'pg_event_trigger_dropped_objects', + 'pg_event_trigger_table_rewrite_oid', + 'pg_event_trigger_table_rewrite_reason', + 'pg_export_snapshot', + 'pg_filenode_relation', + 'pg_function_is_visible', + 'pg_get_catalog_foreign_keys', + 'pg_get_constraintdef', + 'pg_get_expr', + 'pg_get_function_arguments', + 'pg_get_function_identity_arguments', + 'pg_get_function_result', + 'pg_get_functiondef', + 'pg_get_indexdef', + 'pg_get_keywords', + 'pg_get_object_address', + 'pg_get_owned_sequence', + 'pg_get_ruledef', + 'pg_get_serial_sequence', + 'pg_get_statisticsobjdef', + 'pg_get_triggerdef', + 'pg_get_userbyid', + 'pg_get_viewdef', + 'pg_get_wal_replay_pause_state', + 'pg_has_role', + 'pg_identify_object', + 'pg_identify_object_as_address', + 'pg_import_system_collations', + 'pg_index_column_has_property', + 'pg_index_has_property', + 'pg_indexam_has_property', + 'pg_indexes_size', + 'pg_is_in_backup', + 'pg_is_in_recovery', + 'pg_is_other_temp_schema', + 'pg_is_wal_replay_paused', + 'pg_is_xlog_replay_paused', + 'pg_jit_available', + 'pg_last_committed_xact', + 'pg_last_wal_receive_lsn', + 'pg_last_wal_replay_lsn', + 'pg_last_xact_replay_timestamp', + 'pg_last_xlog_receive_location', + 'pg_last_xlog_replay_location', + 'pg_listening_channels', + 'pg_log_backend_memory_contexts', + 'pg_logical_emit_message', + 'pg_logical_slot_get_binary_changes', + 'pg_logical_slot_get_changes', + 'pg_logical_slot_peek_binary_changes', + 'pg_logical_slot_peek_changes', + 'pg_ls_archive_statusdir', + 'pg_ls_dir', + 'pg_ls_logdir', + 'pg_ls_tmpdir', + 'pg_ls_waldir', + 'pg_mcv_list_items', + 'pg_my_temp_schema', + 'pg_notification_queue_usage', + 'pg_opclass_is_visible', + 'pg_operator_is_visible', + 'pg_opfamily_is_visible', + 'pg_options_to_table', + 'pg_partition_ancestors', + 'pg_partition_root', + 'pg_partition_tree', + 'pg_postmaster_start_time', + 'pg_promote', + 'pg_read_binary_file', + 'pg_read_file', + 'pg_relation_filenode', + 'pg_relation_filepath', + 'pg_relation_size', + 'pg_reload_conf', + 'pg_replication_origin_advance', + 'pg_replication_origin_create', + 'pg_replication_origin_drop', + 'pg_replication_origin_oid', + 'pg_replication_origin_progress', + 'pg_replication_origin_session_is_setup', + 'pg_replication_origin_session_progress', + 'pg_replication_origin_session_reset', + 'pg_replication_origin_session_setup', + 'pg_replication_origin_xact_reset', + 'pg_replication_origin_xact_setup', + 'pg_replication_slot_advance', + 'pg_rotate_logfile', + 'pg_safe_snapshot_blocking_pids', + 'pg_size_bytes', + 'pg_size_pretty', + 'pg_sleep', + 'pg_sleep_for', + 'pg_sleep_until', + 'pg_snapshot_xip', + 'pg_snapshot_xmax', + 'pg_snapshot_xmin', + 'pg_start_backup', + 'pg_stat_file', + 'pg_statistics_obj_is_visible', + 'pg_stop_backup', + 'pg_switch_wal', + 'pg_switch_xlog', + 'pg_table_is_visible', + 'pg_table_size', + 'pg_tablespace_databases', + 'pg_tablespace_location', + 'pg_tablespace_size', + 'pg_terminate_backend', + 'pg_total_relation_size', + 'pg_trigger_depth', + 'pg_try_advisory_lock', + 'pg_try_advisory_lock_shared', + 'pg_try_advisory_xact_lock', + 'pg_try_advisory_xact_lock_shared', + 'pg_ts_config_is_visible', + 'pg_ts_dict_is_visible', + 'pg_ts_parser_is_visible', + 'pg_ts_template_is_visible', + 'pg_type_is_visible', + 'pg_typeof', + 'pg_visible_in_snapshot', + 'pg_wal_lsn_diff', + 'pg_wal_replay_pause', + 'pg_wal_replay_resume', + 'pg_walfile_name', + 'pg_walfile_name_offset', + 'pg_xact_commit_timestamp', + 'pg_xact_commit_timestamp_origin', + 'pg_xact_status', + 'pg_xlog_location_diff', + 'pg_xlog_replay_pause', + 'pg_xlog_replay_resume', + 'pg_xlogfile_name', + 'pg_xlogfile_name_offset', + 'phraseto_tsquery', + 'pi', + 'plainto_tsquery', + 'point', + 'polygon', + 'popen', + 'position', + 'power', + 'pqserverversion', + 'query_to_xml', + 'query_to_xml_and_xmlschema', + 'query_to_xmlschema', + 'querytree', + 'quote_ident', + 'quote_literal', + 'quote_nullable', + 'radians', + 'radius', + 'random', + 'range_agg', + 'range_intersect_agg', + 'range_merge', + 'rank', + 'regexp_match', + 'regexp_matches', + 'regexp_replace', + 'regexp_split_to_array', + 'regexp_split_to_table', + 'regr_avgx', + 'regr_avgy', + 'regr_count', + 'regr_intercept', + 'regr_r2', + 'regr_slope', + 'regr_sxx', + 'regr_sxy', + 'regr_syy', + 'repeat', + 'replace', + 'reverse', + 'right', + 'round', + 'row_number', + 'row_security_active', + 'row_to_json', + 'rpad', + 'rtrim', + 'scale', + 'schema_to_xml', + 'schema_to_xml_and_xmlschema', + 'schema_to_xmlschema', + 'session_user', + 'set_bit', + 'set_byte', + 'set_config', + 'set_masklen', + 'setseed', + 'setval', + 'setweight', + 'sha224', + 'sha256', + 'sha384', + 'sha512', + 'shobj_description', + 'sign', + 'sin', + 'sind', + 'sinh', + 'slope', + 'split_part', + 'sprintf', + 'sqrt', + 'starts_with', + 'statement_timestamp', + 'stddev', + 'stddev_pop', + 'stddev_samp', + 'string_agg', + 'string_to_array', + 'string_to_table', + 'strip', + 'strpos', + 'substr', + 'substring', + 'sum', + 'suppress_redundant_updates_trigger', + 'table_to_xml', + 'table_to_xml_and_xmlschema', + 'table_to_xmlschema', + 'tan', + 'tand', + 'tanh', + 'text', + 'timeofday', + 'timezone', + 'to_ascii', + 'to_char', + 'to_date', + 'to_hex', + 'to_json', + 'to_number', + 'to_regclass', + 'to_regcollation', + 'to_regnamespace', + 'to_regoper', + 'to_regoperator', + 'to_regproc', + 'to_regprocedure', + 'to_regrole', + 'to_regtype', + 'to_timestamp', + 'to_tsquery', + 'to_tsvector', + 'transaction_timestamp', + 'translate', + 'trim', + 'trim_array', + 'trim_scale', + 'trunc', + 'ts_debug', + 'ts_delete', + 'ts_filter', + 'ts_headline', + 'ts_lexize', + 'ts_parse', + 'ts_rank', + 'ts_rank_cd', + 'ts_rewrite', + 'ts_stat', + 'ts_token_type', + 'tsquery_phrase', + 'tsvector_to_array', + 'tsvector_update_trigger', + 'tsvector_update_trigger_column', + 'txid_current', + 'txid_current_if_assigned', + 'txid_current_snapshot', + 'txid_snapshot_xip', + 'txid_snapshot_xmax', + 'txid_snapshot_xmin', + 'txid_status', + 'txid_visible_in_snapshot', + 'unistr', + 'unnest', + 'upper', + 'upper_inc', + 'upper_inf', + 'user', + 'var_pop', + 'var_samp', + 'variance', + 'version', + 'websearch_to_tsquery', + 'width', + 'width_bucket', + 'xml_is_well_formed', + 'xml_is_well_formed_content', + 'xml_is_well_formed_document', + 'xmlagg', + 'xmlcomment', + 'xmlconcat', + 'xmlelement', + 'xmlexists', + 'xmlforest', + 'xmlparse', + 'xmlpi', + 'xmlroot', + 'xmlserialize', + 'xpath', + 'xpath_exists', + ], +}; diff --git a/chat2db-client/src/constants/IntelliSense/redis.ts b/chat2db-client/src/constants/IntelliSense/redis.ts new file mode 100644 index 000000000..e28afe2eb --- /dev/null +++ b/chat2db-client/src/constants/IntelliSense/redis.ts @@ -0,0 +1,205 @@ +import { DatabaseTypeCode } from '../common'; + +export default { + type: DatabaseTypeCode.REDIS, + keywords: [ + 'APPEND', + 'AUTH', + 'BGREWRITEAOF', + 'BGSAVE', + 'BITCOUNT', + 'BITFIELD', + 'BITOP', + 'BITPOS', + 'BLPOP', + 'BRPOP', + 'BRPOPLPUSH', + 'CLIENT', + 'KILL', + 'LIST', + 'GETNAME', + 'PAUSE', + 'REPLY', + 'SETNAME', + 'CLUSTER', + 'ADDSLOTS', + 'COUNT-FAILURE-REPORTS', + 'COUNTKEYSINSLOT', + 'DELSLOTS', + 'FAILOVER', + 'FORGET', + 'GETKEYSINSLOT', + 'INFO', + 'KEYSLOT', + 'MEET', + 'NODES', + 'REPLICATE', + 'RESET', + 'SAVECONFIG', + 'SET-CONFIG-EPOCH', + 'SETSLOT', + 'SLAVES', + 'SLOTS', + 'COMMAND', + 'COUNT', + 'GETKEYS', + 'CONFIG', + 'GET', + 'REWRITE', + 'SET', + 'RESETSTAT', + 'DBSIZE', + 'DEBUG', + 'OBJECT', + 'SEGFAULT', + 'DECR', + 'DECRBY', + 'DEL', + 'DISCARD', + 'DUMP', + 'ECHO', + 'EVAL', + 'EVALSHA', + 'EXEC', + 'EXISTS', + 'EXPIRE', + 'EXPIREAT', + 'FLUSHALL', + 'FLUSHDB', + 'GEOADD', + 'GEOHASH', + 'GEOPOS', + 'GEODIST', + 'GEORADIUS', + 'GEORADIUSBYMEMBER', + 'GETBIT', + 'GETRANGE', + 'GETSET', + 'HDEL', + 'HEXISTS', + 'HGET', + 'HGETALL', + 'HINCRBY', + 'HINCRBYFLOAT', + 'HKEYS', + 'HLEN', + 'HMGET', + 'HMSET', + 'HSET', + 'HSETNX', + 'HSTRLEN', + 'HVALS', + 'INCR', + 'INCRBY', + 'INCRBYFLOAT', + 'KEYS', + 'LASTSAVE', + 'LINDEX', + 'LINSERT', + 'LLEN', + 'LPOP', + 'LPUSH', + 'LPUSHX', + 'LRANGE', + 'LREM', + 'LSET', + 'LTRIM', + 'MGET', + 'MIGRATE', + 'MONITOR', + 'MOVE', + 'MSET', + 'MSETNX', + 'MULTI', + 'PERSIST', + 'PEXPIRE', + 'PEXPIREAT', + 'PFADD', + 'PFCOUNT', + 'PFMERGE', + 'PING', + 'PSETEX', + 'PSUBSCRIBE', + 'PUBSUB', + 'PTTL', + 'PUBLISH', + 'PUNSUBSCRIBE', + 'QUIT', + 'RANDOMKEY', + 'READONLY', + 'READWRITE', + 'RENAME', + 'RENAMENX', + 'RESTORE', + 'ROLE', + 'RPOP', + 'RPOPLPUSH', + 'RPUSH', + 'RPUSHX', + 'SADD', + 'SAVE', + 'SCARD', + 'SCRIPT', + 'FLUSH', + 'LOAD', + 'SDIFF', + 'SDIFFSTORE', + 'SELECT', + 'SETBIT', + 'SETEX', + 'SETNX', + 'SETRANGE', + 'SHUTDOWN', + 'SINTER', + 'SINTERSTORE', + 'SISMEMBER', + 'SLAVEOF', + 'SLOWLOG', + 'SMEMBERS', + 'SMOVE', + 'SORT', + 'SPOP', + 'SRANDMEMBER', + 'SREM', + 'STRLEN', + 'SUBSCRIBE', + 'SUNION', + 'SUNIONSTORE', + 'SWAPDB', + 'SYNC', + 'TIME', + 'TOUCH', + 'TTL', + 'TYPE', + 'UNSUBSCRIBE', + 'UNLINK', + 'UNWATCH', + 'WAIT', + 'WATCH', + 'ZADD', + 'ZCARD', + 'ZCOUNT', + 'ZINCRBY', + 'ZINTERSTORE', + 'ZLEXCOUNT', + 'ZRANGE', + 'ZRANGEBYLEX', + 'ZREVRANGEBYLEX', + 'ZRANGEBYSCORE', + 'ZRANK', + 'ZREM', + 'ZREMRANGEBYLEX', + 'ZREMRANGEBYRANK', + 'ZREMRANGEBYSCORE', + 'ZREVRANGE', + 'ZREVRANGEBYSCORE', + 'ZREVRANK', + 'ZSCORE', + 'ZUNIONSTORE', + 'SCAN', + 'SSCAN', + 'HSCAN', + 'ZSCAN', + ], + functions: [], +}; diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts index 3e63fbdd9..ef17b5eb8 100644 --- a/chat2db-client/src/utils/IntelliSense/keyword.ts +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -39,13 +39,12 @@ const registerIntelliSenseKeyword = (databaseCode?: DatabaseTypeCode) => { intelliSenseKeyword = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '('], provideCompletionItems: (model, position) => { - if (databaseCode === DatabaseTypeCode.POSTGRESQL) { - const intelliSensePostgreSQL = Object.values(intelliSense).find((v) => v.type === DatabaseTypeCode.ORACLE); - if (!intelliSensePostgreSQL) return { suggestions: [] }; + const commonIntelliSense = Object.values(intelliSense).find((v) => v.type === databaseCode); + if (commonIntelliSense) { return { suggestions: [ - ...getSQLKeywords(intelliSensePostgreSQL?.keywords), - ...getSQLFunctions(intelliSensePostgreSQL?.functions), + ...getSQLKeywords(commonIntelliSense?.keywords), + ...getSQLFunctions(commonIntelliSense?.functions), ], }; } else { From d7c8b50244fd1f5e115549ff0f9bd8536954f3bc Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 12 Oct 2023 16:46:50 +0800 Subject: [PATCH 0887/1069] feat: table paging --- .../SearchResult/TableBox/index.less | 2 + .../src/components/SearchResult/index.less | 5 + .../src/components/SearchResult/index.tsx | 5 +- .../src/components/StateIndicator/index.less | 2 +- chat2db-client/src/models/workspace.ts | 2 +- .../workspace/components/SaveList/index.less | 7 +- .../workspace/components/TableList/index.less | 13 +- .../workspace/components/TableList/index.tsx | 138 +++++++++++++++--- .../Tree/TreeNodeRightClick/index.tsx | 7 +- .../main/workspace/components/Tree/index.less | 6 +- .../main/workspace/components/Tree/index.tsx | 46 ++++-- .../workspace/components/Tree/treeConfig.tsx | 27 +++- .../components/WorkspaceLeft/index.less | 20 +-- .../components/WorkspaceLeft/index.tsx | 6 +- .../src/pages/main/workspace/index.less | 6 +- chat2db-client/src/service/sql.ts | 18 +-- chat2db-client/src/typings/common.ts | 7 + chat2db-client/src/typings/tree.ts | 22 ++- 18 files changed, 246 insertions(+), 93 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 4373e30be..2af5c69fa 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -106,6 +106,8 @@ } .statusBar { + height: 26px; + box-sizing: border-box; position: sticky; bottom: 0; z-index: 30; diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index e15fd19c8..a7a2a98f7 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -60,3 +60,8 @@ .outputPrefixIcon{ margin-right: 4px; } + +.stateIndicator{ + width: 70%; + margin: 0 auto; +} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 1cafd485c..5c53774ce 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -40,7 +40,7 @@ export default memo((props) => { /> ); } else { - return ; + return ; } }; @@ -78,7 +78,8 @@ export default memo((props) => { prefixIcon: , label: 'Output', key: 'output', - children: , + // children: , + children: '1', styles: { width: '80px' }, canClosed: false, }, diff --git a/chat2db-client/src/components/StateIndicator/index.less b/chat2db-client/src/components/StateIndicator/index.less index d4c42f26b..7e83cd7fd 100644 --- a/chat2db-client/src/components/StateIndicator/index.less +++ b/chat2db-client/src/components/StateIndicator/index.less @@ -35,7 +35,7 @@ .errorText { font-size: 14px; - color: var(--error-color); + color: var(--color-error); text-align: center; transform: translateY(-20px); white-space: pre-wrap; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index cf2a3b5b8..0a49aa154 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -1,7 +1,7 @@ import { getCurrentWorkspaceDatabase, setCurrentWorkspaceDatabase } from '@/utils/localStorage'; import sqlService, { MetaSchemaVO } from '@/service/sql'; import historyService from '@/service/history'; -import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants'; +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'; 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 0dbfaadf2..99a470f33 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -6,7 +6,8 @@ .leftModuleTitle { flex-shrink: 0; - margin-bottom: 10px; + margin-bottom: 4px; + padding: 0px 10px; .leftModuleTitleText { display: flex; justify-content: space-between; @@ -36,7 +37,7 @@ } .saveBoxList { - margin: 0px -10px; + padding: 0px 10px; height: calc(26px * 3 + 6px); overflow-y: auto; } @@ -51,7 +52,7 @@ border-radius: 2px; user-select: none; cursor: pointer; - .saveItemText{ + .saveItemText { width: 0px; flex: 1; .f-single-line(); diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index 49fae6edb..f58816009 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -9,8 +9,10 @@ .leftModuleTitle { flex-shrink: 0; - margin-bottom: 10px; + margin-bottom: 4px; + padding: 0px 10px; .leftModuleTitleText { + // 下阴影 display: flex; justify-content: space-between; align-items: center; @@ -52,7 +54,14 @@ flex: 1; height: 0; overflow-y: auto; - margin: 0px -10px; +} + +.paging { + flex-shrink: 0; + height: 25px; + display: flex; + align-items: center; + justify-content: center; } .refreshIconBox { 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 017bc847d..e27c52ee6 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -2,20 +2,21 @@ import React, { useState, useEffect, useRef, useMemo } from 'react'; import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Input, Cascader, Dropdown, MenuProps } from 'antd'; +import { Input, Cascader, Dropdown, MenuProps, Pagination } from 'antd'; 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 { TreeNodeType, WorkspaceTabType } from '@/constants'; +import { TreeNodeType, WorkspaceTabType, ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; import { approximateTreeNode } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; -import { ITreeNode } from '@/typings'; +import { IPagingData, ITreeNode } from '@/typings'; import styles from './index.less'; import { ExportTypeEnum } from '@/typings/resultTable'; +import historyService from '@/service/history'; interface IOption { value: TreeNodeType; @@ -33,6 +34,12 @@ const optionsList: IOption[] = [ { value: TreeNodeType.TRIGGERS, label: i18n('workspace.tree.trigger') }, ]; +const defaultPaddingData = { + total: 0, + pageSize: 200, + pageNo: 1, +}; + const dvaModel = connect( ({ connection, workspace }: { connection: IConnectionModelType; workspace: IWorkspaceModelType }) => ({ connectionModel: connection, @@ -54,6 +61,7 @@ const TableList = dvaModel((props: any) => { const [curType, setCurType] = useState(optionsList[0]); const [curList, setCurList] = useState([]); const [tableLoading, setTableLoading] = useState(false); + const [pagingData, setPagingData] = useState(defaultPaddingData); // 导出表结构 const handleExport = (exportType: ExportTypeEnum) => { @@ -62,6 +70,18 @@ const TableList = dvaModel((props: any) => { const items: MenuProps['items'] = useMemo( () => [ + { + label: ( +
    + +
    {i18n('common.button.createConsole')}
    +
    + ), + key: 'createConsole', + onClick: () => { + addConsole(); + }, + }, { label: (
    @@ -177,26 +197,65 @@ const TableList = dvaModel((props: any) => { } }, [searching]); - function getList(refresh: boolean = false) { + const addConsole = () => { + const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; + const params = { + name: `new console`, + ddl: '', + dataSourceId: dataSourceId!, + databaseName: databaseName!, + schemaName: schemaName!, + type: databaseType, + status: ConsoleStatus.DRAFT, + tabOpened: ConsoleOpenedStatus.IS_OPEN, + operationType: WorkspaceTabType.CONSOLE, + tabType: WorkspaceTabType.CONSOLE, + }; + + historyService.saveConsole(params).then((res) => { + dispatch({ + type: 'workspace/setCreateConsoleIntro', + payload: { + id: res, + type: WorkspaceTabType.CONSOLE, + title: params.name, + uniqueData: params, + }, + }); + }); + }; + + function getList(params?: { pageNo?: number; refresh?: boolean; searchKey?: string }) { + const { refresh = false, searchKey, pageNo = 1 } = params || {}; setTableLoading(true); - treeConfig[curType.value].getChildren!({ - refresh, - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - }) - .then((res) => { - setCurList(res); - setTableLoading(false); - if (curType.value === TreeNodeType.TABLES) { - dispatch({ - type: 'workspace/setCurTableList', - payload: res, - }); - } + return new Promise((resolve) => { + treeConfig[curType.value].getChildren!({ + refresh, + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + searchKey, + pageNo, }) - .catch(() => { - setTableLoading(false); - }); + .then((res: any) => { + setCurList(res.data); + resolve(res); + setPagingData({ + total: res.total, + pageSize: res.pageSize, + pageNo: res.pageNo, + }); + if (curType.value === TreeNodeType.TABLES) { + dispatch({ + type: 'workspace/setCurTableList', + payload: res.data, + }); + } + setTableLoading(false); + }) + .catch(() => { + setTableLoading(false); + }); + }); } function openSearch() { @@ -211,13 +270,24 @@ const TableList = dvaModel((props: any) => { } function onChange(value: string) { - setSearchedTableList(approximateTreeNode(curList, value)); + if (curType.value === TreeNodeType.TABLES) { + getList({ + searchKey: value, + refresh: false, + }).then((res: any) => { + setSearchedTableList(approximateTreeNode(res.data, value)); + }); + } else { + setSearchedTableList(approximateTreeNode(curList, value)); + } } function refreshTableList() { if (isReady) { setCurList([]); - getList(true); + getList({ + refresh: true, + }); } } @@ -225,6 +295,16 @@ const TableList = dvaModel((props: any) => { setCurType(selectedOptions[0]); } + const handelChangePagination = (pageNo: number) => { + getList({ + pageNo, + refresh: false, + searchKey: inputRef.current?.input.value, + }).then((res: any) => { + setSearchedTableList(approximateTreeNode(res.data, inputRef.current?.input.value)); + }); + }; + return (
    @@ -274,6 +354,18 @@ const TableList = dvaModel((props: any) => { + {pagingData?.total > 200 && ( +
    + +
    + )}
    ); }); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 9504ad94b..a22ec78cc 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -10,7 +10,7 @@ import connectionServer from '@/service/connection'; import mysqlServer from '@/service/sql'; import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import { IConnectionConfig } from '@/components/ConnectionEdit/config/types'; -import { IWorkspaceModelType } from '@/models/workspace'; +import { ICurWorkspaceParams } from '@/models/workspace'; import MonacoEditor from '@/components/Console/MonacoEditor'; export type IProps = { @@ -18,7 +18,7 @@ export type IProps = { setIsLoading: (value: boolean) => void; data: ITreeNode; dispatch: any; - workspaceModel: IWorkspaceModelType['state']; + curWorkspaceParams: ICurWorkspaceParams; }; export interface IOperationColumnConfigItem { @@ -28,14 +28,13 @@ export interface IOperationColumnConfigItem { } function TreeNodeRightClick(props: IProps) { - const { className, data, setIsLoading, dispatch, workspaceModel } = props; + const { className, data, setIsLoading, dispatch, curWorkspaceParams } = props; const [verifyDialog, setVerifyDialog] = useState(); const [verifyTableName, setVerifyTableName] = useState(''); const [modalApi, modelDom] = Modal.useModal(); const [notificationApi, notificationDom] = notification.useNotification(); const treeNodeConfig: ITreeConfigItem = treeConfig[data.treeNodeType]; const { getChildren, operationColumn } = treeNodeConfig; - const { curWorkspaceParams } = workspaceModel; const [monacoVerifyDialog, setMonacoVerifyDialog] = useState(false); const [monacoDefaultValue, setMonacoDefaultValue] = useState(''); const dataSourceFormConfig = dataSourceFormConfigs.find((t: IConnectionConfig) => { diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/index.less index 7f95f73f8..94e0e35e2 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.less @@ -1,7 +1,7 @@ @import '../../../../../styles/var.less'; -.box { - padding: 0px 0px 20px 0px; +.treeBox { + padding: 0px 6px; } .treeNode { @@ -117,7 +117,7 @@ opacity: 0.8; } -.describe{ +.describe { flex: 1; font-size: 10px; margin-left: 20px; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index 590d56984..d98510f89 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -6,14 +6,16 @@ import Iconfont from '@/components/Iconfont'; import { Tooltip } from 'antd'; import { ITreeNode } from '@/typings'; import { callVar, approximateTreeNode } from '@/utils'; -import { TreeNodeType, databaseMap, CreateTabIntroType, WorkspaceTabType } from '@/constants'; +import { TreeNodeType, databaseMap } from '@/constants'; import TreeNodeRightClick from './TreeNodeRightClick'; import { treeConfig, switchIcon, ITreeConfigItem } from './treeConfig'; -import { IWorkspaceModelType } from '@/models/workspace'; +import { IWorkspaceModelType, ICurWorkspaceParams } from '@/models/workspace'; interface IProps { className?: string; initialData?: ITreeNode[]; + workspaceModel: IWorkspaceModelType['state']; + dispatch: any; } interface TreeNodeIProps { @@ -22,7 +24,7 @@ interface TreeNodeIProps { show: boolean; setTreeData: Function; showAllChildrenPenetrate?: boolean; - workspaceModel: IWorkspaceModelType['state']; + curWorkspaceParams: ICurWorkspaceParams; dispatch: any; } @@ -30,8 +32,8 @@ const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => workspaceModel: workspace, })); -function Tree(props: IProps) { - const { className, initialData } = props; +const Tree = dvaModel((props: IProps) => { + const { className, initialData, workspaceModel, dispatch } = props; const [treeData, setTreeData] = useState(); const [searchedTreeData, setSearchedTreeData] = useState(null); @@ -48,16 +50,34 @@ function Tree(props: IProps) { } return ( -
    +
    {(searchedTreeData || treeData)?.map((item, index) => { - return ; + return ( + + ); })}
    ); -} +}); -const TreeNode = dvaModel((props: TreeNodeIProps) => { - const { setTreeData, data, level, show = false, showAllChildrenPenetrate = false, dispatch, workspaceModel } = props; +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'); @@ -194,7 +214,7 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { dispatch={dispatch} className={styles.moreButton} data={data} - workspaceModel={workspaceModel} + curWorkspaceParams={curWorkspaceParams} />
    @@ -203,6 +223,8 @@ const TreeNode = dvaModel((props: TreeNodeIProps) => { {data.children?.map((item: any, i: number) => { return ( { ) : ( <> ); -}); +}; export default Tree; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index 9cc6c8236..89fa5640f 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -1,6 +1,7 @@ -import { ITreeNode } from '@/typings'; +import { ITreeNode, IPagingData } from '@/typings'; import { TreeNodeType, OperationColumn } from '@/constants'; import connectionService from '@/service/connection'; + import mysqlServer, { ISchemaParams, ITableParams } from '@/service/sql'; export type ITreeConfig = Partial<{ [key in TreeNodeType]: ITreeConfigItem }>; @@ -67,7 +68,12 @@ export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string; unfold export interface ITreeConfigItem { icon?: string; - getChildren?: (params: any) => Promise; + getChildren?: (params: any) => Promise< + | ITreeNode[] + | ({ + data: ITreeNode[]; + } & IPagingData) + >; next?: TreeNodeType; operationColumn?: OperationColumn[]; } @@ -78,7 +84,7 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { return new Promise((r: (value: ITreeNode[]) => void, j) => { let p = { pageNo: 1, - pageSize: 999, + pageSize: 1000, }; connectionService .getList(p) @@ -186,9 +192,11 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.TABLES]: { icon: '\ueac5', getChildren: (params) => { - return new Promise((r: (value: ITreeNode[]) => void, j) => { + return new Promise((r, j) => { + params.pageNo = params.pageNo || 1; + params.pageSize = 200; mysqlServer - .getList(params) + .getTableList(params) .then((res) => { const tableList: ITreeNode[] = res.data?.map((t: any) => { return { @@ -203,10 +211,15 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { }, }; }); - r(tableList); + r({ + data: tableList, + pageNo: res.pageNo, + pageSize: res.pageSize, + total: res.total, + }); }) .catch((error) => { - j(); + j(error); }); }); }, 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 429c4dfb2..b44c6bb64 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -5,30 +5,18 @@ flex-direction: column; height: 100%; background-color: var(--color-bg-subtle); - padding: 10px 20px 0px; box-sizing: border-box; min-width: 200px; } -.header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 34px; - flex-shrink: 0; +.createButtonBox { + padding: 10px 0px 10px; } -.cascaderPopup { - :global { - .ant-cascader-menu-item-content { - font-weight: 400 !important; - } - } +.divider{ + margin: 10px 0px; } -.createButtonBox { - padding: 10px 0px 10px; -} .createButton { width: 100%; display: flex; 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 288e84bf5..b5737b008 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.tsx @@ -7,8 +7,8 @@ import Iconfont from '@/components/Iconfont'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType } from '@/constants'; -import styles from './index.less'; import historyService from '@/service/history'; +import styles from './index.less'; import TableList from '../TableList'; import SaveList from '../SaveList'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; @@ -75,12 +75,12 @@ const WorkspaceLeft = memo((props) => { -
    + {/*
    -
    +
    */}
    ); }); diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index b2584ec09..745afe676 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -7,13 +7,13 @@ flex-direction: column; } -.workspaceMain{ +.workspaceMain { height: 100%; } -.loadingContent{ +.loadingContent { flex: 1; - height: 0px; + height: 0px !important; } .boxLeft { diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 3aae6fc1d..144d9bf22 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -1,9 +1,9 @@ import createRequest from './base'; -import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseSupportField, IEditTableInfo } from '@/typings'; +import { IPageResponse, IPageParams, IUniversalTableParams, IManageResultData, IRoutines, IDatabaseSupportField, IEditTableInfo, ITable } from '@/typings'; import { DatabaseTypeCode } from '@/constants'; import { ExportSizeEnum, ExportTypeEnum } from '@/typings/resultTable'; -export interface IGetListParams extends IPageParams { +export interface IGetTableListParams extends IPageParams { dataSourceId: number; databaseName: string; schemaName?: string; @@ -35,7 +35,7 @@ export interface IConnectConsoleParams { databaseName: string; } -const getList = createRequest>('/api/rdb/ddl/list', { method: 'get' }); +const getTableList = createRequest>('/api/rdb/ddl/list', { method: 'get' }); const executeSql = createRequest('/api/rdb/dml/execute', { method: 'post' }); @@ -130,19 +130,19 @@ export interface IExportParams extends IExecuteSqlParams { // const exportResultTable = createRequest('/api/rdb/dml/export', { method: 'post' }); /** 获取视图列表 */ -const getViewList = createRequest>('/api/rdb/view/list', { method: 'get' }); +const getViewList = createRequest>('/api/rdb/view/list', { method: 'get' }); /** 获取函数列表 */ -const getFunctionList = createRequest>('/api/rdb/function/list', { method: 'get' }); +const getFunctionList = createRequest>('/api/rdb/function/list', { method: 'get' }); /** 获取触发器列表 */ -const getTriggerList = createRequest>('/api/rdb/trigger/list', { method: 'get' }); +const getTriggerList = createRequest>('/api/rdb/trigger/list', { method: 'get' }); /** 获取过程列表 */ -const getProcedureList = createRequest>('/api/rdb/procedure/list', { method: 'get' }); +const getProcedureList = createRequest>('/api/rdb/procedure/list', { method: 'get' }); /** 获取视图列列表 */ -const getViewColumnList = createRequest>('/api/rdb/view/column_list', { method: 'get' }); +const getViewColumnList = createRequest>('/api/rdb/view/column_list', { method: 'get' }); /** 获取视图详情 */ const getViewDetail = createRequest<{ @@ -236,7 +236,7 @@ export default { getTriggerList, getFunctionList, getViewList, - getList, + getTableList, executeSql, connectConsole, deleteTable, diff --git a/chat2db-client/src/typings/common.ts b/chat2db-client/src/typings/common.ts index 589f02ffd..830eadda4 100644 --- a/chat2db-client/src/typings/common.ts +++ b/chat2db-client/src/typings/common.ts @@ -14,6 +14,13 @@ export interface IPageParams { pageSize: number; } +export interface IPagingData { + hasNextPage?: boolean; + pageNo: number; + pageSize: number; + total: number; +} + // 控制台详情 export interface IConsole { id: number; // consoleId diff --git a/chat2db-client/src/typings/tree.ts b/chat2db-client/src/typings/tree.ts index c02b73b19..8dadc3c69 100644 --- a/chat2db-client/src/typings/tree.ts +++ b/chat2db-client/src/typings/tree.ts @@ -1,5 +1,4 @@ import { TreeNodeType, DatabaseTypeCode } from '@/constants'; -import { IPageParams } from '@/typings'; export interface IExtraParams { databaseType?: DatabaseTypeCode; @@ -11,8 +10,7 @@ export interface IExtraParams { functionName?: string; procedureName?: string; triggerName?: string; - -}; +} export interface ITreeNode { key: string | number; @@ -31,4 +29,20 @@ export interface IRoutines { name: string; // 名称 comment: string; // 描述 pinned: boolean; // 是否置顶 -} \ No newline at end of file +} + +export interface ITable { + /** + * 表描述 + */ + comment?: string | null; + /** + * 表名称 + */ + name: string | null; + /** + * 是否已经被固定 + */ + pinned?: boolean; + +} From 55b2ed02d906ed34bdec16b1c434ecb90d9a8e71 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 12 Oct 2023 17:39:15 +0800 Subject: [PATCH 0888/1069] feat: Optimize code intellisense --- .../components/Console/MonacoEditor/index.tsx | 80 ------------------- .../src/constants/IntelliSense/mysql.ts | 2 - .../src/constants/IntelliSense/pgsql.ts | 1 - .../components/WorkspaceRight/index.tsx | 3 +- .../src/utils/IntelliSense/field.ts | 26 +++++- .../src/utils/IntelliSense/table.ts | 25 +++++- 6 files changed, 48 insertions(+), 89 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index a9ab98ff8..714140d91 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -36,86 +36,6 @@ export interface IExportRefFunction { setValue: (text: any, range?: IRangeType) => void; } -export interface IHintData { - [keys: string]: string[]; -} -// monaco.languages.registerCompletionItemProvider(`sql`, { -// triggerCharacters: [' ', '('], -// provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => { -// const { lineNumber, column } = position; -// const textBeforePointer = model.getValueInRange({ -// startLineNumber: lineNumber, -// startColumn: 0, -// endLineNumber: lineNumber, -// endColumn: column, -// }); -// const tokens = textBeforePointer.trim().split(/\s+/); -// const lastToken = tokens[tokens.length - 1]; // 获取最后一段非空字符串 -// const lastTokenBefore = tokens[tokens.length - 2]; // 获取最后一段非空字符串 - -// // 后面显示表名 -// if ( -// ['FROM', 'JOIN', 'UPDATE', 'DELETE'].includes(lastToken.toUpperCase()) || -// ['INSERT INTO', 'ALTER TABLE', 'DROP TABLE'].includes((lastTokenBefore + ' ' + lastToken).toUpperCase()) -// ) { -// return { -// // suggestions: [ -// // { -// // label: 'table1(datasource1)', -// // kind: monaco.languages.CompletionItemKind.Method, -// // insertText: 'table1', -// // detail: '
    ', -// // }, -// // ], -// suggestions: getTableSuggest(tableList), -// }; -// } - -// // // 后面显示字段名 -// // if (['SELECT'].includes(lastToken.toUpperCase())) { -// // return { -// // suggestions: [ -// // { -// // label: 'field1(table1)', -// // kind: monaco.languages.CompletionItemKind.Field, -// // insertText: 'field1', -// // detail: '', -// // }, -// // { -// // label: '*', -// // kind: monaco.languages.CompletionItemKind.Field, -// // insertText: '*', -// // detail: '', -// // }, -// // ], -// // }; -// // } - -// return { -// suggestions: [ -// ...getSQLKeywords(), -// ...getSQLFunctions(), -// ...getTableSuggest([ -// { -// tableName: 'table1', -// datasourceName: 'datasource1', -// }, -// ]), -// ...getFieldSuggest([ -// { -// tableName: 'table1', -// fields: [ -// { -// name: 'field1', -// }, -// ], -// }, -// ]), -// ], -// }; -// }, -// }); - function MonacoEditor(props: IProps, ref: ForwardedRef) { const { id, diff --git a/chat2db-client/src/constants/IntelliSense/mysql.ts b/chat2db-client/src/constants/IntelliSense/mysql.ts index c75ae18cc..2a8c54aa9 100644 --- a/chat2db-client/src/constants/IntelliSense/mysql.ts +++ b/chat2db-client/src/constants/IntelliSense/mysql.ts @@ -3,7 +3,6 @@ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.MYSQL, keywords: [ - '*', 'ACCESSIBLE', 'ADD', 'ALL', @@ -584,7 +583,6 @@ export default { 'ROW_NUMBER', 'RPAD', 'RTRIM', - 'SCHEMA', 'SEC_TO_TIME', 'SECOND', 'SESSION_USER', diff --git a/chat2db-client/src/constants/IntelliSense/pgsql.ts b/chat2db-client/src/constants/IntelliSense/pgsql.ts index 9806a9026..13b80648d 100644 --- a/chat2db-client/src/constants/IntelliSense/pgsql.ts +++ b/chat2db-client/src/constants/IntelliSense/pgsql.ts @@ -3,7 +3,6 @@ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.POSTGRESQL, keywords: [ - '*', 'ALL', 'ANALYSE', 'ANALYZE', 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 e7888830b..518e688f4 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -316,9 +316,8 @@ const WorkspaceRight = memo((props: IProps) => { schemaName, }) .then((data) => { - console.log('databaseName', data); tableList.current = (data || []).map((item: any) => item.name); - registerIntelliSenseTable(data, databaseName, databaseType); + registerIntelliSenseTable(data, databaseType, dataSourceId, databaseName, schemaName); registerIntelliSenseField(tableList.current, dataSourceId, databaseName, schemaName); }); } diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index ab71ed586..01efda649 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -12,12 +12,33 @@ let intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { }, }); +const addIntelliSenseField = async (props: { + tableName: string; + dataSourceId: number; + databaseName: string; + schemaName?: string; +}) => { + const { tableName, dataSourceId, databaseName, schemaName } = props; + + if (!fieldList[tableName]) { + const data = await sqlService.getAllFieldByTable({ + dataSourceId, + databaseName, + schemaName, + tableName, + }); + fieldList[tableName] = data; + } +}; + const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseName, schemaName) => { intelliSenseField.dispose(); fieldList = {}; intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { - triggerCharacters: [' ', '.', '`', "'", '"'], + triggerCharacters: [' ', '.', '('], provideCompletionItems: async (model, position) => { + console.log('registerIntelliSenseField'); + // 获取到当前行文本 const textUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, @@ -33,7 +54,6 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa console.log(word); // 输出: text } - console.log('registerIntelliSenseField start', textUntilPosition, word); if (!word) { return; // 如果没有匹配到,直接返回 } @@ -70,4 +90,4 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa }); }; -export { intelliSenseField, registerIntelliSenseField }; +export { intelliSenseField, registerIntelliSenseField, addIntelliSenseField }; diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 42f117a94..32721eb20 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -1,5 +1,6 @@ import { DatabaseTypeCode } from '@/constants'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; +import { addIntelliSenseField } from './field'; /** 当前库下的表 */ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { @@ -12,9 +13,19 @@ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { const registerIntelliSenseTable = ( tableList: Array<{ name: string; comment: string }>, - databaseName?: string, databaseCode?: DatabaseTypeCode, + dataSourceId?: number, + databaseName?: string, + schemaName?: string, ) => { + monaco.editor.registerCommand('myCustomCommand', (_: any, ...args: any[]) => { + // access the arguments here + console.log('trigger suggest', args[0]); + // addIntelliSenseField(tableName) + addIntelliSenseField(args[0]); + return; + }); + intelliSenseTable.dispose(); intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' '], @@ -38,6 +49,18 @@ const registerIntelliSenseTable = ( insertText: handleInsertText(tableName.name), // range: monaco.Range.fromPositions(position), documentation: tableName.comment, + command: { + id: 'myCustomCommand', + title: 'operator_additional_suggestions', + arguments: [ + { + tableName: tableName.name, + dataSourceId, + databaseName, + schemaName, + }, + ], + }, }; }), }; From 9f014540388535a2a690bfdc0419d31e4c545091 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 12 Oct 2023 19:54:01 +0800 Subject: [PATCH 0889/1069] Table paging --- .../workspace/components/TableList/index.less | 14 +- .../workspace/components/TableList/index.tsx | 153 +++++++++++------- .../workspace/components/Tree/treeConfig.tsx | 2 - .../components/WorkspaceLeft/index.tsx | 42 +---- 4 files changed, 111 insertions(+), 100 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index f58816009..13802207c 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -58,10 +58,20 @@ .paging { flex-shrink: 0; - height: 25px; + height: 30px; display: flex; align-items: center; - justify-content: center; + justify-content: space-between; + flex-wrap: wrap; + overflow: hidden; + padding: 0px 4px; + border-top: 1px solid var(--color-border); + .paginationBox, + .paginationSelectBox { + display: flex; + align-items: center; + height: 30px; + } } .refreshIconBox { 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 e27c52ee6..c5f1a8df5 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -1,8 +1,8 @@ -import React, { useState, useEffect, useRef, useMemo } from 'react'; +import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react'; import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; -import { Input, Cascader, Dropdown, MenuProps, Pagination } from 'antd'; +import { Input, Cascader, Dropdown, MenuProps, Pagination, Select } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import { IConnectionModelType } from '@/models/connection'; @@ -17,6 +17,7 @@ import { IPagingData, ITreeNode } from '@/typings'; import styles from './index.less'; import { ExportTypeEnum } from '@/typings/resultTable'; import historyService from '@/service/history'; +import { debounce } from 'lodash'; interface IOption { value: TreeNodeType; @@ -36,7 +37,7 @@ const optionsList: IOption[] = [ const defaultPaddingData = { total: 0, - pageSize: 200, + pageSize: 100, pageNo: 1, }; @@ -62,6 +63,7 @@ const TableList = dvaModel((props: any) => { const [curList, setCurList] = useState([]); const [tableLoading, setTableLoading] = useState(false); const [pagingData, setPagingData] = useState(defaultPaddingData); + const [searchKey, setSearchKey] = useState(''); // 导出表结构 const handleExport = (exportType: ExportTypeEnum) => { @@ -182,6 +184,16 @@ const TableList = dvaModel((props: any) => { getList(); }, [curType]); + useUpdateEffect(() => { + getList(); + }, [pagingData.pageSize, pagingData.pageNo]); + + useUpdateEffect(() => { + if (curType.value !== TreeNodeType.TABLES) { + setSearchedTableList(approximateTreeNode(curList, searchKey)); + } + }, [searchKey]); + useEffect(() => { setCurList([]); if (isReady) { @@ -225,37 +237,41 @@ const TableList = dvaModel((props: any) => { }); }; - function getList(params?: { pageNo?: number; refresh?: boolean; searchKey?: string }) { - const { refresh = false, searchKey, pageNo = 1 } = params || {}; + function getList(params?: { refresh?: boolean }) { + const { refresh = false } = params || {}; setTableLoading(true); - return new Promise((resolve) => { - treeConfig[curType.value].getChildren!({ - refresh, - ...curWorkspaceParams, - extraParams: curWorkspaceParams, - searchKey, - pageNo, - }) - .then((res: any) => { + const p = { + refresh, + ...curWorkspaceParams, + extraParams: curWorkspaceParams, + searchKey: inputRef.current?.input.value || '', + }; + if (curType.value === TreeNodeType.TABLES) { + p.pageNo = pagingData.pageNo; + p.pageSize = pagingData.pageSize; + } + treeConfig[curType.value].getChildren!(p) + .then((res: any) => { + // 表的处理 + console.log(curType); + if (curType.value === TreeNodeType.TABLES) { setCurList(res.data); - resolve(res); setPagingData({ + ...pagingData, total: res.total, - pageSize: res.pageSize, - pageNo: res.pageNo, }); - if (curType.value === TreeNodeType.TABLES) { - dispatch({ - type: 'workspace/setCurTableList', - payload: res.data, - }); - } - setTableLoading(false); - }) - .catch(() => { - setTableLoading(false); - }); - }); + dispatch({ + type: 'workspace/setCurTableList', + payload: res.data, + }); + } else { + setCurList(res); + } + setTableLoading(false); + }) + .catch(() => { + setTableLoading(false); + }); } function openSearch() { @@ -269,19 +285,6 @@ const TableList = dvaModel((props: any) => { } } - function onChange(value: string) { - if (curType.value === TreeNodeType.TABLES) { - getList({ - searchKey: value, - refresh: false, - }).then((res: any) => { - setSearchedTableList(approximateTreeNode(res.data, value)); - }); - } else { - setSearchedTableList(approximateTreeNode(curList, value)); - } - } - function refreshTableList() { if (isReady) { setCurList([]); @@ -296,15 +299,34 @@ const TableList = dvaModel((props: any) => { } const handelChangePagination = (pageNo: number) => { - getList({ + setPagingData({ + ...pagingData, pageNo, - refresh: false, - searchKey: inputRef.current?.input.value, - }).then((res: any) => { - setSearchedTableList(approximateTreeNode(res.data, inputRef.current?.input.value)); }); }; + const handleChangePageSize = (value: number) => { + setPagingData({ + ...pagingData, + pageSize: value, + }); + }; + + const handleValue = useCallback( + debounce(() => { + if (curType.value === TreeNodeType.TABLES) { + if (pagingData.pageNo === 1) { + getList(); + } + setPagingData({ + ...pagingData, + pageNo: 1, + }); + } + }, 500), + [curType, pagingData], + ); + return (
    @@ -312,11 +334,15 @@ const TableList = dvaModel((props: any) => {
    } onBlur={onBlur} - onChange={(e) => onChange(e.target.value)} + onChange={(e) => { + setSearchKey(e.target.value); + handleValue(); + }} allowClear />
    @@ -356,14 +382,29 @@ const TableList = dvaModel((props: any) => { {pagingData?.total > 200 && (
    - +
    + +
    +
    +
    ((props) => { /> ); } else { - return ; + return ( + + ); } }; @@ -73,13 +80,17 @@ export default memo((props) => { ); const outputTabAndTabsList = useMemo(() => { + const params = { + pageNo: 1, + pageSize: 10, + }; + return [ { prefixIcon: , label: 'Output', key: 'output', - // children: , - children: '1', + children: , styles: { width: '80px' }, canClosed: false, }, @@ -89,15 +100,15 @@ export default memo((props) => { return (
    - {outputTabAndTabsList.length ? ( + {tabsList.length ? ( ) : (
    diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index fd1af010e..3e7d358ae 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -30,10 +30,23 @@ interface IProps { type?: 'line'; editableNameOnBlur?: (option: ITabItem) => void; concealTabHeader?: boolean; + // 最后一个tab不能关闭 + lastTabCannotClosed?: boolean; } export default memo((props) => { - const { className, items, onChange, onEdit, activeKey, hideAdd, type, editableNameOnBlur, concealTabHeader } = props; + const { + className, + items, + onChange, + onEdit, + activeKey, + hideAdd, + type, + lastTabCannotClosed, + editableNameOnBlur, + concealTabHeader, + } = props; const [internalTabs, setInternalTabs] = useState([]); const [internalActiveTab, setInternalActiveTab] = useState(); const [editingTab, setEditingTab] = useState(); @@ -97,6 +110,16 @@ export default memo((props) => { setEditingTab(undefined); } + function showClosed() { + if (lastTabCannotClosed && internalTabs.length === 1) { + return false; + } + if (t.canClosed === true) { + return false; + } + return true; + } + return (
    ((props) => {
    {t.label}
    )} - {t.canClosed !== false && ( + {showClosed() && (
    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 518e688f4..d523bbfc5 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -559,6 +559,7 @@ const WorkspaceRight = memo((props: IProps) => { activeKey={activeConsoleId} editableNameOnBlur={editableNameOnBlur} items={tabsList} + lastTabCannotClosed />
    diff --git a/chat2db-client/src/service/history.ts b/chat2db-client/src/service/history.ts index f080d1906..49b5853b4 100644 --- a/chat2db-client/src/service/history.ts +++ b/chat2db-client/src/service/history.ts @@ -9,6 +9,10 @@ export interface IGetSavedListParams extends IPageParams { tabOpened?: ConsoleOpenedStatus; status?: ConsoleStatus } +export interface IGetHistoryListParams extends IPageParams { + dataSourceId?: string; + databaseName?: string; +} export interface ISaveBasicInfo { name: string; type: DatabaseTypeCode; @@ -34,7 +38,7 @@ const deleteSavedConsole = createRequest<{ id: number }, string>('/api/operation const createHistory = createRequest('/api/operation/log/create', { method: 'post' }); -const getHistoryList = createRequest>('/api/operation/log/list', {}); +const getHistoryList = createRequest>('/api/operation/log/list', {}); export default { getSavedConsoleList, @@ -44,4 +48,4 @@ export default { deleteSavedConsole, createHistory, getWindowTab -} \ No newline at end of file +} From 90edff695eb9962c060ae4e54d62a83095ed4ffe Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 12 Oct 2023 21:57:16 +0800 Subject: [PATCH 0893/1069] style --- .../src/pages/main/workspace/components/TableList/index.less | 4 ++-- .../src/pages/main/workspace/components/TableList/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index 13802207c..dbd7c3dd6 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -58,7 +58,7 @@ .paging { flex-shrink: 0; - height: 30px; + height: 25px; display: flex; align-items: center; justify-content: space-between; @@ -70,7 +70,7 @@ .paginationSelectBox { display: flex; align-items: center; - height: 30px; + height: 26px; } } 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 c5f1a8df5..fb8ede70b 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -380,7 +380,7 @@ const TableList = dvaModel((props: any) => { - {pagingData?.total > 200 && ( + {pagingData?.total > 100 && (
    Date: Thu, 12 Oct 2023 22:22:57 +0800 Subject: [PATCH 0894/1069] add --- .../server/domain/core/cache/CacheKey.java | 14 +-- .../domain/core/impl/TableServiceImpl.java | 114 +++++++++++++----- 2 files changed, 89 insertions(+), 39 deletions(-) diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java index f4f5126a9..1340c5c83 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/cache/CacheKey.java @@ -22,25 +22,25 @@ public static String getSchemasKey(Long dataSourceId, String databaseName) { } public static String getTableKey(Long dataSourceId, String databaseName, String schemaName) { - StringBuffer stringBuffer = new StringBuffer("tables_dataSourceId" + dataSourceId); + StringBuffer stringBuffer = new StringBuffer("tables_dataSourceId_" + dataSourceId); if (!StringUtils.isEmpty(databaseName)) { - stringBuffer.append("_databaseName" + databaseName); + stringBuffer.append("_databaseName_" + databaseName); } if (!StringUtils.isEmpty(schemaName)) { - stringBuffer.append("_schemaName" + schemaName); + stringBuffer.append("_schemaName_" + schemaName); } return stringBuffer.toString(); } public static String getColumnKey(Long dataSourceId, String databaseName, String schemaName,String tableName) { - StringBuffer stringBuffer = new StringBuffer("columns_dataSourceId" + dataSourceId); + StringBuffer stringBuffer = new StringBuffer("columns_dataSourceId_" + dataSourceId); if (!StringUtils.isEmpty(databaseName)) { - stringBuffer.append("_databaseName" + databaseName); + stringBuffer.append("_databaseName_" + databaseName); } if (!StringUtils.isEmpty(schemaName)) { - stringBuffer.append("_schemaName" + schemaName); + stringBuffer.append("_schemaName_" + schemaName); } - stringBuffer.append("_tableName"+tableName); + stringBuffer.append("_tableName_"+tableName); return stringBuffer.toString(); } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java index dc5d1f5e0..402dde199 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/TableServiceImpl.java @@ -136,13 +136,45 @@ private void initOldTable(Table oldTable, Table newTable) { @Override public PageResult
    pageQuery(TablePageQueryParam param, TableSelector selector) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(TableCacheVersionDO::getKey, getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); + queryWrapper.eq(TableCacheVersionDO::getKey, key); TableCacheVersionDO versionDO = tableCacheVersionMapper.selectOne(queryWrapper); + long total = 0; + long version = 0L; if (param.isRefresh() || versionDO == null) { - versionDO = addDBCache(param.getDataSourceId(),param.getDatabaseName(),param.getSchemaName(), versionDO); + version = getLock(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), versionDO); + if (version == -1) { + int n = 0; + while (n < 100) { + try { + Thread.sleep(200); + } catch (InterruptedException e) { + } + versionDO = tableCacheVersionMapper.selectOne(queryWrapper); + if (versionDO != null && "1".equals(versionDO.getStatus())) { + version = versionDO.getVersion(); + total = versionDO.getTableCount(); + break; + } + n++; + } + } else { + total = addDBCache(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName(), version); + TableCacheVersionDO versionDO1 = new TableCacheVersionDO(); + versionDO1.setStatus("1"); + versionDO1.setTableCount(total); + tableCacheVersionMapper.update(versionDO1, queryWrapper); + } + } else { + if ("2".equals(versionDO.getStatus())) { + version = versionDO.getVersion() - 1; + } else { + version = versionDO.getVersion(); + } + total = versionDO.getTableCount(); } LambdaQueryWrapper query = new LambdaQueryWrapper<>(); - query.eq(TableCacheDO::getVersion, versionDO.getVersion()); + query.eq(TableCacheDO::getVersion, version); query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); if (StringUtils.isNotBlank(param.getDatabaseName())) { query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); @@ -167,19 +199,22 @@ public PageResult
    pageQuery(TablePageQueryParam param, TableSelector sele tables.add(t); } } - return PageResult.of(tables, versionDO.getTableCount(), param); + return PageResult.of(tables, total, param); } @Override public ListResult queryTables(TablePageQueryParam param) { LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(TableCacheVersionDO::getKey, getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName())); + String key = getTableKey(param.getDataSourceId(), param.getDatabaseName(), param.getSchemaName()); + queryWrapper.eq(TableCacheVersionDO::getKey, key); TableCacheVersionDO versionDO = tableCacheVersionMapper.selectOne(queryWrapper); - if (param.isRefresh() || versionDO == null) { - versionDO = addDBCache(param.getDataSourceId(),param.getDatabaseName(), param.getSchemaName(), versionDO); + if(versionDO == null){ + return ListResult.of(Lists.newArrayList()); } + long version = "2".equals(versionDO.getStatus()) ? versionDO.getVersion() - 1 : versionDO.getVersion(); + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); - query.eq(TableCacheDO::getVersion, versionDO.getVersion()); + query.eq(TableCacheDO::getVersion, version); query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); if (StringUtils.isNotBlank(param.getDatabaseName())) { query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); @@ -189,8 +224,8 @@ public ListResult queryTables(TablePageQueryParam param) { } List tables = new ArrayList<>(); - for (int i = 0; i < versionDO.getTableCount()/500 +1; i++) { - Page page = new Page<>(i+1, 500); + for (int i = 0; i < versionDO.getTableCount() / 500 + 1; i++) { + Page page = new Page<>(i + 1, 500); IPage iPage = tableCacheMapper.selectPage(page, query); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { for (TableCacheDO tableCacheDO : iPage.getRecords()) { @@ -204,23 +239,9 @@ public ListResult queryTables(TablePageQueryParam param) { return ListResult.of(tables); } - private TableCacheVersionDO addDBCache(Long dataSourceId,String databaseName,String schemaName, TableCacheVersionDO versionDO) { + private long addDBCache(Long dataSourceId, String databaseName, String schemaName, long version) { String key = getTableKey(dataSourceId, databaseName, schemaName); - if (versionDO == null) { - versionDO = new TableCacheVersionDO(); - versionDO.setDatabaseName(databaseName); - versionDO.setSchemaName(schemaName); - versionDO.setDataSourceId(dataSourceId); - versionDO.setStatus("2"); - versionDO.setKey(key); - versionDO.setVersion(0L); - versionDO.setTableCount(0L); - tableCacheVersionMapper.insert(versionDO); - } else { - versionDO.setVersion(versionDO.getVersion() + 1); - versionDO.setStatus("2"); - tableCacheVersionMapper.updateById(versionDO); - } + Connection connection = Chat2DBContext.getConnection(); long n = 0; try (ResultSet resultSet = connection.getMetaData().getTables(databaseName, schemaName, null, @@ -233,7 +254,7 @@ private TableCacheVersionDO addDBCache(Long dataSourceId,String databaseName,Str tableCacheDO.setTableName(resultSet.getString("TABLE_NAME")); tableCacheDO.setExtendInfo(resultSet.getString("REMARKS")); tableCacheDO.setDataSourceId(dataSourceId); - tableCacheDO.setVersion(versionDO.getVersion()); + tableCacheDO.setVersion(version); tableCacheDO.setKey(key); cacheDOS.add(tableCacheDO); if (cacheDOS.size() >= 500) { @@ -245,12 +266,9 @@ private TableCacheVersionDO addDBCache(Long dataSourceId,String databaseName,Str if (!CollectionUtils.isEmpty(cacheDOS)) { tableCacheMapper.batchInsert(cacheDOS); } - versionDO.setStatus("1"); - versionDO.setTableCount(n); - tableCacheVersionMapper.updateById(versionDO); LambdaQueryWrapper q = new LambdaQueryWrapper(); q.eq(TableCacheDO::getDataSourceId, dataSourceId); - q.lt(TableCacheDO::getVersion, versionDO.getVersion()); + q.lt(TableCacheDO::getVersion, version); if (StringUtils.isNotBlank(databaseName)) { q.eq(TableCacheDO::getDatabaseName, databaseName); } @@ -261,8 +279,40 @@ private TableCacheVersionDO addDBCache(Long dataSourceId,String databaseName,Str } catch (SQLException e) { throw new RuntimeException(e); } - return versionDO; + return n; + } + private Long getLock(Long dataSourceId, String databaseName, String schemaName, TableCacheVersionDO versionDO) { + String key = getTableKey(dataSourceId, databaseName, schemaName); + if (versionDO == null) { + versionDO = new TableCacheVersionDO(); + versionDO.setDatabaseName(databaseName); + versionDO.setSchemaName(schemaName); + versionDO.setDataSourceId(dataSourceId); + versionDO.setStatus("2"); + versionDO.setKey(key); + versionDO.setVersion(0L); + versionDO.setTableCount(0L); + try { + tableCacheVersionMapper.insert(versionDO); + return 0L; + } catch (Exception e) { + return -1L; + } + } else { + long version = versionDO.getVersion() + 1; + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper(); + queryWrapper.eq(TableCacheVersionDO::getId, versionDO.getId()); + queryWrapper.eq(TableCacheVersionDO::getVersion, versionDO.getVersion()); + versionDO.setVersion(version); + versionDO.setStatus("2"); + int n = tableCacheVersionMapper.update(versionDO, queryWrapper); + if (n == 1) { + return version; + } else { + return -1L; + } + } } From c6fbc306c65ffcd2a73289ff193fd72c4b589e15 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 12 Oct 2023 23:29:57 +0800 Subject: [PATCH 0895/1069] style --- .../components/SearchResult/TableBox/index.less | 16 ++++++++++++++++ .../components/SearchResult/TableBox/index.tsx | 7 +++++++ .../workspace/components/TableList/index.tsx | 6 +++--- .../components/Tree/TreeNodeRightClick/index.tsx | 3 ++- .../main/workspace/components/Tree/index.less | 4 ++++ .../main/workspace/components/Tree/index.tsx | 7 +++++-- chat2db-client/src/service/sql.ts | 8 ++++---- chat2db-client/src/utils/index.ts | 2 +- 8 files changed, 42 insertions(+), 11 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index d4c24762b..dfd05d37b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -13,6 +13,22 @@ .hGEOgo { --header-bgcolor: #fafafa; --header-color: #000; + table colgroup col { + min-width: 120px; + } + table colgroup col:nth-of-type(1) { + min-width: 60px; + } + .fnWppk { + // word-break: break-all; + // display: -webkit-box; + // -webkit-box-orient: vertical; + // box-orient: vertical; + // -webkit-line-clamp: 1; + // line-clamp: 1; + // text-overflow: ellipsis; + overflow: hidden; + } th { font-weight: bold; } diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index c2347819a..1b994e4ed 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -384,6 +384,13 @@ export default function TableBox(props: ITableProps) { // onChangeSorts, }), ); + // .use( + // features.columnResize({ + // fallbackSize: 120, + // minSize: 60, + // maxSize: 1080, + // }), + // ); const onPageNoChange = (pageNo: number) => { const config = { ...paginationConfig, pageNo }; 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 fb8ede70b..d72debae0 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -255,7 +255,7 @@ const TableList = dvaModel((props: any) => { // 表的处理 console.log(curType); if (curType.value === TreeNodeType.TABLES) { - setCurList(res.data); + setCurList(approximateTreeNode(res.data, inputRef.current?.input.value)); setPagingData({ ...pagingData, total: res.total, @@ -265,7 +265,7 @@ const TableList = dvaModel((props: any) => { payload: res.data, }); } else { - setCurList(res); + setCurList(approximateTreeNode(res, inputRef.current?.input.value)); } setTableLoading(false); }) @@ -380,7 +380,7 @@ const TableList = dvaModel((props: any) => { - {pagingData?.total > 100 && ( + {pagingData?.total > 100 && !searchKey && (
    { data.children = res; setIsLoading(false); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/index.less index 94e0e35e2..920397709 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.less @@ -67,6 +67,10 @@ display: none; } +.loadingArrows { + display: flex; +} + .arrowsIcon { display: inline-block; transform: rotate(-90deg); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx index d98510f89..f2097fbc8 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.tsx @@ -181,12 +181,15 @@ const TreeNode = (props: TreeNodeIProps) => {
    {indentArr.map((item, i) => { - return
    ; + return
    ; })}
    {!data.isLeaf && ( -
    +
    {isLoading ? (
    diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 3b76e48b6..acdca6755 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -111,10 +111,10 @@ const updateTableExample = createRequest<{ dbType: DatabaseTypeCode }, string>(' const exportCreateTableSql = createRequest('/api/rdb/ddl/export', { method: 'get' }); const executeTable = createRequest('/api/rdb/ddl/execute', { method: 'post' }); -const getColumnList = createRequest('/api/rdb/ddl/column_list', { method: 'get' }); -const getIndexList = createRequest('/api/rdb/ddl/index_list', { method: 'get' }); -const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get' }); -const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { method: 'get' }); +const getColumnList = createRequest('/api/rdb/ddl/column_list', { method: 'get', delayTime: 200 }); +const getIndexList = createRequest('/api/rdb/ddl/index_list', { method: 'get', delayTime: 200 }); +const getKeyList = createRequest('/api/rdb/ddl/key_list', { method: 'get', delayTime: 200 }); +const getSchemaList = createRequest('/api/rdb/ddl/schema_list', { method: 'get', delayTime: 200 }); const getDatabaseSchemaList = createRequest<{ dataSourceId: number }, MetaSchemaVO>( '/api/rdb/ddl/database_schema_list', diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index d57df5d57..d3f736495 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -75,7 +75,7 @@ export function deepClone(target: any) { } // 模糊匹配树并且高亮 -export function approximateTreeNode(treeData: ITreeNode[], target: string, isDelete = true) { +export function approximateTreeNode(treeData: ITreeNode[], target: string = '', isDelete = true) { if (target) { const newTree: ITreeNode[] = lodash.cloneDeep(treeData || []); newTree.map((item, index) => { From da0d69549cbef27bad083082b05a9e7287328317 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 12 Oct 2023 23:35:23 +0800 Subject: [PATCH 0896/1069] monaco editor revealLine --- chat2db-client/src/components/Console/MonacoEditor/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 714140d91..3e4a58c69 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -238,10 +238,12 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e // 覆盖所有内容 case 'cover': newRange = model.getFullModelRange(); + editor.revealLine(lastLine); break; // 在开头添加内容 case 'front': newRange = new monaco.Range(1, 1, 1, 1); + editor.revealLine(1); break; // 格式化选中区域的sql case 'select': @@ -261,6 +263,7 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); newRange = new monaco.Range(lastLine, lastLineLength, lastLine, lastLineLength); text = `${text}`; + editor.revealLine(lastLine); break; default: break; From 832e5e9f47d1a1ed2e74d3f987d8bc0bb8dd55b2 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 10:26:24 +0800 Subject: [PATCH 0897/1069] feat: Optimize editor IntelliSense order --- .../src/utils/IntelliSense/field.ts | 17 +- .../src/utils/IntelliSense/table.ts | 50 ++- chat2db-client/yarn.lock | 360 +++++++++++++++++- 3 files changed, 407 insertions(+), 20 deletions(-) diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index 01efda649..9a77f17c1 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -31,6 +31,19 @@ const addIntelliSenseField = async (props: { } }; +function checkFieldContext(text) { + const normalizedText = text.trim().toUpperCase(); + const columnKeywords = ['SELECT', 'WHERE', 'AND', 'OR', 'GROUP BY', 'ORDER BY', 'SET']; + + for (const keyword of columnKeywords) { + if (normalizedText.endsWith(keyword)) { + return true; + } + } + + return false; +} + const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseName, schemaName) => { intelliSenseField.dispose(); fieldList = {}; @@ -47,6 +60,8 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa endColumn: position.column, }); + const isFieldContext = checkFieldContext(textUntilPosition); + const match = textUntilPosition.match(/(\b\w+\b)[^\w]*$/); let word; if (match) { @@ -77,11 +92,11 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa }, kind: monaco.languages.CompletionItemKind.Field, insertText: fieldObj.name, + sortText: (isFieldContext ? '01' : '02') + fieldObj.name, })); return [...acc, ...arr]; }, []); - console.log('field suggestions', suggestions); return { suggestions, diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 32721eb20..967c2be86 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -11,6 +11,28 @@ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { }, }); +/** 根据不同的数据库,插入不同的表名 */ +const handleInsertText = (text, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { + if (databaseCode === DatabaseTypeCode.POSTGRESQL) { + return `${text}`; + } else { + return `${text}`; + } +}; + +function checkTableContext(text) { + const normalizedText = text.trim().toUpperCase(); + const tableKeywords = ['FROM', 'JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'UPDATE']; + + for (const keyword of tableKeywords) { + if (normalizedText.endsWith(keyword)) { + return true; + } + } + + return false; +} + const registerIntelliSenseTable = ( tableList: Array<{ name: string; comment: string }>, databaseCode?: DatabaseTypeCode, @@ -18,10 +40,7 @@ const registerIntelliSenseTable = ( databaseName?: string, schemaName?: string, ) => { - monaco.editor.registerCommand('myCustomCommand', (_: any, ...args: any[]) => { - // access the arguments here - console.log('trigger suggest', args[0]); - // addIntelliSenseField(tableName) + monaco.editor.registerCommand('addFieldList', (_: any, ...args: any[]) => { addIntelliSenseField(args[0]); return; }); @@ -30,13 +49,15 @@ const registerIntelliSenseTable = ( intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' '], provideCompletionItems: (model, position) => { - const handleInsertText = (text) => { - if (databaseCode === DatabaseTypeCode.POSTGRESQL) { - return `${text}`; - } else { - return `${text}`; - } - }; + const lineContentUntilPosition = model.getValueInRange({ + startLineNumber: position.lineNumber, + startColumn: 1, + endLineNumber: position.lineNumber, + endColumn: position.column, + }); + + const isTableContext = checkTableContext(lineContentUntilPosition); + return { suggestions: (tableList || []).map((tableName) => { return { @@ -46,12 +67,13 @@ const registerIntelliSenseTable = ( description: '表名', }, kind: monaco.languages.CompletionItemKind.Variable, - insertText: handleInsertText(tableName.name), + insertText: handleInsertText(tableName.name, databaseCode), // range: monaco.Range.fromPositions(position), documentation: tableName.comment, + sortText: (isTableContext ? '01' : '02') + tableName.name, command: { - id: 'myCustomCommand', - title: 'operator_additional_suggestions', + id: 'addFieldList', + title: 'addFieldList', arguments: [ { tableName: tableName.name, diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 9c7f9c872..961aa746d 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1560,6 +1560,21 @@ dependencies: tslib "^2.0.0" +"@electron/get@^2.0.0": + version "2.0.3" + resolved "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" + integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^11.8.5" + progress "^2.0.3" + semver "^6.2.0" + sumchecker "^3.0.1" + optionalDependencies: + global-agent "^3.0.0" + "@electron/universal@1.2.1": version "1.2.1" resolved "https://registry.npmmirror.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339" @@ -2150,6 +2165,11 @@ resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@stylelint/postcss-css-in-js@^0.38.0": version "0.38.0" resolved "https://registry.npmmirror.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.38.0.tgz#eabb061df932744db766f11a153ae1c465b6263c" @@ -2249,6 +2269,13 @@ deepmerge "^4.2.2" svgo "^2.8.0" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@tanstack/match-sorter-utils@^8.7.0": version "8.8.4" resolved "https://registry.npmmirror.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" @@ -2321,6 +2348,16 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/classnames@^2.2.9": version "2.3.1" resolved "https://registry.npmmirror.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" @@ -2380,6 +2417,11 @@ resolved "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== +"@types/http-cache-semantics@*": + version "4.0.2" + resolved "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz#abe102d06ccda1efdf0ed98c10ccf7f36a785a41" + integrity sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw== + "@types/invariant@^2.2.31": version "2.2.35" resolved "https://registry.npmmirror.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" @@ -2424,6 +2466,13 @@ resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/lodash@^4.14.195": version "4.14.195" resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" @@ -2444,6 +2493,11 @@ resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== +"@types/node@^16.11.26": + version "16.18.58" + resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.58.tgz#bf66f63983104ed57c754f4e84ccaf16f8235adb" + integrity sha512-YGncyA25/MaVtQkjWW9r0EFBukZ+JulsLcVZBlGUfIb96OBMjkoRWwQo5IEWJ8Fj06Go3GHw+bjYDitv6BaGsA== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2478,6 +2532,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/responselike@^1.0.0": + version "1.0.1" + resolved "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.1.tgz#1dd57e54509b3b95c7958e52709567077019d65d" + integrity sha512-TiGnitEDxj2X0j+98Eqk5lv/Cij8oHd32bU4D/Yw6AOq7vvTk0gSD2GPj0G/HkvhMoVsdlhYF4yqqlyPBTM6Sg== + dependencies: + "@types/node" "*" + "@types/scheduler@*": version "0.16.3" resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" @@ -2532,6 +2593,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.10.1" + resolved "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.1.tgz#4e8f299f0934d60f36c74f59cb5a8483fd786691" + integrity sha512-CHzgNU3qYBnp/O4S3yv2tXPlvMTq0YWSTVg2/JYLqWZGHwwgJGAwd00poay/11asPq8wLFwHzubyInqHIFmmiw== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz#deee67e399f2cb6b4608c935777110e509d8018c" @@ -3727,6 +3795,11 @@ boolbase@^1.0.0: resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boolean@^3.0.1: + version "3.2.0" + resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== + bplist-parser@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" @@ -3852,6 +3925,11 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -3932,6 +4010,24 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -4076,6 +4172,13 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -4491,6 +4594,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4519,6 +4629,11 @@ default-browser@^4.0.0: execa "^7.1.1" titleize "^3.0.0" +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-data-property@^1.0.1: version "1.1.0" resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" @@ -4890,6 +5005,15 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== +electron@^22.3.0: + version "22.3.27" + resolved "https://registry.npmmirror.com/electron/-/electron-22.3.27.tgz#b77451a53f0c502e7559cceac28ac58eb289eef8" + integrity sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A== + dependencies: + "@electron/get" "^2.0.0" + "@types/node" "^16.11.26" + extract-zip "^2.0.1" + elliptic@^6.5.3: version "6.5.4" resolved "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -4920,7 +5044,7 @@ encoding@^0.1.11: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4954,6 +5078,11 @@ entities@^4.4.0: resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + errno@^0.1.1: version "0.1.8" resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -5139,6 +5268,11 @@ es5-imcompatible-versions@^0.1.78: resolved "https://registry.npmmirror.com/es5-imcompatible-versions/-/es5-imcompatible-versions-0.1.86.tgz#e9583ad3a7a93c1b13835fb804a3bbd05e30a662" integrity sha512-Lbrsn5bCL4iVMBdundiFVNIKlnnoBiIMrjtLRe1Snt92s60WHotw83S2ijp5ioqe6pDil3iBPY634VDwBcb1rg== +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -5532,6 +5666,17 @@ ext@^1.1.2: dependencies: type "^2.7.2" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@^1.2.0: version "1.4.1" resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" @@ -5609,6 +5754,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -5743,6 +5895,15 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -5840,6 +6001,13 @@ get-stdin@^9.0.0: resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -5903,6 +6071,18 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" + integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== + dependencies: + boolean "^3.0.1" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" + global@^4.3.2: version "4.4.0" resolved "https://registry.npmmirror.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" @@ -5923,7 +6103,7 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.3: +globalthis@^1.0.1, globalthis@^1.0.3: version "1.0.3" resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== @@ -5960,6 +6140,23 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +got@^11.8.5: + version "11.8.6" + resolved "https://registry.npmmirror.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -6137,6 +6334,11 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6151,6 +6353,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -6824,6 +7034,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json2mq@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" @@ -6843,6 +7058,13 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.2: resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -6872,6 +7094,13 @@ keyboardevents-areequal@^0.2.1: resolved "https://registry.npmmirror.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== +keyv@^4.0.0: + version "4.5.4" + resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + keyv@^4.5.3: version "4.5.3" resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" @@ -7047,6 +7276,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7081,6 +7315,13 @@ markdown-it-link-attributes@^4.0.1: resolved "https://registry.npmmirror.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz#25751f2cf74fd91f0a35ba7b3247fa45f2056d88" integrity sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ== +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -7165,6 +7406,16 @@ mimic-fn@^4.0.0: resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -7417,6 +7668,11 @@ normalize-range@^0.1.2: resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -7538,7 +7794,7 @@ on-exit-leak-free@^0.2.0: resolved "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -7595,6 +7851,11 @@ os-browserify@^0.3.0: resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -7740,6 +8001,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -8205,6 +8471,11 @@ process@^0.11.10: resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -8236,6 +8507,14 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -8293,6 +8572,11 @@ quick-format-unescaped@^4.0.3: resolved "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" @@ -9014,6 +9298,11 @@ resize-observer-polyfill@^1.5.1: resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -9065,6 +9354,13 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + ret@~0.1.10: version "0.1.15" resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -9090,6 +9386,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" + integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + rollup-plugin-visualizer@5.9.0: version "5.9.0" resolved "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b" @@ -9229,17 +9537,22 @@ select-hose@^2.0.0: resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== + semver@^5.6.0, semver@^5.7.2: version "5.7.2" resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.0, semver@^6.3.1: +semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: +semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -9251,6 +9564,13 @@ semver@~7.0.0: resolved "https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -9458,6 +9778,11 @@ split2@^4.0.0: resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== +sprintf-js@^1.1.2: + version "1.1.3" + resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -9737,6 +10062,13 @@ sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" + integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== + dependencies: + debug "^4.1.0" + superjson@^1.10.0: version "1.13.1" resolved "https://registry.npmmirror.com/superjson/-/superjson-1.13.1.tgz#a0b6ab5d22876f6207fcb9d08b0cb2acad8ee5cd" @@ -10051,6 +10383,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -10174,6 +10511,11 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -10469,6 +10811,14 @@ yargs@^17.5.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 0716f1698dfc874198937ff49ccaf1fc0f3b254c Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 10:59:15 +0800 Subject: [PATCH 0898/1069] feat: Optimize the effect of intelligent completion of table names --- chat2db-client/src/utils/IntelliSense/field.ts | 7 ++----- chat2db-client/src/utils/IntelliSense/table.ts | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index 9a77f17c1..583d52ee7 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -50,8 +50,6 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { triggerCharacters: [' ', '.', '('], provideCompletionItems: async (model, position) => { - console.log('registerIntelliSenseField'); - // 获取到当前行文本 const textUntilPosition = model.getValueInRange({ startLineNumber: position.lineNumber, @@ -61,8 +59,8 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa }); const isFieldContext = checkFieldContext(textUntilPosition); - const match = textUntilPosition.match(/(\b\w+\b)[^\w]*$/); + let word; if (match) { word = match[1]; @@ -73,7 +71,6 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa return; // 如果没有匹配到,直接返回 } if (word && tableList.includes(word) && !fieldList[word]) { - console.log('registerIntelliSenseField start word'); const data = await sqlService.getAllFieldByTable({ dataSourceId, databaseName, @@ -83,7 +80,7 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa fieldList[word] = data; } - const suggestions = Object.keys(fieldList).reduce((acc, cur) => { + const suggestions: monaco.languages.CompletionItem[] = Object.keys(fieldList).reduce((acc, cur) => { const arr = fieldList[cur].map((fieldObj) => ({ label: { label: fieldObj.name, diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 967c2be86..dd38f1883 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -13,8 +13,19 @@ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { /** 根据不同的数据库,插入不同的表名 */ const handleInsertText = (text, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { - if (databaseCode === DatabaseTypeCode.POSTGRESQL) { - return `${text}`; + if ( + [ + DatabaseTypeCode.POSTGRESQL, + DatabaseTypeCode.ORACLE, + DatabaseTypeCode.DB2, + DatabaseTypeCode.SQLITE, + ].includes(databaseCode) + ) { + return `\"${text}\"`; + } else if([DatabaseTypeCode.SQLSERVER].includes(databaseCode)){ + return `[${text}]`; + } else if ([DatabaseTypeCode.MYSQL].includes(databaseCode)) { + return `\`${text}\``; } else { return `${text}`; } @@ -69,7 +80,7 @@ const registerIntelliSenseTable = ( kind: monaco.languages.CompletionItemKind.Variable, insertText: handleInsertText(tableName.name, databaseCode), // range: monaco.Range.fromPositions(position), - documentation: tableName.comment, + // documentation: tableName.comment, sortText: (isTableContext ? '01' : '02') + tableName.name, command: { id: 'addFieldList', From a6fbbabae253ce284e8d8195aa6a8ec8ff7a75a9 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 13 Oct 2023 11:10:44 +0800 Subject: [PATCH 0899/1069] style --- chat2db-client/.vscode/settings.json | 1 + .../components/Console/MonacoEditor/index.tsx | 35 +++-- .../Loading/LoadingContent/index.less | 4 +- .../Loading/LoadingContent/index.tsx | 34 +++-- chat2db-client/src/pages/demo/index.tsx | 102 ++------------ .../workspace/components/SaveList/index.less | 17 ++- .../workspace/components/SaveList/index.tsx | 124 ++++++++++-------- .../workspace/components/TableList/index.less | 16 ++- .../workspace/components/TableList/index.tsx | 33 ++++- .../main/workspace/components/Tree/index.less | 2 +- .../components/WorkspaceLeft/index.less | 4 +- 11 files changed, 182 insertions(+), 190 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 23f53cd04..c943d901f 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -82,6 +82,7 @@ "VARCHAR", "VIEWCOLUMN", "wireframe", + "Wppk", "yapi" ], "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 3e4a58c69..d33638a03 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -132,7 +132,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { onSave?.(value || ''); }); - editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, (event: Event) => { + editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { const value = getCurrentSelectContent(); onExecute?.(value); }); @@ -169,11 +169,11 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { * @returns */ const getCurrentSelectContent = () => { - let selection = editorRef.current?.getSelection(); + const selection = editorRef.current?.getSelection(); if (!selection || selection.isEmpty()) { return ''; } else { - var selectedText = editorRef.current?.getModel()?.getValueInRange(selection); + const selectedText = editorRef.current?.getModel()?.getValueInRange(selection); return selectedText || ''; } }; @@ -187,16 +187,16 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const createAction = (editor: IEditorIns) => { // 用于控制切换该菜单键的显示 - const shouldShowSqlRunnerAction = editor.createContextKey('shouldShowSqlRunnerAction', true); + // const shouldShowSqlRunnerAction = editor.createContextKey('shouldShowSqlRunnerAction', true); if (!props.addAction || !props.addAction.length) { return; } props.addAction.forEach((action) => { - const { id, label, action: runFn } = action; + const { id: _id, label, action: runFn } = action; editor.addAction({ - id, + id: _id, label, // 控制该菜单键显示 precondition: 'shouldShowSqlRunnerAction', @@ -204,7 +204,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { contextMenuGroupId: 'navigation', contextMenuOrder: 1.5, // 点击该菜单键后运行 - run: (event: monaco.editor.ICodeEditor) => { + run: () => { const selectedText = editor.getModel()?.getValueInRange(editor.getSelection()!) || ''; runFn(selectedText); }, @@ -234,6 +234,10 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e editor.setValue(text); return; } + let newText = text; + const lastLine = editor.getModel().getLineCount(); + const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); + switch (range) { // 覆盖所有内容 case 'cover': @@ -246,7 +250,7 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e editor.revealLine(1); break; // 格式化选中区域的sql - case 'select': + case 'select': { const selection = editor.getSelection(); if (selection) { newRange = new monaco.Range( @@ -257,24 +261,29 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e ); } break; + } // 在末尾添加内容 case 'end': - const lastLine = editor.getModel().getLineCount(); - const lastLineLength = editor.getModel().getLineMaxColumn(lastLine); newRange = new monaco.Range(lastLine, lastLineLength, lastLine, lastLineLength); - text = `${text}`; - editor.revealLine(lastLine); + newText = `${text}`; break; default: break; } + const op = { range: newRange, - text, + text: newText, }; + // decorations?: IModelDeltaDecoration[]: 一个数组类型的参数,用于指定插入的文本的装饰。可以用来设置文本的样式、颜色、背景色等。如果不需要设置装饰,可以忽略此参数。 const decorations = [{}]; // 解决新增的文本默认背景色为灰色 editor.executeEdits('setValue', [op], decorations); + if (range === 'end') { + setTimeout(() => { + editor.revealLine(lastLine + 1); + }, 0); + } }; export default forwardRef(MonacoEditor); diff --git a/chat2db-client/src/components/Loading/LoadingContent/index.less b/chat2db-client/src/components/Loading/LoadingContent/index.less index b30e788d9..b69c62b43 100644 --- a/chat2db-client/src/components/Loading/LoadingContent/index.less +++ b/chat2db-client/src/components/Loading/LoadingContent/index.less @@ -1,4 +1,4 @@ -.box { +.loadingContent { height: 100%; position: relative; } @@ -18,7 +18,7 @@ } } -.coverLoading{ +.coverLoading { position: absolute; inset: 0; background-color: rgba(0, 0, 0, 0.01); diff --git a/chat2db-client/src/components/Loading/LoadingContent/index.tsx b/chat2db-client/src/components/Loading/LoadingContent/index.tsx index 548b4a3e9..027fccf15 100644 --- a/chat2db-client/src/components/Loading/LoadingContent/index.tsx +++ b/chat2db-client/src/components/Loading/LoadingContent/index.tsx @@ -1,18 +1,19 @@ -import React, { memo, PropsWithChildren, Fragment } from 'react'; +import React, { useRef, useImperativeHandle, ForwardedRef } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import StateIndicator from '@/components/StateIndicator'; -interface IProps { +// IProps继承div的原生属性 +interface IProps extends React.HTMLAttributes { className?: string; data?: T | null | undefined | true; empty?: React.ReactNode; handleEmpty?: boolean; isLoading?: boolean; - coverLoading?: boolean; + coverLoading?: boolean; } -export default function LoadingContent(props: PropsWithChildren>) { +export default function LoadingContent(props: IProps) { const { children, className, data = true, handleEmpty = false, empty, isLoading, coverLoading } = props; const isEmpty = !isLoading && handleEmpty && !(data as any)?.length; @@ -25,20 +26,17 @@ export default function LoadingContent(props: PropsWithChildren>) { return empty || ; } - return <> - {children} - { - (isLoading || !data) && coverLoading && -
    - -
    - } - + return ( + <> + {children} + {(isLoading || !data) && coverLoading && ( +
    + +
    + )} + + ); }; - return ( -
    - {renderContent()} -
    - ); + return
    {renderContent()}
    ; } diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index 682b9fa59..bdbf5d9d8 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,94 +1,20 @@ -import React, { useState } from 'react'; -import { Button, Form, Input, Select } from 'antd'; - -const { Option } = Select; - -type Currency = 'rmb' | 'dollar'; - -interface PriceValue { - number?: number; - currency?: Currency; -} - -interface PriceInputProps { - value?: PriceValue; - onChange?: (value: PriceValue) => void; -} - -const PriceInput: React.FC = ({ value = {}, onChange }) => { - const [number, setNumber] = useState(0); - const [currency, setCurrency] = useState('rmb'); - - const triggerChange = (changedValue: { number?: number; currency?: Currency }) => { - onChange?.({ number, currency, ...value, ...changedValue }); - }; - - const onNumberChange = (e: React.ChangeEvent) => { - const newNumber = parseInt(e.target.value || '0', 10); - if (Number.isNaN(number)) { - return; - } - if (!('number' in value)) { - setNumber(newNumber); - } - triggerChange({ number: newNumber }); - }; - - const onCurrencyChange = (newCurrency: Currency) => { - if (!('currency' in value)) { - setCurrency(newCurrency); - } - triggerChange({ currency: newCurrency }); - }; - - return ( - - - - - ); -}; +import React, { useEffect, useState } from 'react'; const App: React.FC = () => { - const onFinish = (values: any) => { - console.log('Received values from form: ', values); - }; - - const checkPrice = (_: any, value: { number: number }) => { - if (value.number > 0) { - return Promise.resolve(); - } - return Promise.reject(new Error('Price must be greater than zero!')); + const [name] = useState(''); + const [user, setUser] = useState<{ name: string; age: number }>({ name: '', age: 0 }); + + useEffect(() => { + setUser({ name: 'jack', age: 18 }); + }, []); + + const fuckUser = () => { + const { name: _name, age } = user; + // 一系列操作 + console.log(_name, age); + return true; }; - - return ( - { - console.log(a); - }} - initialValues={{ - price: { - number: 0, - currency: 'rmb', - }, - }} - > - - - - - - - - ); + return name + fuckUser(); }; export default App; 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 99a470f33..22a2afa81 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,7 @@ .saveModule { flex-shrink: 0; + padding-top: 6px; } .leftModuleTitle { @@ -15,10 +16,12 @@ height: 26px; .modelName { font-weight: bold; + line-height: 100%; } .iconBox { display: flex; align-items: center; + height: 100%; } .refreshIcon { margin-right: 10px; @@ -26,6 +29,9 @@ .refreshIcon, .searchIcon { cursor: pointer; + height: 100%; + display: flex; + align-items: center; &:hover { color: var(--color-primary); } @@ -42,14 +48,19 @@ overflow-y: auto; } +.leftModuleTitleShadow { + // 地步加一点模糊 + box-shadow: 0px 1px 2px 0px var(--color-border); +} + .saveItem { display: flex; justify-content: space-between; align-items: center; - padding: 0px 10px; - margin-bottom: 2px; + padding: 0px 6px; + height: 26px; line-height: 26px; - border-radius: 2px; + border-radius: 4px; user-select: none; cursor: pointer; .saveItemText { 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 e01919f1e..c82a41321 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -1,11 +1,9 @@ import React, { memo, useState, useEffect, useRef } from 'react'; -import classnames from 'classnames'; import i18n from '@/i18n'; import { connect } from 'umi'; import { Input, Dropdown } from 'antd'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; -import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import historyServer from '@/service/history'; import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType } from '@/constants'; @@ -13,28 +11,43 @@ import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; -interface IProps { - className?: string; - workspaceModel: IWorkspaceModelType['state'], - dispatch: any; -} +// interface IProps { +// className?: string; +// workspaceModel: IWorkspaceModelType['state']; +// dispatch: any; +// } -const dvaModel = connect( - ({ workspace }: { workspace: IWorkspaceModelType }) => ({ - workspaceModel: workspace, - }), -); +const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ + workspaceModel: workspace, +})); -const SaveList = dvaModel(function (props: any) { +const SaveList = dvaModel((props: any) => { const { workspaceModel, dispatch } = props; const { curWorkspaceParams, consoleList } = workspaceModel; const [searching, setSearching] = useState(false); const inputRef = useRef(); const [searchedList, setSearchedList] = useState(); + const leftModuleTitleRef = useRef(null); + const saveBoxListRef = useRef(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); + } + }); + } + }, []); useEffect(() => { if (!curWorkspaceParams.dataSourceId || !(curWorkspaceParams?.databaseName || curWorkspaceParams?.schemaName)) { - return + return; } dispatch({ type: 'workspace/fetchGetSavedConsole', @@ -49,8 +62,8 @@ const SaveList = dvaModel(function (props: any) { dispatch({ type: 'workspace/setConsoleList', payload: res.data, - }) - } + }); + }, }); }, [curWorkspaceParams]); @@ -60,8 +73,7 @@ const SaveList = dvaModel(function (props: any) { cursor: 'start', }); } - }, [searching]) - + }, [searching]); function openSearch() { setSearching(true); @@ -75,13 +87,13 @@ const SaveList = dvaModel(function (props: any) { } function onChange(value: string) { - setSearchedList(approximateList(consoleList, value,)) + setSearchedList(approximateList(consoleList, value)); } function openConsole(data: IConsole) { let p: any = { id: data.id, - tabOpened: ConsoleOpenedStatus.IS_OPEN + tabOpened: ConsoleOpenedStatus.IS_OPEN, }; historyServer.updateSavedConsole(p).then((res) => { dispatch({ @@ -92,7 +104,7 @@ const SaveList = dvaModel(function (props: any) { title: data.name, uniqueData: data, }, - }) + }); }); } @@ -120,55 +132,54 @@ const SaveList = dvaModel(function (props: any) { payload: { orderByDesc: true, status: ConsoleStatus.RELEASE, - ...curWorkspaceParams + ...curWorkspaceParams, }, callback: (res: any) => { dispatch({ type: 'workspace/setConsoleList', payload: res.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.saved')}
    +
    + {/*
    refreshTableList()}>
    */} -
    openSearch()}> - -
    +
    openSearch()}> +
    - } +
    + )}
    -
    +
    {(searchedList || consoleList)?.map((t: IConsole) => { return (
    { - openConsole(t) + openConsole(t); }} key={t.id} className={styles.saveItem} @@ -183,30 +194,29 @@ const SaveList = dvaModel(function (props: any) { key: 'open', label: i18n('common.button.open'), onClick: () => { - openConsole(t) - } + openConsole(t); + }, }, { key: 'delete', label: i18n('common.button.delete'), onClick: () => { - deleteSaved(t) + deleteSaved(t); }, }, ], }} >
    - +
    -
    ); })}
    -
    -
    +
    +
    ); }); diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index dbd7c3dd6..f897ee7b8 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -23,19 +23,26 @@ display: flex; align-items: center; font-weight: bold; + height: 100%; } .iconBox { display: flex; align-items: center; margin: 0px -5px; + height: 100%; } .itemIcon { - margin: 0px 5px; + padding: 0px 5px; + height: 100%; + display: flex; + align-items: center; } .refreshIcon { } .moreIcon { - transform: rotate(90deg); + i { + transform: rotate(90deg); + } } .refreshIcon, .searchIcon { @@ -50,6 +57,11 @@ } } +.leftModuleTitleShadow { + // 地步加一点模糊 + box-shadow: 0px 1px 2px 0px var(--color-border); +} + .treeBox { flex: 1; height: 0; 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 d72debae0..f06169a52 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -64,6 +64,8 @@ const TableList = dvaModel((props: any) => { const [tableLoading, setTableLoading] = useState(false); const [pagingData, setPagingData] = useState(defaultPaddingData); const [searchKey, setSearchKey] = useState(''); + const leftModuleTitleRef = useRef(null); + const treeBoxRef = useRef(null); // 导出表结构 const handleExport = (exportType: ExportTypeEnum) => { @@ -209,6 +211,27 @@ const TableList = dvaModel((props: any) => { } }, [searching]); + // 监听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 addConsole = () => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; const params = { @@ -329,7 +352,7 @@ const TableList = dvaModel((props: any) => { return (
    -
    +
    {searching ? (
    {
    )}
    - - - +
    + + + +
    {pagingData?.total > 100 && !searchKey && (
    diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/index.less b/chat2db-client/src/pages/main/workspace/components/Tree/index.less index 920397709..53cf5d703 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/index.less +++ b/chat2db-client/src/pages/main/workspace/components/Tree/index.less @@ -1,7 +1,7 @@ @import '../../../../../styles/var.less'; .treeBox { - padding: 0px 6px; + padding: 0px 6px 6px; } .treeNode { 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 b44c6bb64..6c0f73739 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -13,8 +13,8 @@ padding: 10px 0px 10px; } -.divider{ - margin: 10px 0px; +.divider { + margin: 0px 0px 10px; } .createButton { From 02fe15a510d4bb0b4419027e1277c640bbcb47b7 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 11:12:22 +0800 Subject: [PATCH 0900/1069] feat: Optimize the sort of intelligent completion of keyword and function --- chat2db-client/src/utils/IntelliSense/field.ts | 2 +- chat2db-client/src/utils/IntelliSense/keyword.ts | 2 ++ chat2db-client/src/utils/IntelliSense/table.ts | 13 +++++-------- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index 583d52ee7..2d59b5abf 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -89,7 +89,7 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa }, kind: monaco.languages.CompletionItemKind.Field, insertText: fieldObj.name, - sortText: (isFieldContext ? '01' : '02') + fieldObj.name, + sortText: isFieldContext ? '01' : '08', })); return [...acc, ...arr]; diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts index ef17b5eb8..4419dd299 100644 --- a/chat2db-client/src/utils/IntelliSense/keyword.ts +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -12,6 +12,7 @@ const getSQLKeywords = (keywords: string[]) => { }, kind: monaco.languages.CompletionItemKind.Text, insertText: key, + sortText: '08', })); }; @@ -22,6 +23,7 @@ const getSQLFunctions = (functions: string[]) => { label: key, detail: '', description: '函数', + sortText: '09', }, kind: monaco.languages.CompletionItemKind.Method, insertText: key, diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index dd38f1883..2efdc68a1 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -14,15 +14,12 @@ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { /** 根据不同的数据库,插入不同的表名 */ const handleInsertText = (text, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { if ( - [ - DatabaseTypeCode.POSTGRESQL, - DatabaseTypeCode.ORACLE, - DatabaseTypeCode.DB2, - DatabaseTypeCode.SQLITE, - ].includes(databaseCode) + [DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.ORACLE, DatabaseTypeCode.DB2, DatabaseTypeCode.SQLITE].includes( + databaseCode, + ) ) { return `\"${text}\"`; - } else if([DatabaseTypeCode.SQLSERVER].includes(databaseCode)){ + } else if ([DatabaseTypeCode.SQLSERVER].includes(databaseCode)) { return `[${text}]`; } else if ([DatabaseTypeCode.MYSQL].includes(databaseCode)) { return `\`${text}\``; @@ -81,7 +78,7 @@ const registerIntelliSenseTable = ( insertText: handleInsertText(tableName.name, databaseCode), // range: monaco.Range.fromPositions(position), // documentation: tableName.comment, - sortText: (isTableContext ? '01' : '02') + tableName.name, + sortText: isTableContext ? '01' : '08', command: { id: 'addFieldList', title: 'addFieldList', From 1857916422126ff39c052c5da82163e7baaec0d6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 13 Oct 2023 11:48:21 +0800 Subject: [PATCH 0901/1069] eslint --- .../src/blocks/DatabaseTableEditor/index.tsx | 2 +- .../src/blocks/EditTableData/index.tsx | 2 +- .../src/components/TabsNew/index.tsx | 2 +- .../components/WorkspaceRight/index.tsx | 33 +++++++++---------- chat2db-client/src/typings/workspace.ts | 1 - .../src/utils/IntelliSense/table.ts | 2 +- 6 files changed, 20 insertions(+), 22 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index a9af26827..0c9cc608c 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -14,7 +14,7 @@ import lodash from 'lodash'; interface IProps { dataSourceId: number; databaseName: string; - schemaName: string | null; + schemaName?: string | null; tableName?: string; databaseType: DatabaseTypeCode; changeTabDetails: (data: IWorkspaceTab) => void; diff --git a/chat2db-client/src/blocks/EditTableData/index.tsx b/chat2db-client/src/blocks/EditTableData/index.tsx index 4ddd0d5ee..f3b2bc0bb 100644 --- a/chat2db-client/src/blocks/EditTableData/index.tsx +++ b/chat2db-client/src/blocks/EditTableData/index.tsx @@ -7,7 +7,7 @@ interface IProps { className?: string; dataSourceId: number; databaseName: string; - schemaName: string | undefined; + schemaName?: string | null; tableName?: string; databaseType: DatabaseTypeCode; } diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 3e7d358ae..53707ec65 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -10,7 +10,7 @@ export interface ITabItem { key: number | string; popover?: string | React.ReactNode; children?: React.ReactNode; - editableName?: boolean | undefined; + editableName?: boolean; canClosed?: boolean; styles?: React.CSSProperties; } 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 d523bbfc5..efc28dc80 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -49,7 +49,6 @@ const WorkspaceRight = memo((props: IProps) => { id: t.id, title: t.name, type: t.operationType, - editableName: true, uniqueData: t, }; }); @@ -112,7 +111,7 @@ const WorkspaceRight = memo((props: IProps) => { if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.VIEW) { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, tableName, dataSourceId } = extraParams || {}; - const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { + const callback = (consoleId: number, _workspaceTabList: IWorkspaceTab[]) => { sqlService .getViewDetail({ dataSourceId: dataSourceId!, @@ -122,7 +121,7 @@ const WorkspaceRight = memo((props: IProps) => { }) .then((res) => { // 更新ddl - const newList = workspaceTabList.map((t) => { + const newList = _workspaceTabList.map((t) => { if (t.id === consoleId) { return { ...t, @@ -151,7 +150,7 @@ const WorkspaceRight = memo((props: IProps) => { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, triggerName, dataSourceId } = extraParams || {}; const name = doubleClickTreeNodeData.name; - const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { + const callback = (consoleId: number, _workspaceTabList: IWorkspaceTab[]) => { sqlService .getTriggerDetail({ dataSourceId: dataSourceId!, @@ -161,7 +160,7 @@ const WorkspaceRight = memo((props: IProps) => { }) .then((res) => { // 更新ddl - const newList = workspaceTabList.map((t) => { + const newList = _workspaceTabList.map((t) => { if (t.id === consoleId) { return { ...t, @@ -188,7 +187,7 @@ const WorkspaceRight = memo((props: IProps) => { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, procedureName, dataSourceId } = extraParams || {}; const name = doubleClickTreeNodeData.name; - const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { + const callback = (consoleId: number, _workspaceTabList: IWorkspaceTab[]) => { sqlService .getProcedureDetail({ dataSourceId: dataSourceId!, @@ -198,7 +197,7 @@ const WorkspaceRight = memo((props: IProps) => { }) .then((res) => { // 更新ddl - const newList = workspaceTabList.map((t) => { + const newList = _workspaceTabList.map((t) => { if (t.id === consoleId) { return { ...t, @@ -225,7 +224,7 @@ const WorkspaceRight = memo((props: IProps) => { const { extraParams } = doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, functionName } = extraParams || {}; const name = doubleClickTreeNodeData.name; - const callback = (consoleId: number, workspaceTabList: IWorkspaceTab[]) => { + const callback = (consoleId: number, _workspaceTabList: IWorkspaceTab[]) => { sqlService .getFunctionDetail({ dataSourceId: dataSourceId!, @@ -235,7 +234,7 @@ const WorkspaceRight = memo((props: IProps) => { }) .then((res) => { // 更新ddl - const newList = workspaceTabList?.map((t) => { + const newList = _workspaceTabList?.map((t) => { if (t.id === consoleId) { return { ...t, @@ -327,11 +326,11 @@ const WorkspaceRight = memo((props: IProps) => { doubleClickTreeNodeData: any; workSpaceTabType: WorkspaceTabType; name: string; - callback?: Function; + callback?: (res: number, list: any) => void; ddl?: string; }) { - const { doubleClickTreeNodeData, workSpaceTabType, name, callback, ddl } = params; - const { extraParams } = doubleClickTreeNodeData; + const { doubleClickTreeNodeData: _doubleClickTreeNodeData, workSpaceTabType, name, callback, ddl } = params; + const { extraParams } = _doubleClickTreeNodeData; const { databaseName, schemaName, dataSourceId, dataSourceName, databaseType } = extraParams || {}; const newConsole: any = { name, @@ -361,7 +360,7 @@ const WorkspaceRight = memo((props: IProps) => { }); } - function getConsoleList(callback?: Function) { + function getConsoleList(callback?: () => void) { const p: any = { pageNo: 1, pageSize: 999, @@ -434,7 +433,7 @@ const WorkspaceRight = memo((props: IProps) => { }; const closeWindowTab = (key: number) => { - let p: any = { + const p: any = { id: key, tabOpened: 'n', }; @@ -457,11 +456,11 @@ const WorkspaceRight = memo((props: IProps) => { } function editableNameOnBlur(t: ITabItem) { - let p: any = { + const _params: any = { id: t.key, name: t.label, }; - historyService.updateSavedConsole(p).then(() => { + historyService.updateSavedConsole(_params).then(() => { getConsoleList(); dispatch({ type: 'workspace/fetchGetSavedConsole', @@ -499,7 +498,7 @@ const WorkspaceRight = memo((props: IProps) => { prefixIcon: workspaceTabConfig[t.type]?.icon, label: t.title, key: t.id, - editableName: t.editableName, + editableName: t.type === WorkspaceTabType.CONSOLE, children: ( {[ diff --git a/chat2db-client/src/typings/workspace.ts b/chat2db-client/src/typings/workspace.ts index 9d3972432..fb06436df 100644 --- a/chat2db-client/src/typings/workspace.ts +++ b/chat2db-client/src/typings/workspace.ts @@ -12,7 +12,6 @@ export interface IWorkspaceTab { id: number | string; // Tab的id type: WorkspaceTabType; // 工作区tab的类型 title: string; // 工作区tab的名称 - editableName?: boolean; // 可以编辑名称 uniqueData?: any; } diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index dd38f1883..45ea0d5da 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -49,7 +49,7 @@ const registerIntelliSenseTable = ( databaseCode?: DatabaseTypeCode, dataSourceId?: number, databaseName?: string, - schemaName?: string, + schemaName?: string | null, ) => { monaco.editor.registerCommand('addFieldList', (_: any, ...args: any[]) => { addIntelliSenseField(args[0]); From 85885599be684d7b408e13b84399aff01ed74ce6 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 13:41:40 +0800 Subject: [PATCH 0902/1069] feat: Optimized icon display when IntelliSense --- chat2db-client/.vscode/settings.json | 2 +- .../Console/MonacoEditor/index.less | 2 +- chat2db-client/src/layouts/index.less | 20 +++++++++++++++++++ chat2db-client/src/layouts/index.tsx | 1 + chat2db-client/src/styles/global.less | 2 +- .../src/utils/IntelliSense/keyword.ts | 3 ++- .../src/utils/IntelliSense/table.ts | 2 +- 7 files changed, 27 insertions(+), 5 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index c943d901f..5788141f0 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -1,6 +1,6 @@ { "workbench.colorTheme": "One Dark Pro", - "workbench.iconTheme": "vscode-icons-mac", + "workbench.iconTheme": "material-icon-theme", "editor.fontSize": 14, "editor.tabSize": 2, // 自动格式化 diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.less b/chat2db-client/src/components/Console/MonacoEditor/index.less index 28c012a4a..3008a01a7 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.less +++ b/chat2db-client/src/components/Console/MonacoEditor/index.less @@ -1,3 +1,3 @@ .editorContainer { height: 100%; -} +} \ No newline at end of file diff --git a/chat2db-client/src/layouts/index.less b/chat2db-client/src/layouts/index.less index 2eb5969d4..c14626ecc 100644 --- a/chat2db-client/src/layouts/index.less +++ b/chat2db-client/src/layouts/index.less @@ -4,6 +4,25 @@ :global { #root { height: 100%; + + .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='); + } } } @@ -33,6 +52,7 @@ cursor: pointer; font-size: 18px; margin-top: 10px; + &:hover { color: var(--color-primary); } diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 9b099a8e5..b7cd72225 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -19,6 +19,7 @@ import { clearOlderLocalStorage } from '@/utils'; import registerMessage from './init/registerMessage'; import registerNotification from './init/registerNotification'; import MyNotification from '@/components/MyNotification'; + declare global { interface Window { _Lang: string; diff --git a/chat2db-client/src/styles/global.less b/chat2db-client/src/styles/global.less index af5aa84e6..5fa01c99a 100644 --- a/chat2db-client/src/styles/global.less +++ b/chat2db-client/src/styles/global.less @@ -137,4 +137,4 @@ li { button { box-shadow: none !important; -} +} \ No newline at end of file diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts index 4419dd299..279c7ff66 100644 --- a/chat2db-client/src/utils/IntelliSense/keyword.ts +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -25,7 +25,8 @@ const getSQLFunctions = (functions: string[]) => { description: '函数', sortText: '09', }, - kind: monaco.languages.CompletionItemKind.Method, + // kind: monaco.languages.CompletionItemKind.Method, + kind: monaco.languages.CompletionItemKind.Function, insertText: key, })); }; diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 2efdc68a1..7f13ce446 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -74,7 +74,7 @@ const registerIntelliSenseTable = ( detail: databaseName ? `(${databaseName})` : null, description: '表名', }, - kind: monaco.languages.CompletionItemKind.Variable, + kind: monaco.languages.CompletionItemKind.Folder, insertText: handleInsertText(tableName.name, databaseCode), // range: monaco.Range.fromPositions(position), // documentation: tableName.comment, From 61e2da4fd439655d80847fb1ba322dd181d98dd0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 14:15:44 +0800 Subject: [PATCH 0903/1069] feat: Add SQLServer keywords and functions --- .../src/constants/IntelliSense/index.ts | 3 +- .../src/constants/IntelliSense/sqlserver.ts | 245 ++++++++++++++++++ 2 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 chat2db-client/src/constants/IntelliSense/sqlserver.ts diff --git a/chat2db-client/src/constants/IntelliSense/index.ts b/chat2db-client/src/constants/IntelliSense/index.ts index ac945c7cb..6171df691 100644 --- a/chat2db-client/src/constants/IntelliSense/index.ts +++ b/chat2db-client/src/constants/IntelliSense/index.ts @@ -2,5 +2,6 @@ import mysql from './mysql'; import oracle from './oracle'; import postgresql from './pgsql'; import redis from './redis'; +import sqlserver from './sqlserver' -export default { mysql, oracle, postgresql, redis }; +export default { mysql, oracle, postgresql, redis, sqlserver }; diff --git a/chat2db-client/src/constants/IntelliSense/sqlserver.ts b/chat2db-client/src/constants/IntelliSense/sqlserver.ts new file mode 100644 index 000000000..c2332c83f --- /dev/null +++ b/chat2db-client/src/constants/IntelliSense/sqlserver.ts @@ -0,0 +1,245 @@ +import { DatabaseTypeCode } from '../common'; + +export default { + type: DatabaseTypeCode.SQLSERVER, + keywords: [ + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUTHORIZATION', + 'BACKUP', + 'BEGIN', + 'BETWEEN', + 'BREAK', + 'BROWSE', + 'BULK', + 'BY', + 'CASCADE', + 'CASE', + 'CHECK', + 'CLOSE', + 'CLUSTERED', + 'COALESCE', + 'COLLATE', + 'COLUMN', + 'COMMIT', + 'COMPUTE', + 'CONSTRAINT', + 'CONTAINS', + 'CONTAINSTABLE', + 'CONTINUE', + 'CONVERT', + 'CREATE', + 'CROSS', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_USER', + 'CURSOR', + 'DATABASE', + 'DBCC', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DELETE', + 'DENY', + 'DESC', + 'DISK', + 'DISTINCT', + 'DISTRIBUTED', + 'DOUBLE', + 'DROP', + 'DUMP', + 'ELSE', + 'END', + 'ERRLVL', + 'ESCAPE', + 'EXCEPT', + 'EXEC', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXTERNAL', + 'FETCH', + 'FILE', + 'FILLFACTOR', + 'FOR', + 'FOREIGN', + 'FREETEXT', + 'FREETEXTTABLE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GOTO', + 'GRANT', + 'GROUP', + 'HAVING', + 'HOLDLOCK', + 'IDENTITY', + 'IDENTITY_INSERT', + 'IDENTITYCOL', + 'IF', + 'IN', + 'INDEX', + 'INNER', + 'INSERT', + 'INTERSECT', + 'INTO', + 'IS', + 'JOIN', + 'KEY', + 'KILL', + 'LEFT', + 'LIKE', + 'LINENO', + 'LOAD', + 'MERGE', + 'NATIONAL', + 'NOCHECK', + 'NONCLUSTERED', + 'NOT', + 'NULL', + 'NULLIF', + 'OF', + 'OFF', + 'OFFSETS', + 'ON', + 'OPEN', + 'OPENDATASOURCE', + 'OPENQUERY', + 'OPENROWSET', + 'OPENXML', + 'OPTION', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'PERCENT', + 'PIVOT', + 'PLAN', + 'PRECISION', + 'PRIMARY', + 'PRINT', + 'PROC', + 'PROCEDURE', + 'PUBLIC', + 'RAISERROR', + 'READ', + 'READTEXT', + 'RECONFIGURE', + 'REFERENCES', + 'REPLICATION', + 'RESTORE', + 'RESTRICT', + 'RETURN', + 'REVERT', + 'REVOKE', + 'RIGHT', + 'ROLLBACK', + 'ROWCOUNT', + 'ROWGUIDCOL', + 'RULE', + 'SAVE', + 'SCHEMA', + 'SECURITYAUDIT', + 'SELECT', + 'SEMANTICKEYPHRASETABLE', + 'SEMANTICSIMILARITYDETAILSTABLE', + 'SEMANTICSIMILARITYTABLE', + 'SESSION_USER', + 'SET', + 'SETUSER', + 'SHUTDOWN', + 'SOME', + 'STATISTICS', + 'SYSTEM_USER', + 'TABLE', + 'TABLESAMPLE', + 'TEXTSIZE', + 'THEN', + 'TO', + 'TOP', + 'TRAN', + 'TRANSACTION', + 'TRIGGER', + 'TRUNCATE', + 'TRY_CONVERT', + 'TSEQUAL', + 'UNION', + 'UNIQUE', + 'UNPIVOT', + 'UPDATE', + 'UPDATETEXT', + 'USE', + 'USER', + 'VALUES', + 'VARYING', + 'VIEW', + 'WAITFOR', + 'WHEN', + 'WHERE', + 'WHILE', + 'WITH', + 'WRITETEXT', + ], + functions: [ + 'ABS', + 'AVG', + 'CAST', + 'CEILING', + 'COALESCE', + 'CONVERT', + 'COUNT', + 'CURRENT_TIMESTAMP', + 'DATALENGTH', + 'DATEADD', + 'DATEDIFF', + 'DATENAME', + 'DATEPART', + 'DAY', + 'GETDATE', + 'GETUTCDATE', + 'IDENT_CURRENT', + 'IDENT_INCR', + 'IDENT_SEED', + 'ISDATE', + 'ISNULL', + 'LEN', + 'LOWER', + 'LTRIM', + 'MAX', + 'MIN', + 'MONTH', + 'NEWID', + 'NULLIF', + 'PATINDEX', + 'RAND', + 'REPLACE', + 'REPLICATE', + 'REVERSE', + 'ROUND', + 'RTRIM', + 'SESSION_USER', + 'SPACE', + 'SQL_VARIANT_PROPERTY', + 'SQUARE', + 'SQRT', + 'STATS_DATE', + 'STDEV', + 'STDEVP', + 'STR', + 'STRING_AGG', + 'STRING_ESCAPE', + 'STRING_SPLIT', + 'SUBSTRING', + 'SUM', + 'SYSTEM_USER', + 'UPPER', + 'YEAR', + ], +}; From 0de30f19e42472388989f4ddede7dc6adbd8ceea Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 14:19:03 +0800 Subject: [PATCH 0904/1069] feat: Update SQLServer keywords and functions --- .../src/constants/IntelliSense/sqlserver.ts | 204 +++++++++++++++--- 1 file changed, 178 insertions(+), 26 deletions(-) diff --git a/chat2db-client/src/constants/IntelliSense/sqlserver.ts b/chat2db-client/src/constants/IntelliSense/sqlserver.ts index c2332c83f..8ef18344b 100644 --- a/chat2db-client/src/constants/IntelliSense/sqlserver.ts +++ b/chat2db-client/src/constants/IntelliSense/sqlserver.ts @@ -3,24 +3,33 @@ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.SQLSERVER, keywords: [ + 'ABSOLUTE', + 'ACTION', 'ADD', + 'AFTER', 'ALL', 'ALTER', + 'ALWAYS', 'AND', 'ANY', + 'APPLY', 'AS', 'ASC', 'AUTHORIZATION', 'BACKUP', 'BEGIN', 'BETWEEN', + 'BINARY', 'BREAK', 'BROWSE', 'BULK', 'BY', 'CASCADE', 'CASE', + 'CAST', + 'CATCH', 'CHECK', + 'CHECKPOINT', 'CLOSE', 'CLUSTERED', 'COALESCE', @@ -33,14 +42,13 @@ export default { 'CONTAINSTABLE', 'CONTINUE', 'CONVERT', + 'CORRESPONDING', 'CREATE', 'CROSS', 'CURRENT', - 'CURRENT_DATE', - 'CURRENT_TIME', - 'CURRENT_TIMESTAMP', - 'CURRENT_USER', 'CURSOR', + 'CYCLE', + 'DATA', 'DATABASE', 'DBCC', 'DEALLOCATE', @@ -49,13 +57,17 @@ export default { 'DELETE', 'DENY', 'DESC', - 'DISK', + 'DESCRIPTION', + 'DISABLE', + 'DISALLOW', + 'DISCONNECT', 'DISTINCT', 'DISTRIBUTED', 'DOUBLE', 'DROP', 'DUMP', 'ELSE', + 'ENABLE', 'END', 'ERRLVL', 'ESCAPE', @@ -68,10 +80,9 @@ export default { 'FETCH', 'FILE', 'FILLFACTOR', + 'FOLLOWING', 'FOR', 'FOREIGN', - 'FREETEXT', - 'FREETEXTTABLE', 'FROM', 'FULL', 'FUNCTION', @@ -81,23 +92,25 @@ export default { 'HAVING', 'HOLDLOCK', 'IDENTITY', - 'IDENTITY_INSERT', - 'IDENTITYCOL', 'IF', 'IN', 'INDEX', 'INNER', 'INSERT', + 'INSTEAD', 'INTERSECT', 'INTO', 'IS', 'JOIN', 'KEY', 'KILL', + 'LAST', 'LEFT', 'LIKE', + 'LIMIT', 'LINENO', 'LOAD', + 'LOCAL', 'MERGE', 'NATIONAL', 'NOCHECK', @@ -109,19 +122,18 @@ export default { 'OFF', 'OFFSETS', 'ON', + 'ONLY', 'OPEN', - 'OPENDATASOURCE', - 'OPENQUERY', - 'OPENROWSET', - 'OPENXML', 'OPTION', 'OR', 'ORDER', 'OUTER', + 'OUTPUT', 'OVER', + 'PARTITION', 'PERCENT', - 'PIVOT', 'PLAN', + 'PRECEDING', 'PRECISION', 'PRIMARY', 'PRINT', @@ -130,6 +142,7 @@ export default { 'PUBLIC', 'RAISERROR', 'READ', + 'READONLY', 'READTEXT', 'RECONFIGURE', 'REFERENCES', @@ -137,7 +150,6 @@ export default { 'RESTORE', 'RESTRICT', 'RETURN', - 'REVERT', 'REVOKE', 'RIGHT', 'ROLLBACK', @@ -162,13 +174,14 @@ export default { 'TABLESAMPLE', 'TEXTSIZE', 'THEN', + 'THROW', 'TO', 'TOP', 'TRAN', 'TRANSACTION', 'TRIGGER', 'TRUNCATE', - 'TRY_CONVERT', + 'TRY', 'TSEQUAL', 'UNION', 'UNIQUE', @@ -185,31 +198,85 @@ export default { 'WHERE', 'WHILE', 'WITH', + 'WITHIN', 'WRITETEXT', + 'XML', ], functions: [ 'ABS', + 'ACOS', + 'APP_NAME', + 'ASCII', + 'ASIN', + 'ATAN', + 'ATN2', 'AVG', + 'BIGCOUNT', + 'BINARY_CHECKSUM', 'CAST', 'CEILING', + 'CHAR', + 'CHARINDEX', + 'CHECKSUM', + 'CHECKSUM_AGG', 'COALESCE', + 'COL_LENGTH', + 'COL_NAME', + 'CONCAT', + 'CONCAT_WS', + 'CONNECTIONPROPERTY', + 'CONTEXT_INFO', 'CONVERT', + 'COS', + 'COT', 'COUNT', - 'CURRENT_TIMESTAMP', - 'DATALENGTH', + 'COUNT_BIG', + 'CRYPT_GEN_RANDOM', + 'CRYPT_PROPERTY', + 'CURSOR_STATUS', + 'DATABASEPROPERTYEX', 'DATEADD', 'DATEDIFF', 'DATENAME', 'DATEPART', 'DAY', + 'DB_ID', + 'DB_NAME', + 'DEGREES', + 'DIFFERENCE', + 'EXP', + 'FILE_ID', + 'FILE_NAME', + 'FILEGROUP_ID', + 'FILEGROUP_NAME', + 'FILEGROUPPROPERTY', + 'FILEPROPERTY', + 'FLOOR', + 'FORMAT', + 'GETANSINULL', 'GETDATE', 'GETUTCDATE', + 'HASHBYTES', + 'HOST_ID', + 'HOST_NAME', 'IDENT_CURRENT', 'IDENT_INCR', 'IDENT_SEED', + 'IIF', + 'INDEX_COL', + 'INDEXKEY_PROPERTY', + 'INDEXPROPERTY', 'ISDATE', 'ISNULL', + 'ISNUMERIC', + 'IS_MEMBER', + 'IS_OBJECTSIGNED', + 'IS_SRVROLEMEMBER', + 'ISNULL', + 'LEFT', 'LEN', + 'LOG', + 'LOG10', 'LOWER', 'LTRIM', 'MAX', @@ -217,29 +284,114 @@ export default { 'MONTH', 'NEWID', 'NULLIF', + 'OBJECT_DEFINITION', + 'OBJECT_ID', + 'OBJECT_NAME', + 'OBJECT_SCHEMA_NAME', + 'OBJECTPROPERTY', + 'OBJECTPROPERTYEX', + 'PARSE', 'PATINDEX', - 'RAND', + 'PERCENT_RANK', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + 'PI', + 'POWER', + 'RADIANS', + 'RANK', 'REPLACE', 'REPLICATE', 'REVERSE', + 'RIGHT', 'ROUND', + 'ROW_NUMBER', 'RTRIM', - 'SESSION_USER', + 'SCOPE_IDENTITY', + 'SERVERPROPERTY', + 'SESSIONPROPERTY', + 'SIGN', + 'SIN', + 'SOUNDEX', 'SPACE', - 'SQL_VARIANT_PROPERTY', - 'SQUARE', 'SQRT', + 'SQUARE', 'STATS_DATE', 'STDEV', 'STDEVP', - 'STR', - 'STRING_AGG', - 'STRING_ESCAPE', - 'STRING_SPLIT', + 'STUFF', 'SUBSTRING', 'SUM', + 'SUSER_ID', + 'SUSER_NAME', + 'SUSER_SID', + 'SUSER_SNAME', 'SYSTEM_USER', + 'TAN', + 'TEXTPTR', + 'TEXTVALID', + 'TRY_CAST', + 'TRY_CONVERT', + 'TRY_PARSE', + 'TYPE_ID', + 'TYPE_NAME', + 'TYPEPROPERTY', + 'UNICODE', 'UPPER', + 'USER_ID', + 'USER_NAME', 'YEAR', + 'GET_FILESTREAM_TRANSACTION_CONTEXT', + 'CURRENT_TRANSACTION_ID', + 'XACT_STATE', + 'STRING_AGG', + 'STRING_ESCAPE', + 'STRING_SPLIT', + 'FORMATMESSAGE', + 'XML_SCHEMA_NAMESPACE', + 'ISJSON', + 'JSON_VALUE', + 'JSON_QUERY', + 'JSON_MODIFY', + 'JSON_EXISTS', + 'JSON_TEXTCONTAINS', + 'CHOOSE', + 'IIF', + 'FORMAT', + 'PARSE', + 'SEQUENCE_SCHEMA', + 'SEQUENCE_NAME', + 'SPATIAL_ST_GEOMETRYTYPE', + 'SPATIAL_ST_DIMENSION', + 'SPATIAL_ST_SRID', + 'SPATIAL_ST_ISVALID', + 'SPATIAL_ST_ASTEXT', + 'SPATIAL_ST_ASBINARY', + 'SPATIAL_ST_AREA', + 'SPATIAL_ST_LENGTH', + 'SPATIAL_ST_ISCLOSED', + 'SPATIAL_ST_NUMPOINTS', + 'SPATIAL_ST_X', + 'SPATIAL_ST_Y', + 'SPATIAL_ST_NUMGEOMETRIES', + 'SPATIAL_ST_BOUNDARY', + 'SPATIAL_ST_BUFFER', + 'SPATIAL_ST_CENTROID', + 'SPATIAL_ST_CONTAINS', + 'SPATIAL_ST_CONVEXHULL', + 'SPATIAL_ST_CROSSES', + 'SPATIAL_ST_DIFFERENCE', + 'SPATIAL_ST_DISJOINT', + 'SPATIAL_ST_DISTANCE', + 'SPATIAL_ST_ENVELOPE', + 'SPATIAL_ST_EQUALS', + 'SPATIAL_ST_INTERSECTION', + 'SPATIAL_ST_INTERSECTS', + 'SPATIAL_ST_ISRING', + 'SPATIAL_ST_ISSIMPLE', + 'SPATIAL_ST_OVERLAPS', + 'SPATIAL_ST_SYMDIFFERENCE', + 'SPATIAL_ST_TOUCHES', + 'SPATIAL_ST_UNION', + 'SPATIAL_ST_WITHIN', ], }; From 06b309dfa46347c14dcecdcd8e36d6603489027c Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Fri, 13 Oct 2023 16:45:14 +0800 Subject: [PATCH 0905/1069] =?UTF-8?q?=E7=BD=91=E5=85=B3=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat2db/server/web/api/http/GatewayClientService.java | 8 ++++---- .../server/web/api/http/request/TableSchemaRequest.java | 4 ++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index 4f5017286..70e8f6d8e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -62,7 +62,7 @@ public interface GatewayClientService { * @param request * @return */ - @Post("/api/milvus/knowledge/save") + @Post("/api/client/milvus/knowledge/save") ActionResult knowledgeVectorSave(KnowledgeRequest request); /** @@ -71,7 +71,7 @@ public interface GatewayClientService { * @param request * @return */ - @Post("/api/milvus/schema/save") + @Post("/api/client/milvus/schema/save") ActionResult schemaVectorSave(TableSchemaRequest request); /** @@ -80,7 +80,7 @@ public interface GatewayClientService { * @param searchVectors * @return */ - @Get("/api/milvus/knowledge/search") + @Get("/api/client/milvus/knowledge/search") DataResult knowledgeVectorSearch(KnowledgeRequest searchVectors); /** @@ -89,6 +89,6 @@ public interface GatewayClientService { * @param request * @return */ - @Get("/api/milvus/schema/search") + @Get("/api/client/milvus/schema/search") DataResult schemaVectorSearch(TableSchemaRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java index 3747f5d5d..aac8c1e03 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java @@ -18,6 +18,10 @@ public class TableSchemaRequest { private String databaseName; + private String apiKey; + + private String dataSourceSchema; + private List> schemaVector; private List schemaList; From abb6f3089cb477bb40879d4f7c2ecac151df67ad Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 13 Oct 2023 19:22:12 +0800 Subject: [PATCH 0906/1069] login --- chat2db-client/.umirc.prod.desktop.ts | 3 + chat2db-client/.umirc.ts | 3 - chat2db-client/readme.md | 42 +++---- chat2db-client/src/blocks/Setting/index.tsx | 12 +- chat2db-client/src/indexedDB/index.ts | 0 chat2db-client/src/indexedDB/indexedDB.ts | 104 +++++++++++++++++ chat2db-client/src/indexedDB/table.ts | 110 ++++++++++++++++++ chat2db-client/src/layouts/index.tsx | 80 +++++++------ chat2db-client/src/pages/demo/index.tsx | 20 +++- chat2db-client/src/pages/login/index.tsx | 13 +-- .../src/pages/main/connection/index.tsx | 6 +- chat2db-client/src/service/ai.ts | 1 - chat2db-client/src/service/base.ts | 9 +- chat2db-client/src/theme/index.ts | 2 +- chat2db-client/src/utils/index.ts | 9 ++ 15 files changed, 331 insertions(+), 83 deletions(-) create mode 100644 chat2db-client/src/indexedDB/index.ts create mode 100644 chat2db-client/src/indexedDB/indexedDB.ts create mode 100644 chat2db-client/src/indexedDB/table.ts diff --git a/chat2db-client/.umirc.prod.desktop.ts b/chat2db-client/.umirc.prod.desktop.ts index 0a9ae590c..8fc0c0132 100644 --- a/chat2db-client/.umirc.prod.desktop.ts +++ b/chat2db-client/.umirc.prod.desktop.ts @@ -13,6 +13,9 @@ const chainWebpack = (config: any, { webpack }: any) => { }; export default defineConfig({ + history: { + type: 'hash', + }, publicPath: './', chainWebpack, define: { diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index ae741c1e4..089dd0931 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -16,9 +16,6 @@ const chainWebpack = (config: any, { webpack }: any) => { export default defineConfig({ title: 'Chat2DB', - history: { - type: 'hash', - }, base: '/', publicPath: '/', hash: false, diff --git a/chat2db-client/readme.md b/chat2db-client/readme.md index 3825f7be4..c2802a2fc 100644 --- a/chat2db-client/readme.md +++ b/chat2db-client/readme.md @@ -9,30 +9,24 @@ 目录结构 tree ./ -L 2 -I node_modules ## 启动项目 - 强制使用yarn,因为环境变量、lock文件只维护了yarn,npm/pnpm可能会产生意想不到的bug - node版本要求16以上 - `npm i -g yarn` - `yarn` - `yarn run build:web:prod` - `cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front` (复制打包结果到指定目录。windows可能命令不一样,可以手动复制下) - 之后就可以启动后端了 `mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml` - - 启动前端项目调试 - `yarn run start:web` - 注意:因为electron包比较难下载,如果yarn时electron下载失败或超时,可以删除掉chat2db-client/package.json下的electron,再次yarn - -## TS书写规范 - 1. 所有的interface 与 type 必须已I开头 - `interface IState { name: string }` // good - `interface State { name: string }` // bad - -## 如何在js与css中使用颜色 - 具体转换在 /theme/index.ts 中的 InjectThemeVar - - js 在window._AppThemePack中去取 eg:`window._AppThemePack.controlItemBgActive` // good - - css eg: `background: var(--control-item-bg-active)` // good - - css `color: #fff` // bad - -## 如何使用国际化 + +强制使用 yarn,因为环境变量、lock 文件只维护了 yarn,npm/pnpm 可能会产生意想不到的 bug node 版本要求 16 以上 `npm i -g yarn` `yarn` `yarn run build:web:prod` `cp -r dist ../chat2db-server/chat2db-server-start/src/main/resources/static/front` (复制打包结果到指定目录。windows 可能命令不一样,可以手动复制下) 之后就可以启动后端了 `mvn clean package -B '-Dmaven.test.skip=true' -f chat2db-server/pom.xml` + +启动前端项目调试 `yarn run start:web` 注意:因为 electron 包比较难下载,如果 yarn 时 electron 下载失败或超时,可以删除掉 chat2db-client/package.json 下的 electron,再次 yarn + +## TS 书写规范 + +1. 所有的 interface 与 type 必须已 I 开头 `interface IState { name: string }` // good `interface State { name: string }` // bad + +## 如何在 js 与 css 中使用颜色 + +具体转换在 /theme/index.ts 中的 injectThemeVar + +- js 在 window.\_AppThemePack 中去取 eg:`window._AppThemePack.controlItemBgActive` // good +- css eg: `background: var(--control-item-bg-active)` // good +- css `color: #fff` // bad + +## 如何使用国际化 所有 key 参考格式为 `模块名称.文案类型.文案描述`。若文案包含可变部分,可使用 `{1}`、`{2}`、`{3}` 代替。 diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index 032ef750a..1b925b4b9 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -28,14 +28,19 @@ function Setting(props: IProps) { const [currentMenu, setCurrentMenu] = useState(0); + // 判断当前页面是否为登录页 + const loginPage = window.location.pathname === '/login'; + useEffect(() => { - if (isModalVisible) { + if (isModalVisible && !loginPage) { getAiSystemConfig(); } }, [isModalVisible]); useEffect(() => { - getAiSystemConfig(); + if (!loginPage) { + getAiSystemConfig(); + } }, []); const getAiSystemConfig = () => { @@ -114,6 +119,9 @@ function Setting(props: IProps) {
    {i18n('setting.title.setting')}
    {menusList.map((t, index) => { + if (loginPage && index === 1) { + return false; + } return (
    { + return new Promise((resolve, reject) => { + const request = window.indexedDB.open(dbName, version); + request.onerror = (event: any) => { + reject(event.target.error); + }; + request.onsuccess = (event: any) => { + resolve(event.target.result); + }; + request.onupgradeneeded = (event: any) => { + const db = event.target.result; // 数据库对象 + // 创建存储库 + tableList.forEach((item: any) => { + const { tableDetails } = item; + const objectStore = db.createObjectStore(tableDetails.name, tableDetails.primaryKey); + tableDetails.column.forEach((i: any) => { + if (i.isIndex) { + objectStore.createIndex(i.name, i.keyPath, i.options); + } + }); + }); + }; + }); +}; + +// 添加数据 +export const addData = (db: any, tableName: string, data: any) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.add(data); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = (error) => { + reject(error); + }; + }); +}; + +// 删除数据 +export const deleteData = (db: any, tableName: string, key: string) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.delete(key); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 修改数据 +export const updateData = (db: any, tableName: string, data: any) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.put(data); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 查询数据 +export const getData = (db: any, tableName: string, key: string) => { + return new Promise((resolve, reject) => { + const transaction = db.transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.get(key); + request.onsuccess = () => { + resolve(request.result); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 关闭数据库 +export const closeDB = (db: any) => { + return new Promise((resolve) => { + db.close(); + resolve(true); + }); +}; + +export default { + createDB, + addData, + deleteData, + updateData, + getData, + closeDB, +}; diff --git a/chat2db-client/src/indexedDB/table.ts b/chat2db-client/src/indexedDB/table.ts new file mode 100644 index 000000000..985c5c4c1 --- /dev/null +++ b/chat2db-client/src/indexedDB/table.ts @@ -0,0 +1,110 @@ +import { WorkspaceTabType } from '@/constants'; + +export interface IWorkspaceConsole { + id: number; // Tab的id + name: string; // 工作区tab的名称 + dataSourceId: number; // 数据源id + dataSourceName: string; // 数据源名称 + databaseName: string; // 数据库名称 + schemaName?: string; // schema名称q + databaseType: string; // 数据源类型 + ddl: string; // 数据源ddl + workspaceTabType: WorkspaceTabType; // 操作类型 + userName?: string; // 用户名,用户的唯一id +} + +// 工作区console表 +export const workspaceConsoleTable = { + name: 'workspaceConsoleTab', + primaryKey: { + keyPath: 'id', + autoIncrement: true, + }, + column: [ + { + name: 'id', + keyPath: 'id', + isIndex: true, + options: { + unique: true, + }, + }, + { + name: 'userName', + keyPath: 'userName', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'name', + keyPath: 'name', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'dataSourceId', + keyPath: 'dataSourceId', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'dataSourceName', + keyPath: 'dataSourceName', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'databaseName', + keyPath: 'databaseName', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'schemaName', + keyPath: 'schemaName', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'databaseType', + keyPath: 'databaseType', + isIndex: true, + options: { + unique: false, + }, + }, + { + name: 'ddl', + keyPath: 'ddl', + options: { + unique: false, + }, + }, + { + name: 'workspaceTabType', + keyPath: 'workspaceTabType', + isIndex: true, + options: { + unique: false, + }, + }, + ], +} + +export const tableList = [ + { + tableDetails: workspaceConsoleTable, + } +] diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 9b099a8e5..cc4f0d7dd 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -1,24 +1,23 @@ -import React, { useEffect, useLayoutEffect } from 'react'; -import i18n from '@/i18n'; -import { Outlet } from 'umi'; -import { ConfigProvider, theme, App, Button, Spin, notification } from 'antd'; -import { useState } from 'react'; +import React, { useEffect, useLayoutEffect, useState } from 'react'; +import i18n, { isEn } from '@/i18n'; +import { Outlet, useNavigate } from 'umi'; +import { ConfigProvider, theme, Spin } from 'antd'; import { v4 as uuidv4 } from 'uuid'; -import { getAntdThemeConfig } from '@/theme'; +import { getAntdThemeConfig, injectThemeVar } from '@/theme'; import { IVersionResponse } from '@/typings'; import miscService from '@/service/misc'; +import { getUser } from '@/service/user'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import { useTheme } from '@/hooks'; -import { isEn } from '@/i18n'; -import { ThemeType, PrimaryColorType, LangType } from '@/constants/'; -import { InjectThemeVar } from '@/theme'; +import { ThemeType, LangType } from '@/constants/'; import styles from './index.less'; -import { getLang, getPrimaryColor, getTheme, setLang } from '@/utils/localStorage'; +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'; + declare global { interface Window { _Lang: string; @@ -45,11 +44,11 @@ initConfig(); window._Lang = getLang(); -const { getDesignToken, useToken } = theme; +const { useToken } = theme; -export const colorSchemeListeners: { [key: string]: Function } = {}; +export const colorSchemeListeners: { [key: string]: () => void } = {}; -export function addColorSchemeListener(callback: Function) { +export function addColorSchemeListener(callback: () => void) { const uuid = uuidv4(); colorSchemeListeners[uuid] = callback; return uuid; @@ -74,19 +73,28 @@ export default function Layout() { const restartCount = 200; function AppContainer() { + const navigate = useNavigate(); const { token } = useToken(); const [initEnd, setInitEnd] = useState(false); const [appTheme, setAppTheme] = useTheme(); const [startSchedule, setStartSchedule] = useState(1); // 0 初始状态 1 服务启动中 2 启动成功 const [serviceFail, setServiceFail] = useState(false); + const [isLogin, setIsLogin] = useState(null); useEffect(() => { - let date = new Date('2030-12-30 12:30:00').toUTCString(); + const date = new Date('2030-12-30 12:30:00').toUTCString(); document.cookie = `CHAT2DB.LOCALE=${getLang()};Expires=${date}`; + + getUser().then((res) => { + if (!res) { + navigate('/login'); + } + setIsLogin(!!res); + }); }, []); useEffect(() => { - InjectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); + injectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); }, [token]); useLayoutEffect(() => { @@ -157,34 +165,30 @@ function AppContainer() {
    {initEnd && (
    - {/* 待启动状态 */} - {/* {startSchedule === 0 &&
    } */} {/* 服务启动中 */} - {startSchedule === 1 && ( - <> -
    - - {/*
    + {(startSchedule === 1 || isLogin === null) && ( +
    + + {/*
    */} - {serviceFail && ( - <> -
    - {i18n('common.text.contactUs')}: - - github - -
    -
    - {i18n('common.text.tryToRestart')} -
    - - )} -
    - + {serviceFail && ( + <> +
    + {i18n('common.text.contactUs')}: + + github + +
    +
    + {i18n('common.text.tryToRestart')} +
    + + )} +
    )} {/* 服务启动完成 */} - {startSchedule === 2 && } + {startSchedule === 2 && isLogin !== null && }
    )} {/* 全局的弹窗 */} diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index bdbf5d9d8..96ee35661 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,11 +1,16 @@ import React, { useEffect, useState } from 'react'; +import indexedDB from '@/indexedDB/indexedDB'; const App: React.FC = () => { const [name] = useState(''); const [user, setUser] = useState<{ name: string; age: number }>({ name: '', age: 0 }); + const [db, setDb] = useState(); useEffect(() => { setUser({ name: 'jack', age: 18 }); + indexedDB.createDB('chat2db', 2).then((db) => { + setDb(db); + }); }, []); const fuckUser = () => { @@ -14,7 +19,20 @@ const App: React.FC = () => { console.log(_name, age); return true; }; - return name + fuckUser(); + + const add = () => { + indexedDB.addData(db, 'users', { + id: 8, + a: 1, + }); + }; + + return ( +
    + {name + fuckUser()} + +
    + ); }; export default App; diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 9f67ed49b..783e3b8b9 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -1,7 +1,6 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Button, Form, Input, Tooltip } from 'antd'; -import { getUser, userLogin } from '@/service/user'; -import { history } from 'umi'; +import { userLogin } from '@/service/user'; import LogoImg from '@/assets/logo/logo.png'; import styles from './index.less'; import Setting from '@/blocks/Setting'; @@ -14,10 +13,10 @@ interface IFormData { password: string; } -const App: React.FC = () => { +const Login: React.FC = () => { const navigate = useNavigate(); - const handleLogin = async (formData: { userName: string; password: string }) => { - let res = await userLogin(formData); + const handleLogin = async (formData: IFormData) => { + const res = await userLogin(formData); if (res) { navigate('/'); } @@ -77,4 +76,4 @@ const App: React.FC = () => { ); }; -export default App; +export default Login; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 6000f309b..64df74bb4 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -8,7 +8,7 @@ import connectionService from '@/service/connection'; import { databaseMap, databaseTypeList, ConnectionKind } from '@/constants'; import { IDatabase, IConnectionDetails, IConnectionEnv } from '@/typings'; import { Button, Dropdown } from 'antd'; -import { connect, history, Dispatch } from 'umi'; +import { connect } from 'umi'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import FileUploadModal from '@/components/ImportConnection'; @@ -42,7 +42,7 @@ interface IProps { function Connections(props: IProps) { const { connectionModel, dispatch } = props; - const { connectionList, connectionEnvList } = connectionModel; + const { connectionList } = connectionModel; const volatileRef = useRef(); const [curConnection, setCurConnection] = useState>({}); const [allMenuList, setAllMenuList] = useState(); @@ -160,7 +160,7 @@ function Connections(props: IProps) { [styles.menuItemActive]: curConnection.id === key, })} onDoubleClick={handleMenuItemDoubleClick.bind(null, t)} - onClick={(event) => { + onClick={() => { if (curConnection.id !== t.meta?.id) { setCurConnection(t.meta); } diff --git a/chat2db-client/src/service/ai.ts b/chat2db-client/src/service/ai.ts index 1deec1449..34d059740 100644 --- a/chat2db-client/src/service/ai.ts +++ b/chat2db-client/src/service/ai.ts @@ -1,4 +1,3 @@ -import { IChartItem, IDashboardItem, IPageResponse } from '@/typings'; import { IInviteQrCode, ILoginAndQrCode, IRemainingUse } from '@/typings/ai'; import createRequest from './base'; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 3237c7885..cbf21b819 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -1,5 +1,6 @@ -import { extend, ResponseError,type RequestOptionsInit } from 'umi-request'; +import { extend, ResponseError, type RequestOptionsInit } from 'umi-request'; import { message } from 'antd'; +import { navigate } from '@/utils' export type IErrorLevel = 'toast' | 'prompt' | 'critical' | false; export interface IOptions { @@ -116,14 +117,16 @@ request.interceptors.response.use(async (response) => { } const { errorCode } = res; if (errorCode === ErrorCode.NEED_LOGGED_IN) { - const callback = window.location.hash.substr(1).split('?')[0]; - window.location.href = '#/login' + (callback === '/login' ? '' : `?callback=${callback}`); + navigate('/login'); + // const callback = window.location.hash.substr(1).split('?')[0]; + // window.location.href = '#/login' + (callback === '/login' ? '' : `?callback=${callback}`); } return response; }); export default function createRequest

    (url: string, options?: IOptions) { + // 路由跳转 const { method = 'get', mock = false, errorLevel = 'toast', delayTime, outside, isFullPath, dynamicUrl } = options || {}; // 是否需要mock diff --git a/chat2db-client/src/theme/index.ts b/chat2db-client/src/theme/index.ts index dd6f288a5..0acec5ff3 100644 --- a/chat2db-client/src/theme/index.ts +++ b/chat2db-client/src/theme/index.ts @@ -21,7 +21,7 @@ export function getAntdThemeConfig(theme: ITheme) { } // TODO: 只插入一次 -export function InjectThemeVar(token: { [key in string]: string }, theme: ThemeType, primaryColor: PrimaryColorType) { +export function injectThemeVar(token: { [key in string]: string }, theme: ThemeType, primaryColor: PrimaryColorType) { let css = ''; Object.keys(token).map((t) => { const attributeName = camelToDash(t); diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index d3f736495..36c2c34ad 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -259,3 +259,12 @@ export function formatSql(sql: string, dbType: DatabaseTypeCode) { } }); } + +// 桌面端用hash模式,web端用history模式,路由跳转 +export function navigate(path: string) { + if (__ENV__ === 'desktop') { + window.location.href = `#${path}`; + } else { + window.location.href = path; + } +} From 85e4b62c090d90a580ffce71c4538fa3c22ade1a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 20:51:50 +0800 Subject: [PATCH 0907/1069] feat: Add tips about auto sync table to AI --- .../components/Console/ChatInput/index.less | 2 +- .../components/Console/ChatInput/index.tsx | 66 ++++++++++++------- .../src/components/Console/index.tsx | 5 +- chat2db-client/src/i18n/en-us/chat.ts | 6 +- chat2db-client/src/i18n/zh-cn/chat.ts | 7 +- 5 files changed, 53 insertions(+), 33 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index efdf834e9..abdec1adc 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -43,7 +43,7 @@ .tableSelectBlock { // margin-right: 8px; cursor: pointer; - padding: 4px; + padding: 8px; border-radius: 4px 12px; &:hover { diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 9f788c4e7..3b4eaa9a2 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; -import { Button, Checkbox, Dropdown, Input, Modal, Popover, Select, Spin } from 'antd'; +import { Button, Checkbox, Dropdown, Input, Modal, Popover, Select, Spin, Tooltip, Radio } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; @@ -12,12 +12,14 @@ interface IProps { value?: string; result?: string; tables?: string[]; + syncTableModel: number; selectedTables?: string[]; remainingUse?: IRemainingUse; aiType: AiSqlSourceType; remainingBtnLoading: boolean; disabled?: boolean; onPressEnter: (value: string) => void; + onSelectTableSyncModel: (model: number) => void; onSelectTables?: (tables: string[]) => void; onClickRemainBtn: Function; } @@ -37,31 +39,38 @@ const ChatInput = (props: IProps) => { }; const renderSelectTable = () => { - const { tables, selectedTables, onSelectTables } = props; + const { tables, syncTableModel, onSelectTableSyncModel, selectedTables, onSelectTables } = props; const options = (tables || []).map((t) => ({ value: t, label: t })); return (

    - - {/* */} - {i18n('chat.input.remain.tooltip')} - - { + onSelectTables && onSelectTables(v); + }} + /> + + +
    ); }; const renderSuffix = () => { - const remainCnt = props?.remainingUse?.remainingUses ?? '-'; + // const remainCnt = props?.remainingUse?.remainingUses ?? '-'; return (
    -
    - - - -
    + { + localStorage.setItem('syncTableBubble', 'true'); + }} + > +
    + + + +
    +
    + {/* {props.aiType === AiSqlSourceType.CHAT2DBAI && (
    { />
    ); -} +}; export default ChatInput; diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index b48079fb2..60119adf3 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -93,6 +93,7 @@ function Console(props: IProps) { const chatResult = useRef(''); const editorRef = useRef(); const [selectedTables, setSelectedTables] = useState([]); + const [syncTableModel, setSyncTableModel] = useState(0); const [isLoading, setIsLoading] = useState(false); const [aiContent, setAiContent] = useState(''); const [isAiDrawerOpen, setIsAiDrawerOpen] = useState(false); @@ -233,7 +234,7 @@ function Console(props: IProps) { dataSourceId, databaseName, schemaName, - tableNames: selectedTables, + tableNames: syncTableModel ? selectedTables : null, }); const handleMessage = (message: string) => { @@ -429,6 +430,8 @@ function Console(props: IProps) { setSelectedTables(tables); }} onClickRemainBtn={handleClickRemainBtn} + syncTableModel={syncTableModel} + onSelectTableSyncModel={(model: number) => setSyncTableModel(model)} /> )} {/*
    {chatContent.current}
    */} diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts index 4d455e804..0ddd55445 100644 --- a/chat2db-client/src/i18n/en-us/chat.ts +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -1,9 +1,9 @@ export default { 'chat.input.remain': '{1} remaining', - 'chat.input.remain.tooltip': 'Table schema added to prompt.', 'chat.input.tableSelect.placeholder':'Please choose tables', 'chat.input.tableSelect.error.TooManyTable':'You can only select up to 8 tables', 'chat.input.remain.dialog.tips': - 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', - + 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', + "chat.input.syncTable.tips":'Mode One: Automatically synchronize all tables to the AI context', + 'chat.input.remain.tooltip': 'Mode Two: Table schema added to prompt.', }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index 9220a5a13..76b9d343e 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -1,9 +1,8 @@ export default { 'chat.input.remain': '剩余 {1} 次', - 'chat.input.remain.tooltip': '选中表的schema将会被添加到prompt中', 'chat.input.tableSelect.placeholder': '请选择表', 'chat.input.tableSelect.error.TooManyTable': '最多选择8张表', - 'chat.input.remain.dialog.tips': - '关注公众号,发送"推广"获取更多体验次数', - + 'chat.input.remain.dialog.tips': '关注公众号,发送"推广"获取更多体验次数', + 'chat.input.syncTable.tips': '模式一: 自动同步所有表给AI上下文', + 'chat.input.remain.tooltip': '模式二: 选中表的schema将会被添加到prompt中', }; From 5487c23cf7bef986616e95ddda07f2496d8d990a Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 21:35:18 +0800 Subject: [PATCH 0908/1069] fix: Optimize sync table --- .../components/Console/ChatInput/index.less | 4 +- .../components/Console/ChatInput/index.tsx | 57 +++++++++++-------- chat2db-client/src/i18n/en-us/chat.ts | 11 ++-- chat2db-client/src/i18n/zh-cn/chat.ts | 5 +- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index abdec1adc..965fb1031 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -43,8 +43,8 @@ .tableSelectBlock { // margin-right: 8px; cursor: pointer; - padding: 8px; - border-radius: 4px 12px; + padding: 4px 8px; + border-radius: 8px; &:hover { background-color: var(--color-bg-subtle); diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 3b4eaa9a2..05006d9e7 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -1,7 +1,7 @@ import React, { ChangeEvent, useEffect, useState } from 'react'; import styles from './index.less'; import AIImg from '@/assets/img/ai.svg'; -import { Button, Checkbox, Dropdown, Input, Modal, Popover, Select, Spin, Tooltip, Radio } from 'antd'; +import { Button, Checkbox, Dropdown, Input, Modal, Popover, Select, Spin, Tooltip, Radio, Space } from 'antd'; import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; @@ -43,34 +43,41 @@ const ChatInput = (props: IProps) => { const options = (tables || []).map((t) => ({ value: t, label: t })); return (
    - onSelectTableSyncModel(v.target.value)} value={syncTableModel}> - {i18n('chat.input.syncTable.tips')} - - <> - - {/* */} - {i18n('chat.input.remain.tooltip')} - - { + onSelectTables && onSelectTables(v); + }} + /> + + )}
    ); }; const renderSuffix = () => { // const remainCnt = props?.remainingUse?.remainingUses ?? '-'; + const hasBubble = localStorage.getItem('syncTableBubble'); return (
    {i18n('chat.input.syncTable.tempTips')}} + defaultOpen={!hasBubble} + color={window._AppThemePack.colorBgBase} + trigger={'click'} onOpenChange={() => { localStorage.setItem('syncTableBubble', 'true'); }} diff --git a/chat2db-client/src/i18n/en-us/chat.ts b/chat2db-client/src/i18n/en-us/chat.ts index 0ddd55445..80079d764 100644 --- a/chat2db-client/src/i18n/en-us/chat.ts +++ b/chat2db-client/src/i18n/en-us/chat.ts @@ -1,9 +1,10 @@ export default { 'chat.input.remain': '{1} remaining', - 'chat.input.tableSelect.placeholder':'Please choose tables', - 'chat.input.tableSelect.error.TooManyTable':'You can only select up to 8 tables', + 'chat.input.tableSelect.placeholder': 'Please choose tables', + 'chat.input.tableSelect.error.TooManyTable': 'You can only select up to 8 tables', 'chat.input.remain.dialog.tips': - 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', - "chat.input.syncTable.tips":'Mode One: Automatically synchronize all tables to the AI context', - 'chat.input.remain.tooltip': 'Mode Two: Table schema added to prompt.', + 'Subscribe our official WeChat account, send 推广 to get more chances to experience.', + 'chat.input.syncTable.tips': 'The automatically synchronize all table structures to the AI context', + 'chat.input.remain.tooltip': 'The manually selected table will be synchronized to the AI context', + 'chat.input.syncTable.tempTips': '🎉Update: Automatically synchronize all table structures to the AI context', }; diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index 76b9d343e..d76f6745a 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -3,6 +3,7 @@ export default { 'chat.input.tableSelect.placeholder': '请选择表', 'chat.input.tableSelect.error.TooManyTable': '最多选择8张表', 'chat.input.remain.dialog.tips': '关注公众号,发送"推广"获取更多体验次数', - 'chat.input.syncTable.tips': '模式一: 自动同步所有表给AI上下文', - 'chat.input.remain.tooltip': '模式二: 选中表的schema将会被添加到prompt中', + 'chat.input.syncTable.tips': '自动同步所有表结构给AI上下文', + 'chat.input.remain.tooltip': '手动选中的表的结构将会同步给AI上下文', + 'chat.input.syncTable.tempTips': '🎉上线:自动同步所有表结构到AI上下文', }; From db57c6ff9ef1dbb413689133d89d4f68446c01dc Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 13 Oct 2023 22:11:39 +0800 Subject: [PATCH 0909/1069] fix: fix intelligent tips issue in Oracle --- .../src/constants/IntelliSense/oracle.ts | 272 +++++++----------- .../components/WorkspaceRight/index.tsx | 3 +- chat2db-client/src/service/sql.ts | 2 +- .../src/utils/IntelliSense/keyword.ts | 1 + 4 files changed, 112 insertions(+), 166 deletions(-) diff --git a/chat2db-client/src/constants/IntelliSense/oracle.ts b/chat2db-client/src/constants/IntelliSense/oracle.ts index bd3d1ff23..7d984cdd5 100644 --- a/chat2db-client/src/constants/IntelliSense/oracle.ts +++ b/chat2db-client/src/constants/IntelliSense/oracle.ts @@ -3,173 +3,117 @@ import { DatabaseTypeCode } from '../common'; export default { type: DatabaseTypeCode.ORACLE, keywords: [ - 'ABS', - 'ACOS', - 'ADD_MONTHS', - 'ASCII', - 'ASIN', - 'ATAN', - 'ATAN2', - 'AVG', - 'BFILENAME', - 'BIN_TO_NUM', - 'BITAND', - 'CARDINALITY', - 'CAST', - 'CEIL', - 'CHARTOROWID', - 'CHR', - 'COALESCE', - 'COMPOSE', - 'CON_DBID_TO_ID', - 'CON_GUID_TO_ID', - 'CON_ID_TO_CONTRACT', - 'CON_NAME_TO_CONTRACT', - 'CONCAT', - 'CONVERT', - 'CORR', - 'COS', - 'COSH', - 'COUNT', - 'COVAR_POP', - 'COVAR_SAMP', - 'CUME_DIST', - 'CURRENT_DATE', - 'CURRENT_TIMESTAMP', - 'DBTIMEZONE', - 'DECODE', - 'DECOMPOSE', - 'DENSE_RANK', - 'DUMP', - 'EMPTY_BLOB', - 'EMPTY_CLOB', - 'EXISTSNODE', - 'EXP', - 'EXTRACT', - 'EXTRACTVALUE', - 'FIRST_VALUE', - 'FLOOR', - 'FROM_TZ', - 'GREATEST', - 'GROUP_ID', - 'GROUPING', - 'GROUPING_ID', - 'HEXTORAW', - 'INITCAP', - 'INSTR', - 'INSTR2', - 'INSTR4', - 'INSTRB', - 'INSTRC', - 'LAG', - 'LAST_DAY', - 'LAST_VALUE', - 'LEAD', - 'LEAST', - 'LENGTH', - 'LENGTH2', - 'LENGTH4', - 'LENGTHB', - 'LENGTHC', - 'LISTAGG', - 'LN', - 'LNNVL', - 'LOCALTIMESTAMP', - 'LOG', - 'LOWER', - 'LPAD', - 'LTRIM', - 'MAX', - 'MEDIAN', - 'MIN', - 'MOD', - 'MONTHS_BETWEEN', - 'NANVL', - 'NCHR', - 'NEW_TIME', - 'NEXT_DAY', - 'NTH_VALUE', - 'NULLIF', - 'NUMTODSINTERVAL', - 'NUMTOYMINTERVAL', - 'NVL', - 'NVL2', - 'POWER', - 'RANK', - 'RAWTOHEX', - 'REGEXP_COUNT', - 'REGEXP_INSTR', - 'REGEXP_REPLACE', - 'REGEXP_SUBSTR', - 'REGR_AVGX', - 'REGR_AVGY', - 'REGR_COUNT', - 'REGR_INTERCEPT', - 'REGR_R2', - 'REGR_SLOPE', - 'REGR_SXX', - 'REGR_SXY', - 'REGR_SYY', - 'REPLACE', - 'ROUND', - 'ROW_NUMBER', - 'ROWIDTOCHAR', - 'ROWIDTONCHAR', - 'RPAD', - 'RTRIM', - 'SESSIONTIMEZONE', - 'SIGN', - 'SIN', - 'SINH', - 'SOUNDEX', - 'SQRT', - 'STDDEV', - 'STDDEV_POP', - 'STDDEV_SAMP', - 'SUBSTR', - 'SUM', - 'SYS_CONNECT_BY_PATH', - 'SYS_CONTEXT', - 'SYS_DBURIGEN', - 'SYS_EXTRACT_UTC', - 'SYS_GUID', - 'SYS_XMLAGG', - 'SYS_XMLGEN', - 'TAN', - 'TANH', - 'TO_CHAR', - 'TO_CLOB', - 'TO_DATE', - 'TO_DSINTERVAL', - 'TO_LOB', - 'TO_MULTI_BYTE', - 'TO_NCLOB', - 'TO_NUMBER', - 'TO_SINGLE_BYTE', - 'TO_YMINTERVAL', - 'TRANSLATE', - 'TRIM', - 'TRUNC', - 'TZ_OFFSET', + 'ACCESS', + 'ADD', + 'ALL', + 'ALTER', + 'AND', + 'ANY', + 'AS', + 'ASC', + 'AUDIT', + 'BETWEEN', + 'BY', + 'CHAR', + 'CHECK', + 'CLUSTER', + 'COLUMN', + 'COMMENT', + 'COMPRESS', + 'CONNECT', + 'CREATE', + 'CURRENT', + 'DATE', + 'DECIMAL', + 'DEFAULT', + 'DELETE', + 'DESC', + 'DISTINCT', + 'DROP', + 'ELSE', + 'EXCLUSIVE', + 'EXISTS', + 'FILE', + 'FLOAT', + 'FOR', + 'FROM', + 'GRANT', + 'GROUP', + 'HAVING', + 'IDENTIFIED', + 'IMMEDIATE', + 'IN', + 'INCREMENT', + 'INDEX', + 'INITIAL', + 'INSERT', + 'INTEGER', + 'INTERSECT', + 'INTO', + 'IS', + 'LEVEL', + 'LIKE', + 'LOCK', + 'LONG', + 'MAXEXTENTS', + 'MINUS', + 'MLSLABEL', + 'MODE', + 'MODIFY', + 'NOAUDIT', + 'NOCOMPRESS', + 'NOT', + 'NOWAIT', + 'NULL', + 'NUMBER', + 'OF', + 'OFFLINE', + 'ON', + 'ONLINE', + 'OPTION', + 'OR', + 'ORDER', + 'PCTFREE', + 'PRIOR', + 'PRIVILEGES', + 'PUBLIC', + 'RAW', + 'RENAME', + 'RESOURCE', + 'REVOKE', + 'ROW', + 'ROWID', + 'ROWNUM', + 'ROWS', + 'SELECT', + 'SESSION', + 'SET', + 'SHARE', + 'SIZE', + 'SMALLINT', + 'START', + 'SUCCESSFUL', + 'SYNONYM', + 'SYSDATE', + 'TABLE', + 'THEN', + 'TO', + 'TRIGGER', 'UID', - 'UNISTR', - 'UPPER', + 'UNION', + 'UNIQUE', + 'UPDATE', 'USER', - 'USERENV', - 'VAR_POP', - 'VAR_SAMP', - 'VARIANCE', - 'VSIZE', - 'WIDTH_BUCKET', - 'XMLAGG', - 'XMLCOLATTVAL', - 'XMLELEMENT', - 'XMLFOREST', - 'XMLSEQUENCE', - 'XMLTRANSFORM', + 'VALIDATE', + 'VALUES', + 'VARCHAR', + 'VARCHAR2', + 'VIEW', + 'WHENEVER', + 'WHERE', + 'WITH', ], - - keywords: [ + functions: [ 'CONNECT BY', 'START WITH', 'ORDER SIBLINGS BY', 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 efc28dc80..fc8c27590 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -307,7 +307,8 @@ const WorkspaceRight = memo((props: IProps) => { // 更新表名提示 useEffect(() => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; - if (databaseName) { + + if (databaseName || schemaName) { sqlService .getAllTableList({ dataSourceId, diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index acdca6755..f7f4e1583 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -245,7 +245,7 @@ const getAllTableList = createRequest< /** 获取表的所有字段 */ const getAllFieldByTable = createRequest< - { dataSourceId: number; databaseName: string; schemaName?: string | null; tableName: string }, + { dataSourceId: number; databaseName?: string; schemaName?: string | null; tableName: string }, Array<{ name: string; tableName: string }> >('/api/rdb/table/column_list', { method: 'get' }); diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts index 279c7ff66..f09dada45 100644 --- a/chat2db-client/src/utils/IntelliSense/keyword.ts +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -43,6 +43,7 @@ const registerIntelliSenseKeyword = (databaseCode?: DatabaseTypeCode) => { triggerCharacters: [' ', '('], provideCompletionItems: (model, position) => { const commonIntelliSense = Object.values(intelliSense).find((v) => v.type === databaseCode); + if (commonIntelliSense) { return { suggestions: [ From ebbc1fdc2c83f685e2346c41a166db0cabdba842 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sat, 14 Oct 2023 09:21:59 +0800 Subject: [PATCH 0910/1069] feat: Add new menu --- chat2db-client/src/main/menu.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index 8cbbde012..44ac00b55 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -101,7 +101,21 @@ const registerAppMenu = (mainWindow) => { { label: '访问官网', click() { - const url = 'https://chat2db.opensource.alibaba.com/'; + const url = 'https://chat2db.ai/'; + shell.openExternal(url); + }, + }, + { + label: '查看文档', + click() { + const url = 'https://doc.chat2db.ai/'; + shell.openExternal(url); + }, + }, + { + label: '查看更新日志', + click() { + const url = 'https://doc.chat2db.ai/changelog/'; shell.openExternal(url); }, }, From ac19fc60a64e882fcaa3c3ebde5cf0c9c52d17ba Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 14 Oct 2023 09:56:17 +0800 Subject: [PATCH 0911/1069] edit table Distinguish between different databases --- chat2db-client/.vscode/settings.json | 2 + .../DatabaseTableEditor/BaseInfo/index.tsx | 25 +- .../DatabaseTableEditor/ColumnList/index.tsx | 44 +++- .../DatabaseTableEditor/IncludeCol/index.tsx | 28 +- .../DatabaseTableEditor/IndexList/index.tsx | 242 ++++++++++-------- .../src/blocks/DatabaseTableEditor/index.tsx | 1 + .../SearchResult/TableBox/index.tsx | 7 +- chat2db-client/src/i18n/en-us/editTable.ts | 5 + chat2db-client/src/i18n/zh-cn/editTable.ts | 4 + .../components/WorkspaceHeader/index.tsx | 2 +- chat2db-client/src/typings/database.ts | 1 + 11 files changed, 237 insertions(+), 124 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 5788141f0..4af7ae187 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -55,6 +55,7 @@ "Mddhhmmss", "Menlo", "netstat", + "NOCASE", "nsis", "OCEANBASE", "OPENAI", @@ -67,6 +68,7 @@ "Prec", "remaininguses", "RESTAI", + "RTRIM", "scrollbar", "Sercurity", "sortablejs", diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx index 2dcbf3470..c82f2063c 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/BaseInfo/index.tsx @@ -4,6 +4,7 @@ import classnames from 'classnames'; import { Form, Input } from 'antd'; import { Context } from '../index'; import { IBaseInfo } from '@/typings'; +import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; export interface IBaseInfoRef { @@ -16,7 +17,7 @@ interface IProps { const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => { const { className } = props; - const { tableDetails } = useContext(Context); + const { tableDetails, databaseType } = useContext(Context); const [form] = Form.useForm(); useEffect(() => { @@ -53,15 +54,19 @@ const BaseInfo = forwardRef((props: IProps, ref: ForwardedRef) => - - - - - - - - - + {databaseType === DatabaseTypeCode.MYSQL && ( + <> + + + + + + + + + + + )}
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index b4fd2e670..61038a24a 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -10,7 +10,7 @@ import { CSS } from '@dnd-kit/utilities'; import { Context } from '../index'; import { IColumnItemNew, IColumnTypes } from '@/typings'; import i18n from '@/i18n'; -import { EditColumnOperationType } from '@/constants'; +import { EditColumnOperationType, DatabaseTypeCode } from '@/constants'; import CustomSelect from '@/components/CustomSelect'; import Iconfont from '@/components/Iconfont'; @@ -93,7 +93,7 @@ const createInitialData = () => { }; const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) => { - const { databaseSupportField, databaseName, schemaName, tableDetails } = useContext(Context); + const { databaseSupportField, databaseName, schemaName, tableDetails, databaseType } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingData, setEditingData] = useState(null); @@ -246,6 +246,10 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) { width: '40px', render: (text: string, record: IColumnItemNew) => { + // sqlLite不支持删除字段,新增的字段可以删除 + if (databaseType === DatabaseTypeCode.SQLITE && record.editStatus !== EditColumnOperationType.Add) { + return null; + } return (
    ) )} + {databaseType === DatabaseTypeCode.SQLSERVER && ( + + + + )} + {editingConfig?.supportDefaultValue && ( + + + + )} {editingConfig?.supportDefaultValue && ( ) )} + {editingConfig?.supportUnit && ( + + + + )} {editingConfig?.supportValue && ( @@ -427,6 +463,10 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) const onRow = (record: any) => { return { onClick: () => { + // sqlLite不支持修改字段,新增的字段可以修改 + if (databaseType === DatabaseTypeCode.SQLITE && record.editStatus !== EditColumnOperationType.Add) { + return; + } if (editingData?.key !== record.key) { edit(record); } diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 66036425f..6286c762a 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -17,6 +17,7 @@ import { Table, Form, Select, Button } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { Context } from '../index'; import { IColumnItemNew, IIndexIncludeColumnItem } from '@/typings'; +import { DatabaseTypeCode } from '@/constants'; import i18n from '@/i18n'; import lodash from 'lodash'; import Iconfont from '@/components/Iconfont'; @@ -53,7 +54,7 @@ export interface IIncludeColRef { const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) => { const { includedColumnList } = props; - const { columnListRef } = useContext(Context); + const { columnListRef, databaseType } = useContext(Context); const [dataSource, setDataSource] = useState([createInitialData()]); const [form] = Form.useForm(); const [editingKey, setEditingKey] = useState(null); @@ -181,6 +182,31 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) // }, // }, ]; + // sqlLite 添加排序规则 + if (databaseType === DatabaseTypeCode.SQLITE) { + columns.splice(2, 0, { + title: i18n('editTable.label.collation'), + dataIndex: 'collation', + render: (text: string, record: IIndexIncludeColumnItem) => { + const editable = isEditing(record); + return editable ? ( + + - - ) : ( -
    {text}
    - ); + const columns = useMemo(() => { + const _columns = [ + { + key: 'sort', + width: '40px', + align: 'center', }, - }, - { - title: i18n('editTable.label.indexType'), - dataIndex: 'type', - width: '180px', - render: (text: string, record: IIndexItem) => { - const editable = isEditing(record); - return editable ? ( - - - - ) : ( -
    {text}
    - ); + // { + // title: i18n('editTable.label.index'), + // width: '70px', + // align: 'center', + // render: (text: string, record: IIndexItem) => { + // return dataSource.findIndex((i) => i.key === record.key) + 1; + // }, + // }, + { + title: i18n('editTable.label.indexName'), + dataIndex: 'name', + width: '180px', + render: (text: string, record: IIndexItem) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
    {text}
    + ); + }, }, - }, - { - title: i18n('editTable.label.includeColumn'), - dataIndex: 'columnList', - render: (columnList: IIndexIncludeColumnItem[], record: IIndexItem) => { - const editable = isEditing(record); - const text = columnList - ?.map((t) => { - return `${t.columnName}`; - }) - .join(','); - return editable ? ( -
    - { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
    {text}
    + ); + }, + }, + { + title: i18n('editTable.label.includeColumn'), + dataIndex: 'columnList', + render: (columnList: IIndexIncludeColumnItem[], record: IIndexItem) => { + const editable = isEditing(record); + const text = columnList + ?.map((t) => { + return `${t.columnName}`; + }) + .join(','); + return editable ? ( +
    + { + setIncludeColModalOpen(true); + }} + > + {i18n('common.button.edit')} + + {text} +
    + ) : ( +
    {text}
    + ); + }, + }, + { + title: i18n('editTable.label.comment'), + dataIndex: 'comment', + render: (text: string, record: IIndexItem) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
    edit(record)}> + {text} +
    + ); + }, + }, + { + width: '40px', + render: (text: string, record: IIndexItem) => { + return ( +
    { - setIncludeColModalOpen(true); + deleteData(record); }} > - {i18n('common.button.edit')} - - {text} -
    - ) : ( -
    {text}
    - ); - }, - }, - { - title: i18n('editTable.label.comment'), - dataIndex: 'comment', - render: (text: string, record: IIndexItem) => { - const editable = isEditing(record); - return editable ? ( - - - - ) : ( -
    edit(record)}> - {text} -
    - ); - }, - }, - { - width: '40px', - render: (text: string, record: IIndexItem) => { - return ( -
    { - deleteData(record); - }} - > -
    - +
    + +
    -
    - ); + ); + }, }, - }, - ]; + ]; + if (databaseType === DatabaseTypeCode.MYSQL) { + _columns.splice(3, 0, { + title: i18n('editTable.label.indexMethod'), + dataIndex: 'method', + width: '180px', + render: (text: string, record: IIndexItem) => { + const editable = isEditing(record); + return editable ? ( + + + + ) : ( +
    {text}
    + ); + }, + }); + } + if (databaseType === DatabaseTypeCode.ORACLE) { + _columns.splice(-2, 1); + } + return _columns; + // TODO: isEditing 每次都会变所以这里是无意义的,后续在优化一下 + }, [isEditing]); const getIncludeColInfo = () => { setDataSource( @@ -353,7 +383,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) =
    - {i18n('editTable.button.addColumn')} + {i18n('editTable.button.addIndex')}
    diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx index 0c9cc608c..550ca5f30 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/index.tsx @@ -229,6 +229,7 @@ export default memo((props: IProps) => { columnListRef, indexListRef, databaseSupportField, + databaseType, }} >
    diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 1b994e4ed..194ca79a6 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -488,12 +488,12 @@ export default function TableBox(props: ITableProps) { : item, ), ); - + const newDataOldList = oldDataList.find((item) => item[0] === curOperationRowNo); setUpdateData([ ...updateData, { type: CRUD.DELETE, - oldDataList: oldDataList[curOperationRowNo], + oldDataList: newDataOldList, rowNo: curOperationRowNo, }, ]); @@ -579,7 +579,6 @@ export default function TableBox(props: ITableProps) { setQueryResultData(res?.[0]); }); }; - // 不同状态下的表格行样式 const tableRowStyle = (rowNo: string) => { // 如果是当前操作的行 @@ -587,7 +586,7 @@ export default function TableBox(props: ITableProps) { return { '--hover-bgcolor': 'transparent', '--bgcolor': 'transparent', - background: 'linear-gradient(140deg, #ff000038, #009cff3d)', + background: 'var(--color-primary-bg-hover)', }; } // 如果是删除过的行 diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts index 91fb5cfec..bce44febc 100644 --- a/chat2db-client/src/i18n/en-us/editTable.ts +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -10,6 +10,7 @@ export default { 'editTable.button.down': 'Down', 'editTable.label.indexName': 'Name', 'editTable.label.indexType': 'Type', + 'editTable.label.indexMethod': 'Index method', 'editTable.label.includeColumn': 'Include column', 'editTable.button.createTable': 'Create table', 'editTable.button.importTable': 'Import table', @@ -20,8 +21,11 @@ export default { 'editTable.label.nullable': 'Nullable', 'editTable.label.prefixLength': 'Prefix length', 'editTable.label.defaultValue': 'Default value', + 'editTable.label.sparse': 'Sparse', 'editTable.label.characterSet': 'Character set', 'editTable.label.collation': 'Collation', + 'editTable.label.decimalPoint': 'Decimal point', + 'editTable.label.unit': 'Unit', 'editTable.label.value': 'Value', 'editTable.label.autoIncrement': 'Auto increment', 'editTable.label.engine': 'Engine', @@ -29,4 +33,5 @@ export default { 'editTable.label.order': 'Order', 'editTable.title.sqlPreview': 'SQL preview', 'editTable.button.addColumn': 'Add column', + 'editTable.button.addIndex': 'Add Index', }; diff --git a/chat2db-client/src/i18n/zh-cn/editTable.ts b/chat2db-client/src/i18n/zh-cn/editTable.ts index 19e318705..3bd2c927d 100644 --- a/chat2db-client/src/i18n/zh-cn/editTable.ts +++ b/chat2db-client/src/i18n/zh-cn/editTable.ts @@ -10,6 +10,7 @@ export default { 'editTable.button.down': '下移', 'editTable.label.indexName': '索引名称', 'editTable.label.indexType': '索引类型', + 'editTable.label.indexMethod': '索引方法', 'editTable.label.includeColumn': '包含列', 'editTable.button.createTable': '新建表', 'editTable.button.importTable': '导出表', @@ -20,9 +21,11 @@ export default { 'editTable.label.nullable': '可空', 'editTable.label.prefixLength': '前缀长度', 'editTable.label.defaultValue': '默认值', + 'editTable.label.sparse': '稀疏', 'editTable.label.characterSet': '字符集', 'editTable.label.collation': '排序规则', 'editTable.label.decimalPoint': '小数点', + 'editTable.label.unit': '单位', 'editTable.label.value': '值', 'editTable.label.autoIncrement': '是否自增', 'editTable.label.engine': '引擎', @@ -30,4 +33,5 @@ export default { 'editTable.label.order': '排序', 'editTable.title.sqlPreview': 'sql预览', 'editTable.button.addColumn': '添加列', + 'editTable.button.addIndex': '添加索引', }; 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 c09cf5037..2460549d4 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -260,7 +260,7 @@ const WorkspaceHeader = memo((props) => {
    - + {!!curDBOptions?.length && } {!!curDBOptions?.length && ( Date: Sat, 14 Oct 2023 11:18:21 +0800 Subject: [PATCH 0912/1069] Hide edit tables for unsupported databases --- .../DatabaseTableEditor/ColumnList/index.tsx | 16 ---------------- .../ConnectionEdit/config/dataSource.ts | 16 +++++++++++++--- .../workspace/components/TableList/index.tsx | 18 ++++++++++++------ 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 61038a24a..5da77f531 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -409,22 +409,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) />
    )} - {editingConfig?.supportDefaultValue && ( - - - - )} {editingConfig?.supportCharset && ( diff --git a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts index af7536848..dd3739d9d 100644 --- a/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts +++ b/chat2db-client/src/components/ConnectionEdit/config/dataSource.ts @@ -360,7 +360,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:postgresql:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:postgresql://{host}:{port}/{database}', - excludes: [OperationColumn.EditTable] }, ssh: sshConfig, }, @@ -612,7 +611,6 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:h2:tcp:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:h2:tcp://{host}:{port}/{database}', - excludes: [OperationColumn.EditTable] }, ssh: sshConfig, }, @@ -1015,7 +1013,7 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:clickhouse:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:clickhouse://{host}:{port}/{database}', - excludes: [OperationColumn.ExportDDL, OperationColumn.CreateTable] + excludes: [OperationColumn.ExportDDL, OperationColumn.CreateTable,OperationColumn.EditTable] //排除掉导出ddl 和 创建表功能 支持的功能见 ./enum.ts => OperationColumn }, ssh: sshConfig, @@ -1124,6 +1122,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:dm:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:dm://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] + }, ssh: sshConfig, extendInfo: [ @@ -1249,6 +1249,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:db2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:db2://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] + }, ssh: sshConfig, extendInfo: [ @@ -1371,6 +1373,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:presto:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:presto://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] + }, ssh: sshConfig, extendInfo: [ @@ -1493,6 +1497,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:oceanbase:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:oceanbase://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] + }, ssh: sshConfig, extendInfo: [ @@ -1737,6 +1743,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:hive2:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:hive2://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] + }, ssh: sshConfig, extendInfo: [ @@ -1859,6 +1867,8 @@ export const dataSourceFormConfigs: IConnectionConfig[] = [ ], pattern: /jdbc:kingbase8:\/\/(.*):(\d+)(\/(\w+))?/, template: 'jdbc:kingbase8://{host}:{port}/{database}', + excludes: [OperationColumn.EditTable] + }, ssh: sshConfig, extendInfo: [ 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 f06169a52..63dbdd803 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -9,7 +9,7 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import Tree from '../Tree'; import { treeConfig } from '../Tree/treeConfig'; -import { TreeNodeType, WorkspaceTabType, ConsoleStatus, ConsoleOpenedStatus } from '@/constants'; +import { TreeNodeType, WorkspaceTabType, ConsoleStatus, ConsoleOpenedStatus, OperationColumn } from '@/constants'; import { approximateTreeNode } from '@/utils'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; @@ -18,6 +18,7 @@ import styles from './index.less'; import { ExportTypeEnum } from '@/typings/resultTable'; import historyService from '@/service/history'; import { debounce } from 'lodash'; +import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; interface IOption { value: TreeNodeType; @@ -72,8 +73,8 @@ const TableList = dvaModel((props: any) => { props.onExport && props.onExport(exportType); }; - const items: MenuProps['items'] = useMemo( - () => [ + const items: MenuProps['items'] = useMemo(() => { + const list = [ { label: (
    @@ -177,9 +178,14 @@ const TableList = dvaModel((props: any) => { }, ], }, - ], - [curWorkspaceParams], - ); + ]; + const dataSourceFormConfig = dataSourceFormConfigs.find((item) => item.type === curWorkspaceParams.databaseType); + + if (dataSourceFormConfig?.baseInfo.excludes?.includes(OperationColumn.EditTable)) { + list.splice(1, 1); + } + return list; + }, [curWorkspaceParams]); useUpdateEffect(() => { setCurList([]); From cdd743a578ff62e1ba374cbf1d62e1e351f4169d Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sat, 14 Oct 2023 13:25:15 +0800 Subject: [PATCH 0913/1069] add pg index --- .../plugin/postgresql/PostgreSQLMetaData.java | 1 + .../postgresql/type/PostgreSQLIndexTypeEnum.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+) 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 e5595b629..f9f9a14fb 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 @@ -291,6 +291,7 @@ public TableMeta getTableMeta(String databaseName, String schemaName, String tab .columnTypes(PostgreSQLColumnTypeEnum.getTypes()) .charsets(PostgreSQLCharsetEnum.getCharsets()) .collations(PostgreSQLCollationEnum.getCollations()) + .indexTypes(PostgreSQLIndexTypeEnum.getIndexTypes()) .build(); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java index 4506b1e88..206917f82 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/type/PostgreSQLIndexTypeEnum.java @@ -1,12 +1,16 @@ package ai.chat2db.plugin.postgresql.type; import ai.chat2db.spi.enums.EditStatus; +import ai.chat2db.spi.model.IndexType; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.model.TableIndexColumn; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import java.util.Arrays; +import java.util.List; + public enum PostgreSQLIndexTypeEnum { PRIMARY("Primary", "PRIMARY KEY"), @@ -21,10 +25,13 @@ public enum PostgreSQLIndexTypeEnum { private String name; private String keyword; + private IndexType indexType; + PostgreSQLIndexTypeEnum(String name, String keyword) { this.name = name; this.keyword = keyword; + this.indexType =new IndexType(name); } public static PostgreSQLIndexTypeEnum getByType(String type) { @@ -36,6 +43,14 @@ public static PostgreSQLIndexTypeEnum getByType(String type) { return null; } + public static List getIndexTypes() { + return Arrays.asList(PostgreSQLIndexTypeEnum.values()).stream().map(PostgreSQLIndexTypeEnum::getIndexType).collect(java.util.stream.Collectors.toList()); + } + + public IndexType getIndexType() { + return indexType; + } + public String getName() { return name; } From a5a2a4ce4282bfa3ed0f22dd9f2dc9d13ad3c989 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 14 Oct 2023 16:10:29 +0800 Subject: [PATCH 0914/1069] Use indexedDB to save automatically --- .../src/blocks/SQLExecute/index.tsx | 4 - .../src/components/Console/index.tsx | 49 ++++-- chat2db-client/src/indexedDB/index.ts | 158 ++++++++++++++++++ chat2db-client/src/indexedDB/indexedDB.ts | 104 ------------ chat2db-client/src/indexedDB/table.ts | 88 ++-------- chat2db-client/src/layouts/index.tsx | 14 +- chat2db-client/src/models/connection.ts | 2 +- chat2db-client/src/pages/demo/index.tsx | 25 ++- chat2db-client/src/pages/login/index.tsx | 26 ++- .../src/pages/main/connection/index.tsx | 12 +- chat2db-client/src/pages/main/index.tsx | 24 ++- .../workspace/components/TableList/index.tsx | 1 - .../Tree/TreeNodeRightClick/index.tsx | 19 ++- .../components/WorkspaceRight/index.tsx | 10 +- chat2db-client/src/utils/index.ts | 44 ++--- chat2db-client/src/utils/localStorage.ts | 7 +- 16 files changed, 304 insertions(+), 283 deletions(-) delete mode 100644 chat2db-client/src/indexedDB/indexedDB.ts diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 5f90e9676..fb8b67060 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -70,10 +70,6 @@ const SQLExecute = memo((props) => { setAppendValue({ text: data.initDDL }); }, [data.initDDL]); - useEffect(() => { - console.log(resultData); - }, [resultData]); - /** * 执行SQL * @param sql diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 60119adf3..3c8f83c92 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -13,13 +13,14 @@ import Iconfont from '../Iconfont'; import { IAiConfig, ITreeNode } from '@/typings'; import { IAIState } from '@/models/ai'; import Popularize from '@/components/Popularize'; -import { handleLocalStorageSavedConsole, readLocalStorageSavedConsoleText, formatSql } from '@/utils'; +import { formatSql, getCookie } from '@/utils'; import { chatErrorForKey, chatErrorToLogin } from '@/constants/chat'; import { AiSqlSourceType } from '@/typings/ai'; import i18n from '@/i18n'; import configService from '@/service/config'; // import NewEditor from './NewMonacoEditor'; import styles from './index.less'; +import indexedDB from '@/indexedDB'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -102,6 +103,7 @@ function Console(props: IProps) { const [modalProps, setModalProps] = useState({}); const timerRef = useRef(); const aiFetchIntervalRef = useRef(); + const initializeSuccessful = useRef(false); /** * 当前选择的AI类型是Chat2DBAI @@ -117,6 +119,21 @@ function Console(props: IProps) { } }, [appendValue]); + useEffect(() => { + indexedDB + .getDataByCursor('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + userId: getCookie('CHAT2DB.USER_ID'), + }) + .then((res: any) => { + const value = res?.[0]?.ddl || ''; + if (value) { + editorRef?.current?.setValue(value, 'reset'); + initializeSuccessful.current = true; + } + }); + }, []); + useEffect(() => { if (source !== 'workspace') { return; @@ -124,9 +141,13 @@ function Console(props: IProps) { // 离开时保存 if (!isActive) { // 离开时清除定时器 + indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + ddl: editorRef?.current?.getAllContent(), + userId: getCookie('CHAT2DB.USER_ID'), + }); if (timerRef.current) { clearInterval(timerRef.current); - handleLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); } } else { // 活跃时自动保存 @@ -139,20 +160,18 @@ function Console(props: IProps) { }; }, [isActive]); - useEffect(() => { - if (source !== 'workspace') { + function timingAutoSave() { + // 如果没有初始化那就不自动保存 + if (!initializeSuccessful.current) { return; } - const value = readLocalStorageSavedConsoleText(executeParams.consoleId!); - if (value) { - editorRef?.current?.setValue(value, 'reset'); - } - }, []); - - function timingAutoSave() { timerRef.current = setInterval(() => { - handleLocalStorageSavedConsole(executeParams.consoleId!, 'save', editorRef?.current?.getAllContent()); - }, 500); + indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + ddl: editorRef?.current?.getAllContent(), + userId: getCookie('CHAT2DB.USER_ID'), + }); + }, 10000); } const tableListName = useMemo(() => { @@ -338,8 +357,8 @@ function Console(props: IProps) { ddl: value, }; - historyServer.updateSavedConsole(p).then((res) => { - handleLocalStorageSavedConsole(executeParams.consoleId!, 'delete'); + historyServer.updateSavedConsole(p).then(() => { + indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', executeParams.consoleId!); message.success(i18n('common.tips.saveSuccessfully')); props.onConsoleSave && props.onConsoleSave(); }); diff --git a/chat2db-client/src/indexedDB/index.ts b/chat2db-client/src/indexedDB/index.ts index e69de29bb..9934fe89a 100644 --- a/chat2db-client/src/indexedDB/index.ts +++ b/chat2db-client/src/indexedDB/index.ts @@ -0,0 +1,158 @@ +import { tableList } from './table'; + +// 创建数据库的方法 +export const createDB = (dbName: string, version: number) => { + return new Promise((resolve, reject) => { + const request = window.indexedDB.open(dbName, version); + request.onerror = (event: any) => { + reject(event.target.error); + }; + request.onsuccess = (event: any) => { + resolve(event.target.result); + }; + request.onupgradeneeded = (event: any) => { + const db = event.target.result; // 数据库对象 + // 创建存储库 + tableList.forEach((item: any) => { + const { tableDetails } = item; + const objectStore = db.createObjectStore(tableDetails.name, tableDetails.primaryKey); + tableDetails.column.forEach((i: any) => { + if (i.isIndex) { + objectStore.createIndex(i.name, i.keyPath, i.options); + } + }); + }); + }; + }); +}; + +type TableType = 'workspaceConsoleDDL'; + +type DBType = 'chat2db'; + +// 添加数据 +export const addData = (db: DBType, tableName: TableType, data: any) => { + return new Promise((resolve, reject) => { + const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.add(data); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = (error) => { + reject(error); + }; + }); +}; + +// 通过索引删除数据 +export const deleteDataByIndex = (db: DBType, tableName: TableType, indexName, indexValue) => { + return new Promise((resolve, reject) => { + const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.index(indexName).delete(indexValue); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 通过主键删除数据 +export const deleteData = (db: DBType, tableName: TableType, key: any) => { + return new Promise((resolve, reject) => { + const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.delete(key); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 通过索引查询数据,支持传入多个索引 +export const getDataByIndex = (db: DBType, tableName: TableType, indexName: string, indexValue: any) => { + return new Promise((resolve, reject) => { + const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.index(indexName).get(indexValue); + request.onsuccess = () => { + resolve(request.result); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 通过游标查询数据,支持传入多个条件 +export const getDataByCursor = (db: DBType, tableName: TableType, condition: {[key in string]: any} +) => { + return new Promise((resolve, reject) => { + const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.openCursor(); + const result: any[] = []; + request.onsuccess = (event: any) => { + const cursor = event.target.result; + if (cursor) { + let flag = true; + Object.keys(condition).forEach((key) => { + if (cursor.value[key] !== condition[key]) { + flag = false; + } + }); + if (flag) { + result.push(cursor.value); + } + cursor.continue(); + } else { + resolve(result); + } + }; + request.onerror = () => { + reject(false); + }; + }); + +}; + + +// 修改数据 +export const updateData = (db: DBType, tableName: TableType, data: any) => { + return new Promise((resolve, reject) => { + const transaction = window._indexedDB[db].transaction(tableName, 'readwrite'); + const objectStore = transaction.objectStore(tableName); + const request = objectStore.put(data); + request.onsuccess = () => { + resolve(true); + }; + request.onerror = () => { + reject(false); + }; + }); +}; + +// 关闭数据库 +export const closeDB = (db: DBType) => { + return new Promise((resolve) => { + window._indexedDB[db].close(); + resolve(true); + }); +}; + +export default { + createDB, + addData, + deleteDataByIndex, + deleteData, + getDataByIndex, + getDataByCursor, + updateData, + closeDB, +}; diff --git a/chat2db-client/src/indexedDB/indexedDB.ts b/chat2db-client/src/indexedDB/indexedDB.ts deleted file mode 100644 index f2fc125bc..000000000 --- a/chat2db-client/src/indexedDB/indexedDB.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { tableList } from './table'; - -// 创建数据库的方法 -export const createDB = (dbName: string, version: number) => { - return new Promise((resolve, reject) => { - const request = window.indexedDB.open(dbName, version); - request.onerror = (event: any) => { - reject(event.target.error); - }; - request.onsuccess = (event: any) => { - resolve(event.target.result); - }; - request.onupgradeneeded = (event: any) => { - const db = event.target.result; // 数据库对象 - // 创建存储库 - tableList.forEach((item: any) => { - const { tableDetails } = item; - const objectStore = db.createObjectStore(tableDetails.name, tableDetails.primaryKey); - tableDetails.column.forEach((i: any) => { - if (i.isIndex) { - objectStore.createIndex(i.name, i.keyPath, i.options); - } - }); - }); - }; - }); -}; - -// 添加数据 -export const addData = (db: any, tableName: string, data: any) => { - return new Promise((resolve, reject) => { - const transaction = db.transaction(tableName, 'readwrite'); - const objectStore = transaction.objectStore(tableName); - const request = objectStore.add(data); - request.onsuccess = () => { - resolve(true); - }; - request.onerror = (error) => { - reject(error); - }; - }); -}; - -// 删除数据 -export const deleteData = (db: any, tableName: string, key: string) => { - return new Promise((resolve, reject) => { - const transaction = db.transaction(tableName, 'readwrite'); - const objectStore = transaction.objectStore(tableName); - const request = objectStore.delete(key); - request.onsuccess = () => { - resolve(true); - }; - request.onerror = () => { - reject(false); - }; - }); -}; - -// 修改数据 -export const updateData = (db: any, tableName: string, data: any) => { - return new Promise((resolve, reject) => { - const transaction = db.transaction(tableName, 'readwrite'); - const objectStore = transaction.objectStore(tableName); - const request = objectStore.put(data); - request.onsuccess = () => { - resolve(true); - }; - request.onerror = () => { - reject(false); - }; - }); -}; - -// 查询数据 -export const getData = (db: any, tableName: string, key: string) => { - return new Promise((resolve, reject) => { - const transaction = db.transaction(tableName, 'readwrite'); - const objectStore = transaction.objectStore(tableName); - const request = objectStore.get(key); - request.onsuccess = () => { - resolve(request.result); - }; - request.onerror = () => { - reject(false); - }; - }); -}; - -// 关闭数据库 -export const closeDB = (db: any) => { - return new Promise((resolve) => { - db.close(); - resolve(true); - }); -}; - -export default { - createDB, - addData, - deleteData, - updateData, - getData, - closeDB, -}; diff --git a/chat2db-client/src/indexedDB/table.ts b/chat2db-client/src/indexedDB/table.ts index 985c5c4c1..b22419333 100644 --- a/chat2db-client/src/indexedDB/table.ts +++ b/chat2db-client/src/indexedDB/table.ts @@ -1,110 +1,48 @@ -import { WorkspaceTabType } from '@/constants'; -export interface IWorkspaceConsole { - id: number; // Tab的id - name: string; // 工作区tab的名称 - dataSourceId: number; // 数据源id - dataSourceName: string; // 数据源名称 - databaseName: string; // 数据库名称 - schemaName?: string; // schema名称q - databaseType: string; // 数据源类型 +export interface IWorkspaceConsoleDDL { + consoleId: string; // 控制台的id 唯一 ddl: string; // 数据源ddl - workspaceTabType: WorkspaceTabType; // 操作类型 - userName?: string; // 用户名,用户的唯一id + userId?: string; // 用户的唯一id } // 工作区console表 -export const workspaceConsoleTable = { - name: 'workspaceConsoleTab', +export const workspaceConsoleDDL = { + name: 'workspaceConsoleDDL', primaryKey: { - keyPath: 'id', + keyPath: 'consoleId', autoIncrement: true, }, column: [ { - name: 'id', - keyPath: 'id', + name: 'consoleId', isIndex: true, + keyPath: 'consoleId', options: { unique: true, }, }, { - name: 'userName', - keyPath: 'userName', - isIndex: true, - options: { - unique: false, - }, - }, - { - name: 'name', - keyPath: 'name', - isIndex: true, - options: { - unique: false, - }, - }, - { - name: 'dataSourceId', - keyPath: 'dataSourceId', - isIndex: true, - options: { - unique: false, - }, - }, - { - name: 'dataSourceName', - keyPath: 'dataSourceName', - isIndex: true, - options: { - unique: false, - }, - }, - { - name: 'databaseName', - keyPath: 'databaseName', - isIndex: true, - options: { - unique: false, - }, - }, - { - name: 'schemaName', - keyPath: 'schemaName', - isIndex: true, - options: { - unique: false, - }, - }, - { - name: 'databaseType', - keyPath: 'databaseType', + name: 'userId', isIndex: true, + keyPath: 'userId', options: { unique: false, }, }, { name: 'ddl', - keyPath: 'ddl', - options: { - unique: false, - }, - }, - { - name: 'workspaceTabType', - keyPath: 'workspaceTabType', isIndex: true, + keyPath: 'ddl', options: { unique: false, }, }, + ], } export const tableList = [ { - tableDetails: workspaceConsoleTable, + tableDetails: workspaceConsoleDDL, } ] diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index cc4f0d7dd..7397c21b7 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -13,10 +13,11 @@ import { useTheme } from '@/hooks'; import { ThemeType, LangType } from '@/constants/'; import styles from './index.less'; import { getLang, setLang } from '@/utils/localStorage'; -import { clearOlderLocalStorage } from '@/utils'; +import { clearOlderLocalStorage, getCookie } from '@/utils'; import registerMessage from './init/registerMessage'; import registerNotification from './init/registerNotification'; import MyNotification from '@/components/MyNotification'; +import indexedDB from '@/indexedDB'; declare global { interface Window { @@ -27,6 +28,7 @@ declare global { _AppThemePack: { [key in string]: string }; _appGatewayParams: IVersionResponse; _notificationApi: any; + _indexedDB: any; } const __APP_VERSION__: string; const __BUILD_TIME__: string; @@ -84,11 +86,17 @@ function AppContainer() { useEffect(() => { const date = new Date('2030-12-30 12:30:00').toUTCString(); document.cookie = `CHAT2DB.LOCALE=${getLang()};Expires=${date}`; - + indexedDB.createDB('chat2db', 1).then((db) => { + window._indexedDB = { + chat2db: db, + }; + }); getUser().then((res) => { if (!res) { navigate('/login'); } + // 向cookie中写入当前用户id + document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; setIsLogin(!!res); }); }, []); @@ -150,7 +158,7 @@ function AppContainer() { setStartSchedule(2); flag++; }) - .catch((error) => { + .catch(() => { setStartSchedule(1); flag++; }); diff --git a/chat2db-client/src/models/connection.ts b/chat2db-client/src/models/connection.ts index b55438d48..d95ffd9f0 100644 --- a/chat2db-client/src/models/connection.ts +++ b/chat2db-client/src/models/connection.ts @@ -45,7 +45,7 @@ const ConnectionModel: IConnectionModelType = { // 设置当前选着的Connection setCurConnection(state, { payload }) { - localStorage.setItem('cur-connection', JSON.stringify(payload)); + localStorage.setItem(`cur-connection`, JSON.stringify(payload)); return { ...state, curConnection: payload }; }, diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index 96ee35661..6cabad645 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,36 +1,31 @@ import React, { useEffect, useState } from 'react'; -import indexedDB from '@/indexedDB/indexedDB'; +import indexedDB from '@/indexedDB'; const App: React.FC = () => { - const [name] = useState(''); - const [user, setUser] = useState<{ name: string; age: number }>({ name: '', age: 0 }); const [db, setDb] = useState(); useEffect(() => { - setUser({ name: 'jack', age: 18 }); indexedDB.createDB('chat2db', 2).then((db) => { setDb(db); }); }, []); - const fuckUser = () => { - const { name: _name, age } = user; - // 一系列操作 - console.log(_name, age); - return true; - }; - const add = () => { - indexedDB.addData(db, 'users', { - id: 8, - a: 1, + indexedDB.addData(db, 'workspaceConsoleDDL', { + userId: '1', + consoleId: '1', + ddl: 'select * from user', }); }; + const deleteFn = () => { + indexedDB.deleteData(db, 'workspaceConsoleDDL', '1'); + }; + return (
    - {name + fuckUser()} +
    ); }; diff --git a/chat2db-client/src/pages/login/index.tsx b/chat2db-client/src/pages/login/index.tsx index 783e3b8b9..dbaf12943 100644 --- a/chat2db-client/src/pages/login/index.tsx +++ b/chat2db-client/src/pages/login/index.tsx @@ -1,12 +1,13 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Button, Form, Input, Tooltip } from 'antd'; -import { userLogin } from '@/service/user'; +import { userLogin, getUser } from '@/service/user'; import LogoImg from '@/assets/logo/logo.png'; import styles from './index.less'; import Setting from '@/blocks/Setting'; import Iconfont from '@/components/Iconfont'; import i18n from '@/i18n'; -import { useNavigate } from 'react-router-dom'; +// import { useNavigate } from 'react-router-dom'; +import { logoutClearSomeLocalStorage, navigate } from '@/utils'; interface IFormData { userName: string; @@ -14,12 +15,21 @@ interface IFormData { } const Login: React.FC = () => { - const navigate = useNavigate(); + useEffect(() => { + logoutClearSomeLocalStorage(); + }, []); + + // const navigate = useNavigate(); const handleLogin = async (formData: IFormData) => { - const res = await userLogin(formData); - if (res) { - navigate('/'); - } + const token = await userLogin(formData); + getUser().then((res) => { + // 向cookie中写入当前用户id + const date = new Date('2030-12-30 12:30:00').toUTCString(); + document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; + if (token && res) { + navigate('/'); + } + }); }; return ( diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 64df74bb4..1026adcc6 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, Fragment } from 'react'; import classnames from 'classnames'; import i18n from '@/i18n'; import ConnectionEdit from '@/components/ConnectionEdit'; @@ -138,12 +138,12 @@ function Connections(props: IProps) { return (
    {allMenuList && - Object.keys(allMenuList).map((t) => { + Object.keys(allMenuList).map((t, i) => { const data = allMenuList[t as ConnectionKind]; if (data.list?.length) { return ( - <> -
    + +
    {data.name}
    ); })} - + ); } })} @@ -296,7 +296,7 @@ function Connections(props: IProps) {
    ); })} -
    diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index e816c38f4..d253afa70 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -58,9 +58,7 @@ const navConfig: INavItem[] = [ }, ]; -const initPageIndex = navConfig.findIndex((t) => `${t.key}` === localStorage.getItem('curPage')); -const activeIndex = initPageIndex > -1 ? initPageIndex : 2; -navConfig[activeIndex].isLoad = true; +// const initPageIndex = navConfig.findIndex((t) => `${t.key}` === localStorage.getItem('curPage')); interface IProps { mainModel: IMainPageType['state']; @@ -73,7 +71,7 @@ function MainPage(props: IProps) { const navigate = useNavigate(); const { mainModel, dispatch } = props; const { curPage } = mainModel; - const [activeNav, setActiveNav] = useState(navConfig[activeIndex]); + const [activeNav, setActiveNav] = useState(null); const [userInfo, setUserInfo] = useState(); useEffect(() => { @@ -103,6 +101,13 @@ function MainPage(props: IProps) { }); }, []); + useEffect(() => { + const initPageIndex = navConfig.findIndex((t) => `${t.key}` === localStorage.getItem('curPage')); + const activeIndex = initPageIndex > -1 ? initPageIndex : 2; + navConfig[activeIndex].isLoad = true; + setActiveNav(navConfig[activeIndex]); + }, []); + useEffect(() => { dispatch({ type: 'connection/fetchConnectionList', @@ -113,6 +118,9 @@ function MainPage(props: IProps) { }, []); useEffect(() => { + if (!activeNav) { + return; + } // activeNav 发生变化,同步到全局状态管理 activeNav.isLoad = true; dispatch({ @@ -130,7 +138,7 @@ function MainPage(props: IProps) { useEffect(() => { // 全局状态curPage发生变化,activeNav 需要同步变化 - if (curPage && curPage !== activeNav.key) { + if (curPage && curPage !== activeNav?.key) { const newActiveNav = navConfig[findObjListValue(navConfig, 'key', curPage)]; setActiveNav(newActiveNav); } @@ -146,7 +154,7 @@ function MainPage(props: IProps) { } const handleLogout = () => { - userLogout().then((res) => { + userLogout().then(() => { setUserInfo(undefined); navigate('/login'); }); @@ -181,7 +189,7 @@ function MainPage(props: IProps) {
  • switchingNav(item)} > @@ -205,7 +213,7 @@ function MainPage(props: IProps) {
    {navConfig.map((item) => { return ( -
  • tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); + + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + singleThreadExecutor.submit(() -> { + try { + Chat2DBContext.putContext(connectInfo); + syncTableVector(request); + } catch (Exception e) { + log.error("sync table vector error", e); + } finally { + Chat2DBContext.removeContext(); + } + log.info("sync table vector finish"); + }); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index 9dd847fdb..078fd8287 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -224,4 +224,12 @@ public abstract class RdbWebConverter { public abstract UpdateSelectResultParam request2param(SelectResultUpdateRequest request); + + public abstract TableMilvusQueryRequest request2request(TableBriefQueryRequest request); + + @Mappings({ + @Mapping(source = "databaseName", target = "database"), + @Mapping(source = "schemaName", target = "schema"), + }) + public abstract TableVectorParam param2param(TableBriefQueryRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index 70e8f6d8e..e296c47c0 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -4,11 +4,10 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.web.api.http.request.KnowledgeRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; +import ai.chat2db.server.web.api.http.request.WhiteListRequest; import ai.chat2db.server.web.api.http.response.*; import com.dtflys.forest.annotation.*; -import java.math.BigDecimal; -import java.util.List; /** * Gateway 的http 服务 @@ -91,4 +90,14 @@ public interface GatewayClientService { */ @Get("/api/client/milvus/schema/search") DataResult schemaVectorSearch(TableSchemaRequest request); + + /** + * check in white list + * + * @param whiteListRequest + * @return + */ + @Get("/api/client/whitelist/check") + DataResult checkInWhite(WhiteListRequest whiteListRequest); + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/WhiteListRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/WhiteListRequest.java new file mode 100644 index 000000000..93d419b0e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/WhiteListRequest.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.web.api.http.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class WhiteListRequest { + + /** + * api key + */ + private String apiKey; + + /** + * 白名单类型,如向量 + * @see ai.chat2db.server.tools.base.enums.WhiteListTypeEnum + */ + private String whiteType; +} From c8a3eef06faf0f49d499b1924f024e3cfd5012a9 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 15 Oct 2023 22:30:25 +0800 Subject: [PATCH 0932/1069] hidden WorkspaceExtend --- chat2db-client/.vscode/settings.json | 1 + chat2db-client/package.json | 3 +- .../src/components/Iconfont/index.less | 8 +- .../src/components/Iconfont/index.tsx | 12 +- .../src/components/Output/index.less | 74 ++-- .../src/components/Output/index.tsx | 102 ++--- .../src/components/SearchResult/index.less | 4 +- chat2db-client/src/pages/main/index.less | 1 - chat2db-client/src/pages/main/index.tsx | 6 +- .../components/WorkspaceExtend/index.less | 17 + .../components/WorkspaceExtend/index.tsx | 55 +++ .../components/WorkspaceRight/index.less | 9 +- .../components/WorkspaceRight/index.tsx | 12 +- chat2db-client/src/service/sql.ts | 2 +- chat2db-client/yarn.lock | 365 +----------------- 15 files changed, 190 insertions(+), 481 deletions(-) create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less create mode 100644 chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 4af7ae187..67c7dc5e5 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -42,6 +42,7 @@ "fulltext", "gtag", "hexi", + "hljs", "icns", "Iconfont", "IIFE", diff --git a/chat2db-client/package.json b/chat2db-client/package.json index b304bb2df..d2d35ed4d 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -46,7 +46,8 @@ "umi": "^4.0.70", "umi-request": "^1.4.0", "uuid": "^9.0.0", - "@dnd-kit/modifiers": "^6.0.1" + "@dnd-kit/modifiers": "^6.0.1", + "highlight.js": "^11.9.0" }, "devDependencies": { "@types/event-source-polyfill": "^1.0.1", diff --git a/chat2db-client/src/components/Iconfont/index.less b/chat2db-client/src/components/Iconfont/index.less index 5ceb0029d..086a0e90a 100644 --- a/chat2db-client/src/components/Iconfont/index.less +++ b/chat2db-client/src/components/Iconfont/index.less @@ -15,12 +15,18 @@ &:hover { background-color: var(--color-hover-bg); .iconfont { - size: var(--icon-size); color: var(--color-primary); } } } +.activeIconBox { + background-color: var(--color-hover-bg); + .iconfont { + color: var(--color-primary); + } +} + .iconfont { font-family: 'iconfont' !important; font-size: var(--icon-size); diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 896290fa4..79bbe6bd6 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React from 'react'; import classnames from 'classnames'; // import desktopStyle from './desktop.less'; import styles from './index.less'; @@ -20,17 +20,19 @@ if (__ENV__ === 'local') { style.appendChild(document.createTextNode(container)); } -interface IProps { +interface IProps extends React.HTMLAttributes { code: string; box?: boolean; boxSize?: number; size?: number; className?: string; classNameBox?: string; + active?: boolean; } const Iconfont = (props: IProps) => { - const { box, boxSize = 32, size = 14, className, classNameBox, ...args } = props; + console.log(active); + const { box, boxSize = 32, size = 14, className, classNameBox, active, ...args } = props; return box ? (
    { '--icon-size': `${size}px`, } as any } - className={classnames(classNameBox, styles.iconBox)} + className={classnames(classNameBox, styles.iconBox, { [styles.activeIconBox]: active })} > {props.code}
    ) : ( {props.code} diff --git a/chat2db-client/src/components/Output/index.less b/chat2db-client/src/components/Output/index.less index 5339e0905..633fdb117 100644 --- a/chat2db-client/src/components/Output/index.less +++ b/chat2db-client/src/components/Output/index.less @@ -1,46 +1,46 @@ @import '../../styles/var.less'; .output { - overflow: auto; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - :global { - .ant-table-body { - border-top: 0px; - border-bottom: 0px; + width: 100%; + height: 100%; + overflow-y: auto; + position: relative; + .outputTitle { + position: sticky; + top: 0; + z-index: 1; + background-color: var(--color-bg-base); + border-bottom: 1px solid var(--color-border); + line-height: 32px; + display: flex; + align-items: center; + font-size: 14px; + padding: 0px 4px; + i { + margin-right: 6px; } - .ant-table-wrapper .ant-table .ant-table-header { - border-radius: 0px; - } - .ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:first-child { - border-top-left-radius: 0px; - } - .ant-table-wrapper .ant-table-container table > thead > tr:first-child > *:last-child { - border-top-right-radius: 0px; - } - .ant-table-header { - border-bottom: 0px; - } - .ant-table { - border-radius: 10px; - border-bottom: 0px; - } - .ant-table-wrapper .ant-table-tbody > tr > td { - padding: 4px 2px; - } - .ant-table-wrapper .ant-table-thead > tr > th { - padding: 8px 4px; - &::before { - display: none; + } + .outputContent { + padding: 0px 10px 10px; + box-sizing: border-box; + .outputItem { + .timeBox { + display: flex; + align-items: center; } - } - .ant-table-wrapper .ant-table-thead > tr > td { - &::before { - display: none; + .iconBox { + transform: rotate(90deg); + margin-right: 4px; + i { + color: var(--color-success); + } } + > div { + line-height: 20px; + } + padding: 2px 0px; } } + .sqlBox { + } } diff --git a/chat2db-client/src/components/Output/index.tsx b/chat2db-client/src/components/Output/index.tsx index 0b7f81554..46bcc5441 100644 --- a/chat2db-client/src/components/Output/index.tsx +++ b/chat2db-client/src/components/Output/index.tsx @@ -1,9 +1,9 @@ import React, { memo, useEffect } from 'react'; import styles from './index.less'; import classnames from 'classnames'; -import { Table } from 'antd'; +import Iconfont from '@/components/Iconfont'; import historyService, { IHistoryRecord } from '@/service/history'; - +import MonacoEditor from '@/components/Console/MonacoEditor'; interface IProps { className?: string; } @@ -12,55 +12,6 @@ export default memo((props) => { const { className } = props; const [dataSource, setDataSource] = React.useState([]); - const columns = [ - { - title: '', - dataIndex: 'No', - width: 50, - key: 'No', - align: 'center', - render: (text: any, record: any, index: number) => { - return {index + 1}; - }, - }, - { - title: '开始时间', - dataIndex: 'time', - key: 'time', - }, - { - title: '数据库/schema', - dataIndex: 'databaseName', - key: 'databaseName', - render: (value: string, record: IHistoryRecord) => { - return {`${record.dataSourceName}/${record.databaseName}`}; - }, - }, - { - title: 'ddl', - dataIndex: 'ddl', - key: 'ddl', - }, - { - title: '状态', - dataIndex: 'status', - key: 'status', - render: (value: boolean) => { - return {value ? '成功' : '失败'}; - }, - }, - { - title: '影响行数', - dataIndex: 'operationRows', - key: 'operationRows', - }, - { - title: '执行耗时', - dataIndex: 'useTime', - key: 'useTime', - }, - ]; - useEffect(() => { getHistoryList(); }, []); @@ -68,7 +19,7 @@ export default memo((props) => { const getHistoryList = () => { historyService .getHistoryList({ - pageNo: 1, + pageNo: 3, pageSize: 100, }) .then((res) => { @@ -78,16 +29,43 @@ export default memo((props) => { return (
    -
    +
    + + 执行记录 +
    +
    + {dataSource.map((item, index) => { + const nameList = [item.dataSourceName, item.databaseName, item.schemaName]; + return ( +
    +
    +
    + +
    + [2023-10-15 14:50:29] + {item.operationRows && {item.operationRows} rows} + {item.useTime && affected in{item.useTime} ms} +
    +
    {nameList.filter((name) => name).join(' > ')}
    +
    + {item.ddl} + {/* */} +
    +
    + ); + })} +
    ); }); diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 976e4ead4..075d2203a 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -55,6 +55,7 @@ justify-content: center; align-items: center; font-size: 12px; + overflow: hidden; } .outputPrefixIcon { @@ -90,7 +91,8 @@ align-items: center; border-top: 1px solid var(--color-border-secondary); background-color: var(--color-bg-subtle); - + overflow: hidden; + .f-single-line(); & > span { margin-right: 16px; } diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index 2cbc4bf15..b74deb358 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -61,7 +61,6 @@ cursor: pointer; .icon { - font-size: 20px; color: var(--custom-color-icon); } diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 379a84a35..906e95b46 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -199,7 +199,11 @@ function MainPage(props: IProps) { })} onClick={() => switchingNav(item)} > - + ); })} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less new file mode 100644 index 000000000..62f750b36 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.less @@ -0,0 +1,17 @@ +@import '../../../../../styles/var.less'; + +.workspaceExtend { + .workspaceExtendBar { + flex-shrink: 0; + width: 38px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + padding: 10px 0px; + } + .workspaceExtendMain { + width: 400px; + border-right: 1px solid var(--color-border); + } +} diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx new file mode 100644 index 000000000..8be43ea70 --- /dev/null +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceExtend/index.tsx @@ -0,0 +1,55 @@ +import React, { memo, useState } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { Popover } from 'antd'; +import Iconfont from '@/components/Iconfont'; +import Output from '@/components/Output'; + +interface IProps { + className?: string; +} + +const data = [ + { + code: 'ai', + title: 'AI', + icon: '\ue8ad', + components:
    ai
    , + }, + { + code: 'executiveLog', + title: '执行记录', + icon: '\ue8ad', + components: , + }, +]; + +export default memo((props) => { + const { className } = props; + const [activeExtend, setActiveExtend] = useState(null); + + const changeExtend = (item: any) => { + if (activeExtend) { + setActiveExtend(null); + return; + } + setActiveExtend(item); + }; + + return ( +
    + {activeExtend &&
    {activeExtend?.components}
    } +
    + {data.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 204442b84..cc2dee425 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -18,15 +18,10 @@ display: flex; } -.rightBar { +.workspaceExtend { flex-shrink: 0; - width: 38px; border-left: 1px solid var(--color-border); display: flex; - flex-direction: column; - align-items: center; - justify-content: space-between; - padding: 10px 0px; } .tabs { @@ -36,7 +31,7 @@ .tabBox { flex: 1; - width: 100%; + width: 0px; height: 100%; } 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 443288964..421b1e985 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -7,6 +7,7 @@ import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType, WorkspaceTabType, wor import historyService from '@/service/history'; import sqlService from '@/service/sql'; import TabsNew, { ITabItem } from '@/components/TabsNew'; +import WorkspaceExtend from '../WorkspaceExtend'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; @@ -18,7 +19,7 @@ import { IAIState } from '@/models/ai'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; import { IWorkspaceTab } from '@/typings'; -import { Button } from 'antd'; +import { Button, Popover } from 'antd'; import { registerIntelliSenseField, registerIntelliSenseKeyword, @@ -588,14 +589,7 @@ const WorkspaceRight = memo((props: IProps) => { // lastTabCannotClosed /> -
    -
    - -
    -
    - -
    -
    + {/* */} ); diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 662b18c52..df9d3f966 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -44,7 +44,7 @@ export interface IConnectConsoleParams { databaseName: string; } -const getTableList = createRequest>('/api/rdb/ddl/list', { method: 'get' }); +const getTableList = createRequest>('/api/rdb/table/list', { method: 'get' }); const executeSql = createRequest('/api/rdb/dml/execute', { method: 'post' }); diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 961aa746d..32cf86e54 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1560,21 +1560,6 @@ dependencies: tslib "^2.0.0" -"@electron/get@^2.0.0": - version "2.0.3" - resolved "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" - integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== - dependencies: - debug "^4.1.1" - env-paths "^2.2.0" - fs-extra "^8.1.0" - got "^11.8.5" - progress "^2.0.3" - semver "^6.2.0" - sumchecker "^3.0.1" - optionalDependencies: - global-agent "^3.0.0" - "@electron/universal@1.2.1": version "1.2.1" resolved "https://registry.npmmirror.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339" @@ -2165,11 +2150,6 @@ resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== -"@sindresorhus/is@^4.0.0": - version "4.6.0" - resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" - integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== - "@stylelint/postcss-css-in-js@^0.38.0": version "0.38.0" resolved "https://registry.npmmirror.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.38.0.tgz#eabb061df932744db766f11a153ae1c465b6263c" @@ -2269,13 +2249,6 @@ deepmerge "^4.2.2" svgo "^2.8.0" -"@szmarczak/http-timer@^4.0.5": - version "4.0.6" - resolved "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" - integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== - dependencies: - defer-to-connect "^2.0.0" - "@tanstack/match-sorter-utils@^8.7.0": version "8.8.4" resolved "https://registry.npmmirror.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" @@ -2348,16 +2321,6 @@ dependencies: "@babel/types" "^7.20.7" -"@types/cacheable-request@^6.0.1": - version "6.0.3" - resolved "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" - integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== - dependencies: - "@types/http-cache-semantics" "*" - "@types/keyv" "^3.1.4" - "@types/node" "*" - "@types/responselike" "^1.0.0" - "@types/classnames@^2.2.9": version "2.3.1" resolved "https://registry.npmmirror.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" @@ -2417,11 +2380,6 @@ resolved "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== -"@types/http-cache-semantics@*": - version "4.0.2" - resolved "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.2.tgz#abe102d06ccda1efdf0ed98c10ccf7f36a785a41" - integrity sha512-FD+nQWA2zJjh4L9+pFXqWOi0Hs1ryBCfI+985NjluQ1p8EYtoLvjLOKidXBtZ4/IcxDX4o8/E8qDS3540tNliw== - "@types/invariant@^2.2.31": version "2.2.35" resolved "https://registry.npmmirror.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" @@ -2466,13 +2424,6 @@ resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== -"@types/keyv@^3.1.4": - version "3.1.4" - resolved "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" - integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== - dependencies: - "@types/node" "*" - "@types/lodash@^4.14.195": version "4.14.195" resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" @@ -2493,11 +2444,6 @@ resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== -"@types/node@^16.11.26": - version "16.18.58" - resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.58.tgz#bf66f63983104ed57c754f4e84ccaf16f8235adb" - integrity sha512-YGncyA25/MaVtQkjWW9r0EFBukZ+JulsLcVZBlGUfIb96OBMjkoRWwQo5IEWJ8Fj06Go3GHw+bjYDitv6BaGsA== - "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2532,13 +2478,6 @@ "@types/scheduler" "*" csstype "^3.0.2" -"@types/responselike@^1.0.0": - version "1.0.1" - resolved "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.1.tgz#1dd57e54509b3b95c7958e52709567077019d65d" - integrity sha512-TiGnitEDxj2X0j+98Eqk5lv/Cij8oHd32bU4D/Yw6AOq7vvTk0gSD2GPj0G/HkvhMoVsdlhYF4yqqlyPBTM6Sg== - dependencies: - "@types/node" "*" - "@types/scheduler@*": version "0.16.3" resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" @@ -2593,13 +2532,6 @@ dependencies: "@types/yargs-parser" "*" -"@types/yauzl@^2.9.1": - version "2.10.1" - resolved "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.1.tgz#4e8f299f0934d60f36c74f59cb5a8483fd786691" - integrity sha512-CHzgNU3qYBnp/O4S3yv2tXPlvMTq0YWSTVg2/JYLqWZGHwwgJGAwd00poay/11asPq8wLFwHzubyInqHIFmmiw== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz#deee67e399f2cb6b4608c935777110e509d8018c" @@ -3795,11 +3727,6 @@ boolbase@^1.0.0: resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -boolean@^3.0.1: - version "3.2.0" - resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" - integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== - bplist-parser@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" @@ -3925,11 +3852,6 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== - buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -4010,24 +3932,6 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" -cacheable-lookup@^5.0.3: - version "5.0.4" - resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" - integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== - -cacheable-request@^7.0.2: - version "7.0.4" - resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" - integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== - dependencies: - clone-response "^1.0.2" - get-stream "^5.1.0" - http-cache-semantics "^4.0.0" - keyv "^4.0.0" - lowercase-keys "^2.0.0" - normalize-url "^6.0.1" - responselike "^2.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -4172,13 +4076,6 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" -clone-response@^1.0.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" - integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== - dependencies: - mimic-response "^1.0.0" - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -4594,13 +4491,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== -decompress-response@^6.0.0: - version "6.0.0" - resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" - integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== - dependencies: - mimic-response "^3.1.0" - deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4629,11 +4519,6 @@ default-browser@^4.0.0: execa "^7.1.1" titleize "^3.0.0" -defer-to-connect@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" - integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== - define-data-property@^1.0.1: version "1.1.0" resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" @@ -5005,15 +4890,6 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== -electron@^22.3.0: - version "22.3.27" - resolved "https://registry.npmmirror.com/electron/-/electron-22.3.27.tgz#b77451a53f0c502e7559cceac28ac58eb289eef8" - integrity sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A== - dependencies: - "@electron/get" "^2.0.0" - "@types/node" "^16.11.26" - extract-zip "^2.0.1" - elliptic@^6.5.3: version "6.5.4" resolved "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -5044,7 +4920,7 @@ encoding@^0.1.11: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.1.0, end-of-stream@^1.4.1: +end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -5078,11 +4954,6 @@ entities@^4.4.0: resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -env-paths@^2.2.0: - version "2.2.1" - resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== - errno@^0.1.1: version "0.1.8" resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -5268,11 +5139,6 @@ es5-imcompatible-versions@^0.1.78: resolved "https://registry.npmmirror.com/es5-imcompatible-versions/-/es5-imcompatible-versions-0.1.86.tgz#e9583ad3a7a93c1b13835fb804a3bbd05e30a662" integrity sha512-Lbrsn5bCL4iVMBdundiFVNIKlnnoBiIMrjtLRe1Snt92s60WHotw83S2ijp5ioqe6pDil3iBPY634VDwBcb1rg== -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -5666,17 +5532,6 @@ ext@^1.1.2: dependencies: type "^2.7.2" -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - extsprintf@^1.2.0: version "1.4.1" resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" @@ -5754,13 +5609,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -5895,15 +5743,6 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^8.1.0: - version "8.1.0" - resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -6001,13 +5840,6 @@ get-stdin@^9.0.0: resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== -get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -6071,18 +5903,6 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" -global-agent@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" - integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== - dependencies: - boolean "^3.0.1" - es6-error "^4.1.1" - matcher "^3.0.0" - roarr "^2.15.3" - semver "^7.3.2" - serialize-error "^7.0.1" - global@^4.3.2: version "4.4.0" resolved "https://registry.npmmirror.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" @@ -6103,7 +5923,7 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.1, globalthis@^1.0.3: +globalthis@^1.0.3: version "1.0.3" resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== @@ -6140,23 +5960,6 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -got@^11.8.5: - version "11.8.6" - resolved "https://registry.npmmirror.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" - integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== - dependencies: - "@sindresorhus/is" "^4.0.0" - "@szmarczak/http-timer" "^4.0.5" - "@types/cacheable-request" "^6.0.1" - "@types/responselike" "^1.0.0" - cacheable-lookup "^5.0.3" - cacheable-request "^7.0.2" - decompress-response "^6.0.0" - http2-wrapper "^1.0.0-beta.5.2" - lowercase-keys "^2.0.0" - p-cancelable "^2.0.0" - responselike "^2.0.0" - graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -6250,6 +6053,11 @@ he@^1.2.0: resolved "https://registry.npmmirror.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +highlight.js@^11.9.0: + version "11.9.0" + resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.9.0.tgz#04ab9ee43b52a41a047432c8103e2158a1b8b5b0" + integrity sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw== + history@5.3.0, history@^5.2.0: version "5.3.0" resolved "https://registry.npmmirror.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" @@ -6334,11 +6142,6 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" -http-cache-semantics@^4.0.0: - version "4.1.1" - resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" - integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== - http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6353,14 +6156,6 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" -http2-wrapper@^1.0.0-beta.5.2: - version "1.0.3" - resolved "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" - integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== - dependencies: - quick-lru "^5.1.1" - resolve-alpn "^1.0.0" - https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -7034,11 +6829,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json2mq@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" @@ -7058,13 +6848,6 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.2: resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -7094,13 +6877,6 @@ keyboardevents-areequal@^0.2.1: resolved "https://registry.npmmirror.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== -keyv@^4.0.0: - version "4.5.4" - resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" - integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== - dependencies: - json-buffer "3.0.1" - keyv@^4.5.3: version "4.5.3" resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" @@ -7276,11 +7052,6 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lowercase-keys@^2.0.0: - version "2.0.0" - resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" - integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7315,13 +7086,6 @@ markdown-it-link-attributes@^4.0.1: resolved "https://registry.npmmirror.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz#25751f2cf74fd91f0a35ba7b3247fa45f2056d88" integrity sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ== -matcher@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" - integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== - dependencies: - escape-string-regexp "^4.0.0" - md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -7406,16 +7170,6 @@ mimic-fn@^4.0.0: resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== - -mimic-response@^3.1.0: - version "3.1.0" - resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" - integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== - min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -7668,11 +7422,6 @@ normalize-range@^0.1.2: resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -7794,7 +7543,7 @@ on-exit-leak-free@^0.2.0: resolved "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -7851,11 +7600,6 @@ os-browserify@^0.3.0: resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== -p-cancelable@^2.0.0: - version "2.1.1" - resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" - integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== - p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -8001,11 +7745,6 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== - picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -8471,11 +8210,6 @@ process@^0.11.10: resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -8507,14 +8241,6 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -8572,11 +8298,6 @@ quick-format-unescaped@^4.0.3: resolved "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" @@ -9298,11 +9019,6 @@ resize-observer-polyfill@^1.5.1: resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== -resolve-alpn@^1.0.0: - version "1.2.1" - resolved "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" - integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -9354,13 +9070,6 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -responselike@^2.0.0: - version "2.0.1" - resolved "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" - integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== - dependencies: - lowercase-keys "^2.0.0" - ret@~0.1.10: version "0.1.15" resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -9386,18 +9095,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -roarr@^2.15.3: - version "2.15.4" - resolved "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" - integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== - dependencies: - boolean "^3.0.1" - detect-node "^2.0.4" - globalthis "^1.0.1" - json-stringify-safe "^5.0.1" - semver-compare "^1.0.0" - sprintf-js "^1.1.2" - rollup-plugin-visualizer@5.9.0: version "5.9.0" resolved "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b" @@ -9537,22 +9234,17 @@ select-hose@^2.0.0: resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -semver-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" - integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== - semver@^5.6.0, semver@^5.7.2: version "5.7.2" resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: +semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: +semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -9564,13 +9256,6 @@ semver@~7.0.0: resolved "https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== -serialize-error@^7.0.1: - version "7.0.1" - resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" - integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== - dependencies: - type-fest "^0.13.1" - set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -9778,11 +9463,6 @@ split2@^4.0.0: resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== -sprintf-js@^1.1.2: - version "1.1.3" - resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" - integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -10062,13 +9742,6 @@ sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" -sumchecker@^3.0.1: - version "3.0.1" - resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" - integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== - dependencies: - debug "^4.1.0" - superjson@^1.10.0: version "1.13.1" resolved "https://registry.npmmirror.com/superjson/-/superjson-1.13.1.tgz#a0b6ab5d22876f6207fcb9d08b0cb2acad8ee5cd" @@ -10383,11 +10056,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.13.1: - version "0.13.1" - resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" - integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== - type-fest@^0.20.2: version "0.20.2" resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -10511,11 +10179,6 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== -universalify@^0.1.0: - version "0.1.2" - resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - universalify@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -10811,14 +10474,6 @@ yargs@^17.5.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 219c6377067f1847c0bae5dcea758f9ca078430e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 16 Oct 2023 09:30:27 +0800 Subject: [PATCH 0933/1069] iconfont --- .../src/components/Iconfont/index.tsx | 6 +- .../workspace/components/SaveList/index.tsx | 6 -- .../workspace/components/TableList/index.tsx | 62 +++++++++---------- 3 files changed, 34 insertions(+), 40 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 79bbe6bd6..517fc0b89 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_vrz0j0emjjs.woff2?t=1697347643380') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_vrz0j0emjjs.woff?t=1697347643380') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_vrz0j0emjjs.ttf?t=1697347643380') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_2y91mha361m.woff2?t=1697419167687') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_2y91mha361m.woff?t=1697419167687') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_2y91mha361m.ttf?t=1697419167687') format('truetype'); } `; const style = document.createElement('style'); 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 c82a41321..4277902dd 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -11,12 +11,6 @@ import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; -// interface IProps { -// className?: string; -// workspaceModel: IWorkspaceModelType['state']; -// dispatch: any; -// } - const dvaModel = connect(({ workspace }: { workspace: IWorkspaceModelType }) => ({ workspaceModel: workspace, })); 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 d4b7106c6..38605a080 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -79,7 +79,7 @@ const TableList = dvaModel((props: any) => { { label: (
    - +
    {i18n('common.button.createConsole')}
    ), @@ -88,36 +88,6 @@ const TableList = dvaModel((props: any) => { addConsole(); }, }, - { - label: ( - { - if (Array.isArray(file)) return Promise.resolve(false); - - const reader = new FileReader(); - - reader.onload = function (event) { - const sqlContent = (event.target?.result ?? '') as string; - addConsole(sqlContent); - }; - - reader.readAsText(file); - return Promise.resolve(true); - }} - > -
    - -
    {i18n('common.button.import')}
    -
    -
    - ), - key: 'importSQL', - onClick: () => { - // addConsole(); - }, - }, { label: (
    @@ -209,6 +179,36 @@ const TableList = dvaModel((props: any) => { }, ], }, + { + label: ( + { + if (Array.isArray(file)) return Promise.resolve(false); + + const reader = new FileReader(); + + reader.onload = function (event) { + const sqlContent = (event.target?.result ?? '') as string; + addConsole(sqlContent); + }; + + reader.readAsText(file); + return Promise.resolve(true); + }} + > +
    + +
    {i18n('common.button.import')}
    +
    +
    + ), + key: 'importSQL', + onClick: () => { + // addConsole(); + }, + }, ]; const dataSourceFormConfig = dataSourceFormConfigs.find((item) => item.type === curWorkspaceParams.databaseType); From da05071a3c1e5cfd3bfa37b069002b3f32f9a52e Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 10:35:29 +0800 Subject: [PATCH 0934/1069] feat: Optimize user use --- chat2db-client/src/components/Console/MonacoEditor/index.tsx | 2 +- chat2db-client/src/components/Iconfont/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index d41477237..b815ac375 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -140,7 +140,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { onSave?.(value || ''); }); - editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, () => { + editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, () => { const value = getCurrentSelectContent(); onExecute?.(value); }); diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 517fc0b89..0745323cd 100644 --- a/chat2db-client/src/components/Iconfont/index.tsx +++ b/chat2db-client/src/components/Iconfont/index.tsx @@ -31,7 +31,7 @@ interface IProps extends React.HTMLAttributes { } const Iconfont = (props: IProps) => { - console.log(active); + // console.log(active); const { box, boxSize = 32, size = 14, className, classNameBox, active, ...args } = props; return box ? (
    Date: Mon, 16 Oct 2023 10:35:59 +0800 Subject: [PATCH 0935/1069] style --- .../src/components/Console/MonacoEditor/index.tsx | 8 ++++++-- chat2db-client/src/components/SearchResult/index.less | 1 + chat2db-client/src/components/TabsNew/index.less | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index d41477237..d1ed4f7cb 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -145,6 +145,11 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { onExecute?.(value); }); + editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyR, () => { + const value = getCurrentSelectContent(); + onExecute?.(value); + }); + // 注册快捷键command+shift+L新建console editorRef.current.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyL, () => { // onShortcutKeyCallback?.(new KeyboardEvent('keydown', { ctrlKey: true, shiftKey: true, keyCode: 76 })); @@ -211,8 +216,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const createAction = (editor: IEditorIns) => { // 用于控制切换该菜单键的显示 - // const shouldShowSqlRunnerAction = editor.createContextKey('shouldShowSqlRunnerAction', true); - + editor.createContextKey('shouldShowSqlRunnerAction', true); if (!props.addAction || !props.addAction.length) { return; } diff --git a/chat2db-client/src/components/SearchResult/index.less b/chat2db-client/src/components/SearchResult/index.less index 075d2203a..67208098c 100644 --- a/chat2db-client/src/components/SearchResult/index.less +++ b/chat2db-client/src/components/SearchResult/index.less @@ -92,6 +92,7 @@ border-top: 1px solid var(--color-border-secondary); background-color: var(--color-bg-subtle); overflow: hidden; + flex-shrink: 0; .f-single-line(); & > span { margin-right: 16px; diff --git a/chat2db-client/src/components/TabsNew/index.less b/chat2db-client/src/components/TabsNew/index.less index 2b981fb32..32fa718db 100644 --- a/chat2db-client/src/components/TabsNew/index.less +++ b/chat2db-client/src/components/TabsNew/index.less @@ -182,6 +182,7 @@ font-size: 12px; font-weight: 400; background-color: var(--color-bg-subtle); + color: var(--color-text); input:focus { outline: none; } From a37625af6e5314edc37389edc689c64cf5c51288 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 10:48:25 +0800 Subject: [PATCH 0936/1069] fix: fix bug --- .../src/components/Console/MonacoEditor/index.tsx | 2 +- chat2db-client/src/components/ImportBlock/index.tsx | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index b815ac375..dbc178759 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -211,7 +211,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { const createAction = (editor: IEditorIns) => { // 用于控制切换该菜单键的显示 - // const shouldShowSqlRunnerAction = editor.createContextKey('shouldShowSqlRunnerAction', true); + editor.createContextKey('shouldShowSqlRunnerAction', true); if (!props.addAction || !props.addAction.length) { return; diff --git a/chat2db-client/src/components/ImportBlock/index.tsx b/chat2db-client/src/components/ImportBlock/index.tsx index d0f7634fe..66e99eabc 100644 --- a/chat2db-client/src/components/ImportBlock/index.tsx +++ b/chat2db-client/src/components/ImportBlock/index.tsx @@ -38,12 +38,8 @@ function ImportBlock(props: IImportBlockProps) { }; return ( -
    { - setOpen(true); - }} - > - {children} +
    +
    setOpen(true)}>{children}
    Date: Mon, 16 Oct 2023 11:34:26 +0800 Subject: [PATCH 0937/1069] user update --- .../chat2db/server/start/controller/oauth/OauthController.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java index 0ac4bc835..3d484cfeb 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/oauth/OauthController.java @@ -110,9 +110,6 @@ private LoginUser getLoginUser() { if (loginUser == null) { return null; } - if (RoleCodeEnum.DESKTOP.getCode().equals(loginUser.getRoleCode())) { - return null; - } return loginUser; } From 0f327c989217a832844d5f0d06e719c213920278 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 16 Oct 2023 12:14:01 +0800 Subject: [PATCH 0938/1069] fix: login --- chat2db-client/.vscode/settings.json | 2 +- chat2db-client/src/pages/demo/index.tsx | 12 ++++++++++++ chat2db-client/src/pages/main/index.tsx | 4 ++-- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 67c7dc5e5..2ff54c435 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -1,6 +1,6 @@ { "workbench.colorTheme": "One Dark Pro", - "workbench.iconTheme": "material-icon-theme", + "workbench.iconTheme": "vscode-icons-mac", "editor.fontSize": 14, "editor.tabSize": 2, // 自动格式化 diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index 6cabad645..b033dc5e4 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,6 +1,18 @@ import React, { useEffect, useState } from 'react'; import indexedDB from '@/indexedDB'; +export enum CustomerTypeEnum { + visitor = 'visitor', + person = 'person', +} + +export enum CustomerTypeEnum2 { + visitor = 'visitor', + person = 'person', +} + +export type CustomerType1 = CustomerTypeEnum | CustomerTypeEnum2; + const App: React.FC = () => { const [db, setDb] = useState(); diff --git a/chat2db-client/src/pages/main/index.tsx b/chat2db-client/src/pages/main/index.tsx index 906e95b46..829a95fdb 100644 --- a/chat2db-client/src/pages/main/index.tsx +++ b/chat2db-client/src/pages/main/index.tsx @@ -10,7 +10,7 @@ import BrandLogo from '@/components/BrandLogo'; import { findObjListValue } from '@/utils'; import { getUser, userLogout } from '@/service/user'; import { INavItem } from '@/typings/main'; -import { ILoginUser } from '@/typings/user'; +import { ILoginUser, IRole } from '@/typings/user'; import i18n from '@/i18n'; // ----- model ----- @@ -217,7 +217,7 @@ function MainPage(props: IProps) { }} /> */} - {userInfo ? renderUser() : null} + {userInfo?.roleCode !== IRole.DESKTOP ? renderUser() : null}
    From 73419d30cfed011ea85f7f11030023f5c23a9247 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Mon, 16 Oct 2023 13:29:36 +0800 Subject: [PATCH 0939/1069] gateway update --- .../server/web/api/http/GatewayClientService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index e296c47c0..e13c23913 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -62,7 +62,7 @@ public interface GatewayClientService { * @return */ @Post("/api/client/milvus/knowledge/save") - ActionResult knowledgeVectorSave(KnowledgeRequest request); + ActionResult knowledgeVectorSave(@Query KnowledgeRequest request); /** * save table schema vector @@ -71,7 +71,7 @@ public interface GatewayClientService { * @return */ @Post("/api/client/milvus/schema/save") - ActionResult schemaVectorSave(TableSchemaRequest request); + ActionResult schemaVectorSave(@Query TableSchemaRequest request); /** * save knowledge vector @@ -80,7 +80,7 @@ public interface GatewayClientService { * @return */ @Get("/api/client/milvus/knowledge/search") - DataResult knowledgeVectorSearch(KnowledgeRequest searchVectors); + DataResult knowledgeVectorSearch(@Query KnowledgeRequest searchVectors); /** * save table schema vector @@ -89,7 +89,7 @@ public interface GatewayClientService { * @return */ @Get("/api/client/milvus/schema/search") - DataResult schemaVectorSearch(TableSchemaRequest request); + DataResult schemaVectorSearch(@Query TableSchemaRequest request); /** * check in white list @@ -98,6 +98,6 @@ public interface GatewayClientService { * @return */ @Get("/api/client/whitelist/check") - DataResult checkInWhite(WhiteListRequest whiteListRequest); + DataResult checkInWhite(@Query WhiteListRequest whiteListRequest); } From 87fbc23ae85348867467a37e93bfd3ac1cec31ef Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 13:44:19 +0800 Subject: [PATCH 0940/1069] refactor: Remane buildin h2 sql name --- .../{V2_1_3__TableVector.sql => V2_1_5__TableVector.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename chat2db-server/chat2db-server-start/src/main/resources/db/migration/{V2_1_3__TableVector.sql => V2_1_5__TableVector.sql} (100%) diff --git a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableVector.sql b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_5__TableVector.sql similarity index 100% rename from chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_3__TableVector.sql rename to chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_5__TableVector.sql From 15bb5277f1904ffe36221bf56ad67961ebe9134c Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 14:24:14 +0800 Subject: [PATCH 0941/1069] feat: Optimize the front-end logic of the automatic synchronization table structure --- .../components/Console/ChatInput/index.tsx | 11 ++++- .../src/components/Console/index.tsx | 26 +++++++++--- chat2db-client/src/i18n/zh-cn/chat.ts | 2 +- chat2db-client/src/models/ai.ts | 41 +++++++++++++++++-- chat2db-client/src/service/config.ts | 5 +++ 5 files changed, 73 insertions(+), 12 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 05006d9e7..0ec7a9813 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -8,6 +8,11 @@ import { WarningOutlined } from '@ant-design/icons'; import { AiSqlSourceType, IRemainingUse } from '@/typings/ai'; import { WECHAT_MP_URL } from '@/constants/social'; +export const enum SyncModelType { + AUTO = 0, + MANUAL = 1, +} + interface IProps { value?: string; result?: string; @@ -18,6 +23,7 @@ interface IProps { aiType: AiSqlSourceType; remainingBtnLoading: boolean; disabled?: boolean; + defaultSelectedSyncModel?: SyncModelType; onPressEnter: (value: string) => void; onSelectTableSyncModel: (model: number) => void; onSelectTables?: (tables: string[]) => void; @@ -44,13 +50,14 @@ const ChatInput = (props: IProps) => { return (
    onSelectTableSyncModel(v.target.value)} value={syncTableModel} style={{ marginBottom: '8px' }} > - 自动 - 手动 + 自动 + 手动 {syncTableModel === 0 ? ( diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 1996cf0e7..dd179fa16 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -3,7 +3,7 @@ import { connect } from 'umi'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; import { Button, Spin, message, Drawer, Modal } from 'antd'; -import ChatInput from './ChatInput'; +import ChatInput, { SyncModelType } from './ChatInput'; import Editor, { IEditorOptions, IExportRefFunction, IRangeType } from './MonacoEditor'; import historyServer from '@/service/history'; import aiServer from '@/service/ai'; @@ -21,7 +21,7 @@ import configService from '@/service/config'; // import NewEditor from './NewMonacoEditor'; import styles from './index.less'; import indexedDB from '@/indexedDB'; -import ImportBlock from '../ImportBlock'; +import { isEmpty } from 'lodash'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -411,8 +411,6 @@ function Console(props: IProps) { setPopularizeModal(true); }; - const handleImportSQL = () => {}; - /** * 格式化sql */ @@ -428,6 +426,20 @@ function Console(props: IProps) { }); }; + const handleSelectTableSyncModel = () => { + const syncModel: SyncModelType | null = Number(localStorage.getItem('syncTableModel')) ?? null; + const hasAiAccess = aiModel.hasWhite; + + if (!hasAiAccess) { + return SyncModelType.MANUAL; + } + + if (isEmpty(syncModel)) { + return SyncModelType.MANUAL; + } + return syncModel; + }; + return (
    @@ -453,7 +465,11 @@ function Console(props: IProps) { }} onClickRemainBtn={handleClickRemainBtn} syncTableModel={syncTableModel} - onSelectTableSyncModel={(model: number) => setSyncTableModel(model)} + defaultSelectedSyncModel={handleSelectTableSyncModel()} + onSelectTableSyncModel={(model: number) => { + setSyncTableModel(model); + localStorage.setItem('syncTableModel', String(model)); + }} /> )} {/*
    {chatContent.current}
    */} diff --git a/chat2db-client/src/i18n/zh-cn/chat.ts b/chat2db-client/src/i18n/zh-cn/chat.ts index d76f6745a..0cd9d0e01 100644 --- a/chat2db-client/src/i18n/zh-cn/chat.ts +++ b/chat2db-client/src/i18n/zh-cn/chat.ts @@ -3,7 +3,7 @@ export default { 'chat.input.tableSelect.placeholder': '请选择表', 'chat.input.tableSelect.error.TooManyTable': '最多选择8张表', 'chat.input.remain.dialog.tips': '关注公众号,发送"推广"获取更多体验次数', - 'chat.input.syncTable.tips': '自动同步所有表结构给AI上下文', + 'chat.input.syncTable.tips': '自动同步所有表结构给AI上下文(在群内联系群主,申请Chat2DBAI白名单后,仅在Chat2DBAI模型下可用)', 'chat.input.remain.tooltip': '手动选中的表的结构将会同步给AI上下文', 'chat.input.syncTable.tempTips': '🎉上线:自动同步所有表结构到AI上下文', }; diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index 9796cde6a..4d0a5c177 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -9,6 +9,7 @@ import i18n from '@/i18n'; export interface IAIState { aiConfig: IAiConfig; remainingUse?: IRemainingUse; + hasWhite: boolean; } export interface IAIModelType { @@ -17,8 +18,10 @@ export interface IAIModelType { reducers: { setRemainUse: Reducer; setAiConfig: Reducer; + setAiWithWhite: Reducer; }; effects: { + updateAiWithWhite: Effect; getAiSystemConfig: Effect; setAiSystemConfig: Effect; fetchRemainingUse: Effect; @@ -32,6 +35,7 @@ const AIModel: IAIModelType = { aiConfig: { aiSqlSource: AiSqlSourceType.CHAT2DBAI, }, + hasWhite: false, }, reducers: { setAiConfig(state, { payload }) { @@ -46,9 +50,22 @@ const AIModel: IAIModelType = { remainingUse: payload, }; }, + setAiWithWhite(state, { payload }) { + return { + ...state, + hasWhite: payload, + }; + }, }, effects: { - *getAiSystemConfig({ }, { put }) { + *updateAiWithWhite({ payload }: { type: any; payload?: { apiKey: string } }, { put }: EffectsCommandMap) { + const hasAiAccess = yield configService.getAiWhiteAccess({ apiKey: payload?.apiKey ?? '' }); + yield put({ + type: 'setAiWithWhite', + payload: hasAiAccess, + }); + }, + *getAiSystemConfig(_, { put }) { const res = (yield configService.getAiSystemConfig({})) as IAiConfig; yield put({ type: 'setAiConfig', @@ -59,12 +76,15 @@ const AIModel: IAIModelType = { type: 'fetchRemainingUse', payload: { apiKey: res.apiKey }, }); + yield put({ + type: 'updateAiWithWhite', + payload: { apiKey: res.apiKey }, + }); } }, // *setAiSystemConfig({ payload }: { type: string; payload: { aiConfig: IAiConfig } }, { put }: EffectsCommandMap) { *setAiSystemConfig({ payload }: { type: any; payload?: IAiConfig }, { put }: EffectsCommandMap) { const aiConfig = payload; - const { aiSqlSource, apiKey } = aiConfig || {}; try { (yield configService.setAiSystemConfig(aiConfig!)) as void; message.success(i18n('common.text.submittedSuccessfully')); @@ -77,8 +97,21 @@ const AIModel: IAIModelType = { type: 'fetchRemainingUse', payload: { apiKey: aiConfig?.apiKey }, }); - } catch (error) { } + + if (aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { + yield put({ + type: 'updateAiWithWhite', + payload: { apiKey: aiConfig?.apiKey }, + }); + } else { + yield put({ + type: 'setAiWithWhite', + payload: false, + }); + } + } catch (error) {} }, + *fetchRemainingUse({ payload }: { type: any; payload?: { apiKey?: string } }, { put, select }) { const currentState = (yield select((state: any) => state.ai)) as IAIState; const { apiKey } = payload || {}; @@ -96,7 +129,7 @@ const AIModel: IAIModelType = { payload: res, }); return res; - } catch { } + } catch {} }, }, }; diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index b628ef5bb..77b7b1f4b 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -18,9 +18,14 @@ const setAiSystemConfig = createRequest('/api/config/system_con method: 'post', }); +const getAiWhiteAccess = createRequest<{ apiKey: string }, boolean>('/api/ai/embedding/white/check', { + method: 'get', +}); + export default { getSystemConfig, setSystemConfig, getAiSystemConfig, setAiSystemConfig, + getAiWhiteAccess }; From 400792466e22981607c3fae67c463276e68c5cc0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 14:38:06 +0800 Subject: [PATCH 0942/1069] fix: fix bug --- .../src/components/Console/ChatInput/index.tsx | 4 +--- chat2db-client/src/components/Console/index.tsx | 16 ++++++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 0ec7a9813..855443526 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -23,7 +23,6 @@ interface IProps { aiType: AiSqlSourceType; remainingBtnLoading: boolean; disabled?: boolean; - defaultSelectedSyncModel?: SyncModelType; onPressEnter: (value: string) => void; onSelectTableSyncModel: (model: number) => void; onSelectTables?: (tables: string[]) => void; @@ -50,7 +49,6 @@ const ChatInput = (props: IProps) => { return (
    onSelectTableSyncModel(v.target.value)} value={syncTableModel} style={{ marginBottom: '8px' }} @@ -102,7 +100,7 @@ const ChatInput = (props: IProps) => { title={{i18n('chat.input.syncTable.tempTips')}} defaultOpen={!hasBubble} color={window._AppThemePack.colorBgBase} - trigger={'click'} + trigger={'contextMenu'} onOpenChange={() => { localStorage.setItem('syncTableBubble', 'true'); }} diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index dd179fa16..692c74de4 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -21,7 +21,7 @@ import configService from '@/service/config'; // import NewEditor from './NewMonacoEditor'; import styles from './index.less'; import indexedDB from '@/indexedDB'; -import { isEmpty } from 'lodash'; +import { isEmpty, set } from 'lodash'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -114,6 +114,10 @@ function Console(props: IProps) { [aiModel.aiConfig?.aiSqlSource], ); + useEffect(() => { + handleSelectTableSyncModel(); + }, [aiModel.hasWhite, localStorage.getItem('syncTableModel')]); + useEffect(() => { if (appendValue) { editorRef?.current?.setValue(appendValue.text, appendValue.range); @@ -430,14 +434,11 @@ function Console(props: IProps) { const syncModel: SyncModelType | null = Number(localStorage.getItem('syncTableModel')) ?? null; const hasAiAccess = aiModel.hasWhite; - if (!hasAiAccess) { - return SyncModelType.MANUAL; + if (!hasAiAccess || isEmpty(syncModel)) { + setSyncTableModel(SyncModelType.MANUAL); } - if (isEmpty(syncModel)) { - return SyncModelType.MANUAL; - } - return syncModel; + setSyncTableModel(syncModel); }; return ( @@ -465,7 +466,6 @@ function Console(props: IProps) { }} onClickRemainBtn={handleClickRemainBtn} syncTableModel={syncTableModel} - defaultSelectedSyncModel={handleSelectTableSyncModel()} onSelectTableSyncModel={(model: number) => { setSyncTableModel(model); localStorage.setItem('syncTableModel', String(model)); From df651e12cf5233b3fee53be2cd4c0fe4faa9227f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 16 Oct 2023 14:40:16 +0800 Subject: [PATCH 0943/1069] fix:login issues --- .../SearchResult/TableBox/index.less | 9 +++- .../src/components/SearchResult/index.tsx | 2 +- chat2db-client/src/layouts/index.tsx | 42 +++++++++++-------- .../workspace/components/SaveList/index.less | 5 ++- .../workspace/components/SaveList/index.tsx | 25 +++-------- 5 files changed, 43 insertions(+), 40 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index e90c78c17..e22e1016b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -2,11 +2,12 @@ .tableBox { height: calc(100% - 26px); - overflow: auto; + overflow-y: auto; + overflow-x: hidden; display: flex; flex-direction: column; :global { - .hGEOgo { + .art-table { --header-bgcolor: #fafafa; --header-color: #000; table colgroup col { @@ -15,6 +16,10 @@ table colgroup col:nth-of-type(1) { min-width: 60px; } + th, + td { + overflow: hidden; + } .fnWppk { // word-break: break-all; // display: -webkit-box; diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index af7155025..0fee8b6dc 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -48,7 +48,7 @@ export default memo((props) => {
    {`【${i18n('common.text.result')}】${queryResultData.description}.`} {`【${i18n('common.text.timeConsuming')}】${queryResultData.duration}ms.`} - {queryResultData?.dataList?.length && ( + {!!queryResultData?.dataList?.length && ( {`【${i18n('common.text.searchRow')}】${queryResultData?.dataList?.length} ${i18n( 'common.text.row', )}.`} diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 2aadf0c3d..f08490a4b 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -85,39 +85,43 @@ function AppContainer() { const [serviceFail, setServiceFail] = useState(false); const [isLogin, setIsLogin] = useState(null); + useLayoutEffect(() => { + collectInitApp(); + }, []); + useEffect(() => { - const date = new Date('2030-12-30 12:30:00').toUTCString(); - document.cookie = `CHAT2DB.LOCALE=${getLang()};Expires=${date}`; - indexedDB.createDB('chat2db', 1).then((db) => { - window._indexedDB = { - chat2db: db, - }; - }); + injectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); + }, [token]); + + function handelGetUserInfo() { getUser().then((res) => { if (!res) { navigate('/login'); } // 向cookie中写入当前用户id + const date = new Date('2030-12-30 12:30:00').toUTCString(); document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; setIsLogin(!!res); }); - }, []); - - useEffect(() => { - injectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); - }, [token]); - - useLayoutEffect(() => { - collectInitApp(); - }, []); + } // 初始化app function collectInitApp() { monitorOsTheme(); initLang(); + initIndexedDB(); setInitEnd(true); } + // 初始化indexedDB + function initIndexedDB() { + indexedDB.createDB('chat2db', 1).then((db) => { + window._indexedDB = { + chat2db: db, + }; + }); + } + // 监听系统(OS)主题变化 function monitorOsTheme() { function change(e: any) { @@ -136,9 +140,12 @@ function AppContainer() { // 初始化语言 function initLang() { - if (!getLang()) { + 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}`; } } @@ -155,6 +162,7 @@ function AppContainer() { .then(() => { clearInterval(time); setStartSchedule(2); + handelGetUserInfo(); flag++; }) .catch(() => { 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 22a2afa81..dde1750e5 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -45,7 +45,10 @@ .saveBoxList { padding: 0px 10px; height: calc(26px * 3 + 6px); - overflow-y: auto; + overflow-y: hidden; + &:hover { + overflow-y: auto; + } } .leftModuleTitleShadow { 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 4277902dd..4aee13583 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -85,11 +85,11 @@ const SaveList = dvaModel((props: any) => { } function openConsole(data: IConsole) { - let p: any = { + const params: any = { id: data.id, tabOpened: ConsoleOpenedStatus.IS_OPEN, }; - historyServer.updateSavedConsole(p).then((res) => { + historyServer.updateSavedConsole(params).then((res) => { dispatch({ type: 'workspace/setCreateConsoleIntro', payload: { @@ -103,24 +103,11 @@ const SaveList = dvaModel((props: any) => { } function deleteSaved(data: IConsole) { - let p: any = { + const params: any = { id: data.id, + status: ConsoleStatus.DRAFT, }; - historyServer.deleteSavedConsole(p).then((res) => { - // dispatch({ - // type: 'workspace/fetchGetSavedConsole', - // payload: { - // orderByDesc: true, - // tabOpened: ConsoleOpenedStatus.IS_OPEN, - // ...curWorkspaceParams - // }, - // callback: (res: any) => { - // dispatch({ - // type: 'workspace/setOpenConsoleList', - // payload: res.data, - // }) - // } - // }) + historyServer.updateSavedConsole(params).then((res) => { dispatch({ type: 'workspace/fetchGetSavedConsole', payload: { @@ -168,7 +155,7 @@ const SaveList = dvaModel((props: any) => { )}
    - + {(searchedList || consoleList)?.map((t: IConsole) => { return (
    Date: Mon, 16 Oct 2023 14:40:52 +0800 Subject: [PATCH 0944/1069] refacotr: modify menu shortcut key --- chat2db-client/src/main/menu.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index 6575e07b9..cef26e3ba 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -49,7 +49,8 @@ const registerAppMenu = (mainWindow) => { submenu: [ { label: '刷新', - accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'Ctrl+R', + // accelerator: process.platform === 'darwin' ? 'Cmd+R' : 'Ctrl+R', + accelerator: 'CmdOrCtrl+Shift+X', click() { const focusedWindow = BrowserWindow.getFocusedWindow(); if (focusedWindow) { From 458f6efe1a3f113dd7861ef4e5e39f51f4d87ceb Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 17:13:09 +0800 Subject: [PATCH 0945/1069] fix: Resolve wireless redirection issues --- chat2db-client/package.json | 2 +- chat2db-client/src/utils/eventSource.ts | 1 + .../start/config/config/Chat2dbWebMvcConfigurer.java | 7 +++++-- .../start/controller/thymeleaf/ThymeleafController.java | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index d2d35ed4d..0eb21b0eb 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,7 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" + "start:web": "cross-env UMI_ENV=local HMR=none cross-env APP_VERSION=${npm_config_app_version} umi dev" }, "dependencies": { "ahooks": "^3.7.7", diff --git a/chat2db-client/src/utils/eventSource.ts b/chat2db-client/src/utils/eventSource.ts index 21636ee47..b50aa0f54 100644 --- a/chat2db-client/src/utils/eventSource.ts +++ b/chat2db-client/src/utils/eventSource.ts @@ -15,6 +15,7 @@ const connectToEventSource = (params: { url: string; uid: string; onMessage: Fun }, }; const eventSource = new EventSourcePolyfill(`${window._BaseURL}${url}`, p); + // const eventSource = new EventSource(`${window._BaseURL}${url}`, p) eventSource.onmessage = (event) => { // console.log('onmessage', event); diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index ab298cdb4..2f3a68a42 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -56,7 +56,7 @@ public class Chat2dbWebMvcConfigurer implements WebMvcConfigurer { * 全局放行的url */ private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**", - "/api/system"}; + "/api/system", "/login"}; @Resource private UserService userService; @@ -138,6 +138,9 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl log.info("访问{},{}需要登录", buildHeaderString(request), SaHolder.getRequest().getUrl()); String path = SaHolder.getRequest().getRequestPath(); +// if(path.startsWith("/login")){ +// return true; +// } if (path.startsWith(API_PREFIX)) { response.getWriter().println(JSON.toJSONString( ActionResult.fail("common.needLoggedIn", I18nUtils.getMessage("common.needLoggedIn"), @@ -145,7 +148,7 @@ public boolean preHandle(@NotNull HttpServletRequest request, @NotNull HttpServl return false; } else { throw new RedirectBusinessException( - "/login-a#/login?callback=" + SaFoxUtil.joinParam(request.getRequestURI(), + "/login?callback=" + SaFoxUtil.joinParam(request.getRequestURI(), request.getQueryString())); } } diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java index 3c9a28abd..f4741d5f8 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/controller/thymeleaf/ThymeleafController.java @@ -22,7 +22,7 @@ public class ThymeleafController { * * @return */ - @GetMapping(value = {"/", "/web/", "/web/**","login-a"}) + @GetMapping(value = {"/", "/web/", "/web/**","/login"}) public String index() { return "index"; } From 6a14707be060c12967ec3c0d738c877b22c6bfe5 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 16 Oct 2023 21:52:00 +0800 Subject: [PATCH 0946/1069] feat: Optimize ai interface --- chat2db-client/src/components/Console/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 692c74de4..d665b4e35 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -249,6 +249,7 @@ function Console(props: IProps) { if (isNL2SQL) { setIsLoading(true); } else { + setAiContent(''); setIsAiDrawerOpen(true); setIsAiDrawerLoading(true); } @@ -264,11 +265,11 @@ function Console(props: IProps) { const handleMessage = (message: string) => { // console.log('message', message); setIsLoading(false); + setIsAiDrawerLoading(false); try { const isEOF = message === '[DONE]'; if (isEOF) { closeEventSource(); - setIsLoading(false); if (isChat2DBAI) { dispatch({ type: 'ai/fetchRemainingUse', @@ -326,9 +327,11 @@ function Console(props: IProps) { editorRef?.current?.setValue(JSON.parse(message).content); } else { chatResult.current += JSON.parse(message).content; + setAiContent(chatResult.current); } } catch (error) { setIsLoading(false); + setIsAiDrawerLoading(false); } }; From 6332483656e5f467a46311c9388116fa7567c8fa Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 17 Oct 2023 09:39:14 +0800 Subject: [PATCH 0947/1069] fix:The console tab locates bugs --- CHANGELOG.md | 25 +++++++++++++++ chat2db-client/package.json | 3 +- .../src/blocks/Setting/AiSetting/index.tsx | 3 ++ .../SearchResult/TableBox/index.less | 3 ++ .../src/components/TabsNew/index.tsx | 8 ++--- chat2db-client/src/i18n/en-us/connection.ts | 7 +++-- chat2db-client/src/i18n/zh-cn/connection.ts | 2 ++ chat2db-client/src/layouts/index.tsx | 22 ++----------- .../workspace/components/SaveList/index.less | 10 +++++- .../components/WorkspaceHeader/index.less | 31 ++++++++++++++----- .../components/WorkspaceHeader/index.tsx | 25 +++++++-------- .../components/WorkspaceRight/index.less | 1 + .../components/WorkspaceRight/index.tsx | 14 ++++++--- 13 files changed, 101 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca788db10..82d30cee4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +# 2.2.0 + +## ⭐ New Features + +## 🐞 Bug Fixes + +- + +## ⭐ 新特性 + +- 🔥新增**表结构**编辑功能 +- 🔥新增**表数据**编辑功能 +- + + +## 🐞 问题修复 + + # 2.1.0 ## ⭐ New Features @@ -7,6 +25,13 @@ function using 'docker' - Added support for environment selection, better distinguishing between online and daily +## ⭐ 新特性 + +-🔥新推出团队功能,支持团队协作。研发不需要知道在线数据库 +密码,解决企业数据库帐号的安全问题。建议直接部署团队 +使用'docker'的函数 +-增加了环境选择的支持,更好地区分在线和日常 + # 2.0.14 ## 🐞 Bug Fixes diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 0eb21b0eb..c8a828d6c 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,9 +22,10 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env UMI_ENV=local HMR=none cross-env APP_VERSION=${npm_config_app_version} umi dev" + "start:web": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" }, "dependencies": { + "@dnd-kit/modifiers": "^6.0.1", "ahooks": "^3.7.7", "ali-react-table": "^2.6.1", "antd": "^5.6.0", diff --git a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx index d2f30a5b9..85faaa274 100644 --- a/chat2db-client/src/blocks/Setting/AiSetting/index.tsx +++ b/chat2db-client/src/blocks/Setting/AiSetting/index.tsx @@ -24,6 +24,9 @@ export default function SettingAI(props: IProps) { setLoading(true); try { const res = await getUser(); + // 向cookie中写入当前用户id + const date = new Date('2030-12-30 12:30:00').toUTCString(); + document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; setUserInfo(res); } finally { setLoading(false); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index e22e1016b..d86100672 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -46,6 +46,9 @@ .art-table-cell { position: relative; } + .art-sticky-scroll { + display: none !important; + } .art-sticky-scroll.art-horizontal-scroll-container { position: sticky; bottom: 26px !important; diff --git a/chat2db-client/src/components/TabsNew/index.tsx b/chat2db-client/src/components/TabsNew/index.tsx index 53707ec65..1f813e3ab 100644 --- a/chat2db-client/src/components/TabsNew/index.tsx +++ b/chat2db-client/src/components/TabsNew/index.tsx @@ -23,8 +23,8 @@ export interface IOnchangeProps { interface IProps { className?: string; items?: ITabItem[]; - activeKey?: number | string; - onChange?: (key: string | number | undefined) => void; + activeKey?: number | string | null; + onChange?: (key: string | number | null) => void; onEdit?: (action: 'add' | 'remove', data?: ITabItem, list?: ITabItem[]) => void; hideAdd?: boolean; type?: 'line'; @@ -48,7 +48,7 @@ export default memo((props) => { concealTabHeader, } = props; const [internalTabs, setInternalTabs] = useState([]); - const [internalActiveTab, setInternalActiveTab] = useState(); + const [internalActiveTab, setInternalActiveTab] = useState(null); const [editingTab, setEditingTab] = useState(); useEffect(() => { @@ -85,7 +85,7 @@ export default memo((props) => { onEdit?.('remove', data, newInternalTabs); } - function changeTab(key: string | number | undefined) { + function changeTab(key: string | number | null) { setInternalActiveTab(key); } diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 15abf400c..56bdceeeb 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -26,6 +26,7 @@ export default { 'connection.text.tryAgainDownload': 'Try again download', 'connection.text.downloading': 'Downloading...', 'connection.label.private': 'Private', - 'connection.label.shared': 'Shared', - -}; \ No newline at end of file + 'connection.button.shared': 'Shared', + 'connection.button.createConnection': 'Create connection', + 'connection.tips.noConnection': 'You have not created any connections yet', +}; diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 0e627edc0..29e72b046 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -27,4 +27,6 @@ export default { 'connection.text.downloading': '下载中...', 'connection.label.private': '私有', 'connection.label.shared': '共享', + 'connection.button.createConnection': '创建连接', + 'connection.tips.noConnection': '您当前还没有创建任何连接', } diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index f08490a4b..258e2f600 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -1,12 +1,11 @@ import React, { useEffect, useLayoutEffect, useState } from 'react'; import i18n, { isEn } from '@/i18n'; -import { Outlet, useNavigate } from 'umi'; +import { Outlet } from 'umi'; import { ConfigProvider, theme, Spin } from 'antd'; import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig, injectThemeVar } from '@/theme'; import { IVersionResponse } from '@/typings'; import miscService from '@/service/misc'; -import { getUser } from '@/service/user'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import { useTheme } from '@/hooks'; @@ -77,13 +76,11 @@ export default function Layout() { const restartCount = 200; function AppContainer() { - const navigate = useNavigate(); const { token } = useToken(); const [initEnd, setInitEnd] = useState(false); const [appTheme, setAppTheme] = useTheme(); const [startSchedule, setStartSchedule] = useState(0); // 0 初始状态 1 服务启动中 2 启动成功 const [serviceFail, setServiceFail] = useState(false); - const [isLogin, setIsLogin] = useState(null); useLayoutEffect(() => { collectInitApp(); @@ -93,18 +90,6 @@ function AppContainer() { injectThemeVar(token as any, appTheme.backgroundColor, appTheme.primaryColor); }, [token]); - function handelGetUserInfo() { - getUser().then((res) => { - if (!res) { - navigate('/login'); - } - // 向cookie中写入当前用户id - const date = new Date('2030-12-30 12:30:00').toUTCString(); - document.cookie = `CHAT2DB.USER_ID=${res?.id};Expires=${date}`; - setIsLogin(!!res); - }); - } - // 初始化app function collectInitApp() { monitorOsTheme(); @@ -162,7 +147,6 @@ function AppContainer() { .then(() => { clearInterval(time); setStartSchedule(2); - handelGetUserInfo(); flag++; }) .catch(() => { @@ -181,7 +165,7 @@ function AppContainer() { {initEnd && (
    {/* 服务启动中 */} - {(startSchedule < 2 || isLogin === null) && ( + {startSchedule < 2 && (
    {/* 状态等于1时,说明没服务起来需要轮训接口,这时可能服务配置又问题,需要设置来修改 */} @@ -211,7 +195,7 @@ function AppContainer() {
    )} {/* 服务启动完成 */} - {startSchedule === 2 && isLogin !== null && } + {startSchedule === 2 && }
    )} {/* 全局的弹窗 */} 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 dde1750e5..c671348b8 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -2,14 +2,17 @@ .saveModule { flex-shrink: 0; - padding-top: 6px; } .leftModuleTitle { flex-shrink: 0; margin-bottom: 4px; padding: 0px 10px; + height: 32px; + display: flex; + align-items: center; .leftModuleTitleText { + width: 100%; display: flex; justify-content: space-between; align-items: center; @@ -42,6 +45,11 @@ } } +.loadingContent { + height: auto; + padding-bottom: 4px; +} + .saveBoxList { padding: 0px 10px; height: calc(26px * 3 + 6px); diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less index adb06ff3e..56d9b6fbc 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -14,27 +14,27 @@ height: 36px; } -.workspaceHeaderRight{ +.workspaceHeaderRight { width: 0px; flex: 1; flex-shrink: 0; overflow: hidden; } -.workspaceHeaderLeft{ +.workspaceHeaderLeft { display: flex; align-items: center; flex: 1; } -.workspaceHeaderCenter{ +.workspaceHeaderCenter { flex-shrink: 0; display: flex; justify-content: center; padding: 0px 40px; } -.databaseTypeIcon{ +.databaseTypeIcon { margin-right: 10px; font-weight: 400; color: var(--color-primary); @@ -83,7 +83,7 @@ } } -.arrow{ +.arrow { flex-shrink: 0; font-size: 10px; margin: 0px 4px; @@ -97,8 +97,25 @@ .noConnectionModal { display: flex; - justify-content: space-between; - + flex-direction: column; + align-items: center; + .icon { + font-size: 60px; + margin: 10px 0px; + color: var(--color-text-secondary); + } .mainText { + display: flex; + align-items: center; + justify-content: center; + font-size: 18px; + } + .createButton { + font-size: 16px; + color: var(--color-primary); + cursor: pointer; + &:hover { + text-decoration: underline; + } } } 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 2460549d4..93e016b68 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -6,11 +6,12 @@ import Iconfont from '@/components/Iconfont'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; -import { Cascader, Spin, Modal, Button, Tag } from 'antd'; +import { Cascader, Spin, Modal, Tag } from 'antd'; import { databaseMap, TreeNodeType } from '@/constants'; import { treeConfig } from '../Tree/treeConfig'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import styles from './index.less'; +import i18n from '@/i18n'; interface IProps { className?: string; @@ -71,6 +72,9 @@ const WorkspaceHeader = memo((props) => { setIsRefresh(false); } // connectionList转换成可用的ConnectionOptions + // if (!connectionList.length) { + // setNoConnectionModal(true); + // } setConnectionOptions( connectionList?.map((t) => { return { @@ -308,18 +312,11 @@ const WorkspaceHeader = memo((props) => {
    )} - } - keyboard={false} - maskClosable={false} - title="温馨提示" - footer={[]} - > + } keyboard={false} maskClosable={false} footer={false}>
    -
    您当前还没有创建任何连接
    -
    + +
    { setNoConnectionModal(false); @@ -329,8 +326,8 @@ const WorkspaceHeader = memo((props) => { }); }} > - 创建连接 - + {i18n('connection.button.createConnection')} +
    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 cc2dee425..fe2326b78 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.less @@ -61,6 +61,7 @@ display: flex; justify-content: center; align-items: center; + margin-top: 10px; i { margin-right: 10px; } 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 421b1e985..18aa54649 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -19,7 +19,7 @@ import { IAIState } from '@/models/ai'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import { v4 as uuidV4 } from 'uuid'; import { IWorkspaceTab } from '@/typings'; -import { Button, Popover } from 'antd'; +import { Button } from 'antd'; import { registerIntelliSenseField, registerIntelliSenseKeyword, @@ -37,7 +37,7 @@ interface IProps { const WorkspaceRight = memo((props: IProps) => { const { className, workspaceModel, dispatch } = props; // 活跃的TabID - const [activeConsoleId, setActiveConsoleId] = useState(); + const [activeConsoleId, setActiveConsoleId] = useState(null); // 工作台tab列表 const [workspaceTabList, setWorkspaceTabList] = useState([]); @@ -46,6 +46,10 @@ const WorkspaceRight = memo((props: IProps) => { const tableList = useRef>([]); + useEffect(() => { + setActiveConsoleId(null); + }, [curWorkspaceParams]); + // 根据保存的console列表生成tab列表 useEffect(() => { const newTabList = openConsoleList?.map((t) => { @@ -57,7 +61,9 @@ const WorkspaceRight = memo((props: IProps) => { }; }); setWorkspaceTabList(newTabList || []); - setActiveConsoleId(newTabList[0]?.id); + if (!activeConsoleId) { + setActiveConsoleId(newTabList[0]?.id); + } }, [openConsoleList]); // 注册快捷键command+shift+L新建console @@ -402,7 +408,7 @@ const WorkspaceRight = memo((props: IProps) => { } // 切换tab - function onTabChange(key: string | number | undefined) { + function onTabChange(key: string | number | null) { setActiveConsoleId(key); } From 7af8af36aae4a5ccfd21d2658e97baeaac0bbf8f Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Mon, 16 Oct 2023 13:54:56 +0800 Subject: [PATCH 0948/1069] vector update --- .../api/controller/rdb/TableController.java | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 5f6e4cc0e..80ae1a022 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -10,6 +10,7 @@ import ai.chat2db.server.tools.base.wrapper.result.PageResult; import ai.chat2db.server.tools.base.wrapper.result.web.WebPageResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; +import ai.chat2db.server.web.api.controller.ai.EmbeddingController; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; import ai.chat2db.server.web.api.controller.rdb.request.*; import ai.chat2db.server.web.api.controller.rdb.vo.ColumnVO; @@ -17,17 +18,23 @@ import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.model.*; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; import com.google.common.collect.Lists; import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +@Slf4j @ConnectionInfoAspect @RequestMapping("/api/rdb/table") @RestController -public class TableController { +public class TableController extends EmbeddingController { @Autowired private TableService tableService; @@ -41,6 +48,7 @@ public class TableController { @Autowired private DatabaseService databaseService; + public static ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); /** * 查询当前DB下的表列表 @@ -56,6 +64,18 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { tableSelector.setIndexList(false); PageResult
    tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); List tableVOS = rdbWebConverter.tableDto2vo(tableDTOPageResult.getData()); + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + singleThreadExecutor.submit(() -> { + try { + Chat2DBContext.putContext(connectInfo); + syncTableVector(request); + } catch (Exception e) { + log.error("sync table vector error", e); + } finally { + Chat2DBContext.removeContext(); + } + log.info("sync table vector finish"); + }); return WebPageResult.of(tableVOS, tableDTOPageResult.getTotal(), request.getPageNo(), request.getPageSize()); } From 0bb91d2a5c070ba576d22ad40235c102ff02b78b Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Mon, 16 Oct 2023 14:16:18 +0800 Subject: [PATCH 0949/1069] vector update --- .../web/api/controller/ai/ChatController.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index e2590a326..551dfbe64 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -475,19 +475,24 @@ private String buildPrompt(ChatQueryRequest queryRequest) { // 查询schema信息 String dataSourceType = queryDatabaseType(queryRequest); - TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); - Map> tableColumns = buildTableColumn(queryParam, queryRequest.getTableNames()); - List tableSchemas = tableColumns.entrySet().stream().map( - entry -> String.format("%s(%s)", entry.getKey(), - entry.getValue().stream().map(TableColumn::getName).collect( - Collectors.joining(", ")))).collect(Collectors.toList()); - String properties = String.join("\n#", tableSchemas); + String properties = ""; + if (CollectionUtils.isNotEmpty(queryRequest.getTableNames())) { + TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); + Map> tableColumns = buildTableColumn(queryParam, queryRequest.getTableNames()); + List tableSchemas = tableColumns.entrySet().stream().map( + entry -> String.format("%s(%s)", entry.getKey(), + entry.getValue().stream().map(TableColumn::getName).collect( + Collectors.joining(", ")))).collect(Collectors.toList()); + properties = String.join("\n#", tableSchemas); + } else { + properties = queryDatabaseSchema(queryRequest); + } String prompt = queryRequest.getMessage(); String promptType = StringUtils.isBlank(queryRequest.getPromptType()) ? PromptType.NL_2_SQL.getCode() : queryRequest.getPromptType(); PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType); String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : ""; - String schemaProperty = CollectionUtils.isNotEmpty(tableSchemas) ? String.format( + String schemaProperty = StringUtils.isNotEmpty(properties) ? String.format( "### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# " + "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType, properties, prompt) : String.format("### 请根据以下SQL input%s. %s\n#\n### SQL input: %s", @@ -527,7 +532,7 @@ public String queryDatabaseType(ChatQueryRequest queryRequest) { * @return * @throws IOException */ - public String queryDatabaseSchema(ChatQueryRequest queryRequest) throws IOException { + public String queryDatabaseSchema(ChatQueryRequest queryRequest) { // request embedding FastChatEmbeddingResponse response = distributeAIEmbedding(queryRequest.getMessage()); List> contentVector = new ArrayList<>(); From 4db2e53815465386e5e06ea5f97f2db275a9a626 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Mon, 16 Oct 2023 19:24:46 +0800 Subject: [PATCH 0950/1069] query gateway update --- .../chat2db/client/Chat2DBAIStreamClient.java | 20 ++----------------- .../web/api/http/GatewayClientService.java | 8 ++++---- 2 files changed, 6 insertions(+), 22 deletions(-) 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 cf6ff6f35..b2b5d3770 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 @@ -246,25 +246,9 @@ public FastChatEmbeddingResponse embeddings(FastChatEmbedding embedding) { FastChatEmbeddingResponse chatEmbeddingResponse = null; Response response = this.okHttpClient.newCall(request).execute(); - StringBuilder body = new StringBuilder(); if (response.isSuccessful()) { - ResponseBody responseBody = response.body(); - if (responseBody != null) { - // 获取响应体的输入流 - java.io.InputStream inputStream = responseBody.byteStream(); - java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(inputStream)); - - String line; - while ((line = reader.readLine()) != null) { - // 在这里处理每行响应内容 - body.append(line); - } - - // 关闭流 - reader.close(); - inputStream.close(); - } - chatEmbeddingResponse = mapper.readValue(body.toString(), FastChatEmbeddingResponse.class); + String body = response.body().string(); + chatEmbeddingResponse = mapper.readValue(body, FastChatEmbeddingResponse.class); } log.info("finish invoking chat embedding"); return chatEmbeddingResponse; diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index e13c23913..07512229e 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -61,8 +61,8 @@ public interface GatewayClientService { * @param request * @return */ - @Post("/api/client/milvus/knowledge/save") - ActionResult knowledgeVectorSave(@Query KnowledgeRequest request); + @Post(url = "/api/client/milvus/knowledge/save", contentType = "application/json") + ActionResult knowledgeVectorSave(@Body KnowledgeRequest request); /** * save table schema vector @@ -70,8 +70,8 @@ public interface GatewayClientService { * @param request * @return */ - @Post("/api/client/milvus/schema/save") - ActionResult schemaVectorSave(@Query TableSchemaRequest request); + @Post(url = "/api/client/milvus/schema/save", contentType = "application/json") + ActionResult schemaVectorSave(@Body TableSchemaRequest request); /** * save knowledge vector From d6bca641dd7ff4a4888dffaea2c988d3c1c5d817 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 17 Oct 2023 10:57:42 +0800 Subject: [PATCH 0951/1069] embedding sql --- .../db/migration/V2_1_6__TableVectorUpdate.sql | 1 + .../web/api/controller/ai/EmbeddingController.java | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_6__TableVectorUpdate.sql diff --git a/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_6__TableVectorUpdate.sql b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_6__TableVectorUpdate.sql new file mode 100644 index 000000000..196581ad9 --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/resources/db/migration/V2_1_6__TableVectorUpdate.sql @@ -0,0 +1 @@ +ALTER TABLE table_vector_mapping ALTER COLUMN status VARCHAR(64); diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java index 7ea9413ed..7e93c3680 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java @@ -157,10 +157,6 @@ public void syncTableVector(TableBriefQueryRequest param) throws Exception { if (StringUtils.isBlank(vectorParam.getDatabase()) && StringUtils.isBlank(vectorParam.getSchema())) { return; } - DataResult result = tableService.checkTableVector(vectorParam); - if (result.getData()) { - return; - } ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); @@ -178,6 +174,12 @@ public void syncTableVector(TableBriefQueryRequest param) throws Exception { String apiKey = keyConfig.getContent(); request.setApikey(apiKey); + vectorParam.setApiKey(apiKey); + DataResult result = tableService.checkTableVector(vectorParam); + if (result.getData()) { + return; + } + // check if in white list boolean res = gatewayClientService.checkInWhite(new WhiteListRequest(apiKey, WhiteListTypeEnum.VECTOR.getCode())).getData(); if (!res) { From d90b5267ab30fc3c2111b7d3b6509dfaaf7edef0 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 17 Oct 2023 11:56:59 +0800 Subject: [PATCH 0952/1069] feat: Add the function about cancel sse --- .../components/Console/ChatInput/index.less | 16 ++++++++- .../components/Console/ChatInput/index.tsx | 34 +++++++++++++------ .../src/components/Console/index.tsx | 21 +++++++++--- .../src/components/Iconfont/index.tsx | 6 ++-- chat2db-client/src/utils/eventSource.ts | 15 ++++++-- 5 files changed, 70 insertions(+), 22 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index f213a9545..7b6388792 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -36,11 +36,25 @@ height: 24px; padding: 0; margin-right: 16px; + .enterIcon { font-size: 12px; } } +.stop { + font-size: 16px; + color: red; + cursor: pointer; + padding: 2px 8px; + margin-right: 4px; + border-radius: 4px; + &:hover { + background-color: var(--color-hover-bg); + } +} + + .tableSelectBlock { // margin-right: 8px; cursor: pointer; @@ -72,4 +86,4 @@ padding-bottom: 4px; border-bottom: 1px solid var(--color-border-secondary); margin-bottom: 4px; -} +} \ No newline at end of file diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index 855443526..dbb9d858d 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -23,10 +23,12 @@ interface IProps { aiType: AiSqlSourceType; remainingBtnLoading: boolean; disabled?: boolean; + isStream?: boolean; onPressEnter: (value: string) => void; onSelectTableSyncModel: (model: number) => void; onSelectTables?: (tables: string[]) => void; onClickRemainBtn: Function; + onCancelStream: () => void; } const ChatInput = (props: IProps) => { @@ -85,17 +87,27 @@ const ChatInput = (props: IProps) => { const hasBubble = localStorage.getItem('syncTableBubble'); return (
    - + {props.isStream ? ( + { + props.onCancelStream && props.onCancelStream(); + }} + code="" + className={styles.stop} + /> + ) : ( + + )} {i18n('chat.input.syncTable.tempTips')}} defaultOpen={!hasBubble} diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index d665b4e35..3199a89da 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -102,9 +102,11 @@ function Console(props: IProps) { const [isAiDrawerLoading, setIsAiDrawerLoading] = useState(false); const [popularizeModal, setPopularizeModal] = useState(false); const [modalProps, setModalProps] = useState({}); + const [isStream, setIsStream] = useState(false); const timerRef = useRef(); const aiFetchIntervalRef = useRef(); const initializeSuccessful = useRef(false); + const closeEventSource = useRef(); /** * 当前选择的AI类型是Chat2DBAI @@ -269,7 +271,8 @@ function Console(props: IProps) { try { const isEOF = message === '[DONE]'; if (isEOF) { - closeEventSource(); + closeEventSource.current(); + setIsStream(false); if (isChat2DBAI) { dispatch({ type: 'ai/fetchRemainingUse', @@ -303,14 +306,14 @@ function Console(props: IProps) { }); if (hasKeyLimitedOrExpired) { - closeEventSource(); + closeEventSource.current(); setIsLoading(false); handlePopUp(); return; } if (hasErrorToLogin) { - closeEventSource(); + closeEventSource.current(); setIsLoading(false); hasErrorToLogin && handleApiKeyEmptyOrGetQrCode(true); // hasErrorToInvite && handleClickRemainBtn(); @@ -332,6 +335,7 @@ function Console(props: IProps) { } catch (error) { setIsLoading(false); setIsAiDrawerLoading(false); + setIsStream(false); } }; @@ -340,9 +344,12 @@ function Console(props: IProps) { setIsLoading(false); }; - const closeEventSource = connectToEventSource({ + closeEventSource.current = connectToEventSource({ url: `/api/ai/chat?${params}`, uid, + onOpen: () => { + setIsStream(true); + }, onMessage: handleMessage, onError: handleError, }); @@ -449,6 +456,7 @@ function Console(props: IProps) { {hasAiChat && ( { + closeEventSource.current() + setIsStream(false); + setIsLoading(false); + }} /> )} {/*
    {chatContent.current}
    */} diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 0745323cd..dd93ec62d 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_2y91mha361m.woff2?t=1697419167687') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_2y91mha361m.woff?t=1697419167687') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_2y91mha361m.ttf?t=1697419167687') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_xwv1edd4b7n.woff2?t=1697513390352') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_xwv1edd4b7n.woff?t=1697513390352') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_xwv1edd4b7n.ttf?t=1697513390352') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/utils/eventSource.ts b/chat2db-client/src/utils/eventSource.ts index b50aa0f54..bc9cc9032 100644 --- a/chat2db-client/src/utils/eventSource.ts +++ b/chat2db-client/src/utils/eventSource.ts @@ -1,7 +1,13 @@ import { EventSourcePolyfill } from 'event-source-polyfill'; -const connectToEventSource = (params: { url: string; uid: string; onMessage: Function; onError: Function }) => { - const { url, uid, onMessage, onError } = params; +const connectToEventSource = (params: { + url: string; + uid: string; + onOpen: Function; + onMessage: Function; + onError: Function; +}) => { + const { url, uid, onOpen, onMessage, onError } = params; if (!url || !onMessage || !onError) { throw new Error('url, onMessage, and onError are required'); @@ -15,7 +21,10 @@ const connectToEventSource = (params: { url: string; uid: string; onMessage: Fun }, }; const eventSource = new EventSourcePolyfill(`${window._BaseURL}${url}`, p); - // const eventSource = new EventSource(`${window._BaseURL}${url}`, p) + + eventSource.onopen = () => { + onOpen(); + }; eventSource.onmessage = (event) => { // console.log('onmessage', event); From 57d7b0411a8f6ad431e1c21b17919520e79886fa Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 17 Oct 2023 13:05:46 +0800 Subject: [PATCH 0953/1069] embedding query --- .../web/api/controller/ai/ChatController.java | 29 ++++++++++++------- .../web/api/http/GatewayClientService.java | 8 ++--- .../api/http/request/TableSchemaRequest.java | 2 +- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 551dfbe64..32fc93b98 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -542,20 +542,27 @@ public String queryDatabaseSchema(ChatQueryRequest queryRequest) { TableSchemaRequest tableSchemaRequest = new TableSchemaRequest(); tableSchemaRequest.setSchemaVector(contentVector); tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); - String databaseName = StringUtils.isNotBlank(queryRequest.getDatabaseName()) ? queryRequest.getDatabaseName() : queryRequest.getSchemaName(); - if (Objects.isNull(databaseName)) { - databaseName = ""; + tableSchemaRequest.setDatabaseName(queryRequest.getDatabaseName()); + tableSchemaRequest.setDataSourceSchema(queryRequest.getSchemaName()); + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); + if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { + return ""; } - tableSchemaRequest.setDatabaseName(databaseName); - DataResult result = gatewayClientService.schemaVectorSearch(tableSchemaRequest); - - List schemas = Lists.newArrayList(); - if (CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { - for(TableSchema data: result.getData().getTableSchemas()){ - schemas.add(data.getTableSchema()); + tableSchemaRequest.setApiKey(keyConfig.getContent()); + try { + DataResult result = gatewayClientService.schemaVectorSearch(tableSchemaRequest); + List schemas = Lists.newArrayList(); + if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { + for(TableSchema data: result.getData().getTableSchemas()){ + schemas.add(data.getTableSchema()); + } } + return JSON.toJSONString(schemas); + } catch (Exception exception) { + log.error("query table error, do nothing"); + return ""; } - return JSON.toJSONString(schemas); } /** diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index 07512229e..8225840b7 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -79,8 +79,8 @@ public interface GatewayClientService { * @param searchVectors * @return */ - @Get("/api/client/milvus/knowledge/search") - DataResult knowledgeVectorSearch(@Query KnowledgeRequest searchVectors); + @Post(url = "/api/client/milvus/knowledge/search", contentType = "application/json") + DataResult knowledgeVectorSearch(@Body KnowledgeRequest searchVectors); /** * save table schema vector @@ -88,8 +88,8 @@ public interface GatewayClientService { * @param request * @return */ - @Get("/api/client/milvus/schema/search") - DataResult schemaVectorSearch(@Query TableSchemaRequest request); + @Post(url = "/api/client/milvus/schema/search", contentType = "application/json") + DataResult schemaVectorSearch(@Body TableSchemaRequest request); /** * check in white list diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java index aac8c1e03..324e7a7e9 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/TableSchemaRequest.java @@ -26,5 +26,5 @@ public class TableSchemaRequest { private List schemaList; - private Boolean deleteBeforeInsert; + private Boolean deleteBeforeInsert = false; } From 3a192aefc0ccb846ae254119ae82f666b0198888 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 17 Oct 2023 14:26:50 +0800 Subject: [PATCH 0954/1069] fix: fix sse retry --- chat2db-client/src/components/Console/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 3199a89da..cd6b67398 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -336,12 +336,14 @@ function Console(props: IProps) { setIsLoading(false); setIsAiDrawerLoading(false); setIsStream(false); + closeEventSource.current(); } }; const handleError = (error: any) => { console.error('Error:', error); setIsLoading(false); + closeEventSource.current(); }; closeEventSource.current = connectToEventSource({ @@ -482,7 +484,7 @@ function Console(props: IProps) { localStorage.setItem('syncTableModel', String(model)); }} onCancelStream={() => { - closeEventSource.current() + closeEventSource.current(); setIsStream(false); setIsLoading(false); }} From a4f13382ad4d1ab88e77e3bf5af3a698059f27bb Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Tue, 17 Oct 2023 14:39:39 +0800 Subject: [PATCH 0955/1069] embedding query --- .../web/api/controller/ai/ChatController.java | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index 32fc93b98..ae3fecc38 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -13,6 +13,7 @@ import ai.chat2db.server.domain.api.enums.AiSqlSourceEnum; import ai.chat2db.server.domain.api.model.Config; import ai.chat2db.server.domain.api.model.DataSource; +import ai.chat2db.server.domain.api.param.ShowCreateTableParam; import ai.chat2db.server.domain.api.param.TableQueryParam; import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.api.service.DataSourceService; @@ -444,22 +445,39 @@ private SseEmitter buildSseEmitter(SseEmitter sseEmitter, String uid) throws IOE * @param tableNames * @return */ - private Map> buildTableColumn(TableQueryParam tableQueryParam, + private String buildTableColumn(TableQueryParam tableQueryParam, List tableNames) { if (CollectionUtils.isEmpty(tableNames)) { - return Maps.newHashMap(); + return ""; } - List tableColumns = Lists.newArrayList(); + List schemaContent = Lists.newArrayList(); try { - tableColumns = tableService.queryColumns(tableQueryParam); + schemaContent = tableNames.stream().map(tableName -> { + tableQueryParam.setTableName(tableName); + return queryTableDdl(tableName, tableQueryParam); + }).collect(Collectors.toList()); } catch (Exception exception) { log.error("query table error, do nothing"); } - if (CollectionUtils.isEmpty(tableColumns)) { - return Maps.newHashMap(); - } - return tableColumns.stream().filter(tableColumn -> tableNames.contains(tableColumn.getTableName())).collect( - Collectors.groupingBy(TableColumn::getTableName, Collectors.toList())); + + return JSON.toJSONString(schemaContent); + } + + /** + * query table schema + * + * @param tableName + * @param request + * @return + */ + private String queryTableDdl(String tableName, TableQueryParam request) { + ShowCreateTableParam param = new ShowCreateTableParam(); + param.setTableName(tableName); + param.setDataSourceId(request.getDataSourceId()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + DataResult tableSchema = tableService.showCreateTable(param); + return tableSchema.getData(); } /** @@ -478,12 +496,7 @@ private String buildPrompt(ChatQueryRequest queryRequest) { String properties = ""; if (CollectionUtils.isNotEmpty(queryRequest.getTableNames())) { TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); - Map> tableColumns = buildTableColumn(queryParam, queryRequest.getTableNames()); - List tableSchemas = tableColumns.entrySet().stream().map( - entry -> String.format("%s(%s)", entry.getKey(), - entry.getValue().stream().map(TableColumn::getName).collect( - Collectors.joining(", ")))).collect(Collectors.toList()); - properties = String.join("\n#", tableSchemas); + properties = buildTableColumn(queryParam, queryRequest.getTableNames()); } else { properties = queryDatabaseSchema(queryRequest); } From a75f87a45ba029994a0b6fa7d43bfa147f3bb1ad Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 17 Oct 2023 15:43:36 +0800 Subject: [PATCH 0956/1069] fix: bug when streaming content to editor --- chat2db-client/package.json | 4 +- .../src/blocks/CreateConnection/index.tsx | 102 ++++++++--------- .../DatabaseTableEditor/ColumnList/index.tsx | 7 +- .../DatabaseTableEditor/IncludeCol/index.tsx | 1 - .../src/blocks/SQLExecute/index.tsx | 1 - .../components/Console/MonacoEditor/index.tsx | 17 ++- .../Console/NewMonacoEditor/index.tsx | 1 - .../src/components/Console/index.tsx | 23 ++-- .../src/components/ImportConnection/index.tsx | 1 - chat2db-client/src/models/workspace.ts | 8 +- .../pages/main/dashboard/chart-item/index.tsx | 4 +- .../src/pages/main/dashboard/index.tsx | 3 - .../pages/main/team/user-management/index.tsx | 107 ++++++++++-------- .../workspace/components/SaveList/index.less | 10 ++ .../workspace/components/SaveList/index.tsx | 7 +- .../Tree/TreeNodeRightClick/index.less | 5 + .../Tree/TreeNodeRightClick/index.tsx | 29 +++-- .../components/WorkspaceRight/index.tsx | 2 - chat2db-client/src/pages/test/index.tsx | 3 +- chat2db-client/src/service/base.ts | 1 - .../src/utils/IntelliSense/field.ts | 1 - chat2db-client/src/utils/eventSource.ts | 1 - 22 files changed, 183 insertions(+), 155 deletions(-) diff --git a/chat2db-client/package.json b/chat2db-client/package.json index c8a828d6c..0fb1844da 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -22,7 +22,8 @@ "start": "concurrently \"npm run start:web\" \"npm run start:main\"", "start:main": "cross-env NODE_ENV=development electron .", "start:main:prod": "cross-env NODE_ENV=production electron .", - "start:web": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" + "start:web": "cross-env UMI_ENV=local HMR=none cross-env APP_VERSION=${npm_config_app_version} umi dev", + "start:web:hot": "cross-env UMI_ENV=local cross-env APP_VERSION=${npm_config_app_version} umi dev" }, "dependencies": { "@dnd-kit/modifiers": "^6.0.1", @@ -47,7 +48,6 @@ "umi": "^4.0.70", "umi-request": "^1.4.0", "uuid": "^9.0.0", - "@dnd-kit/modifiers": "^6.0.1", "highlight.js": "^11.9.0" }, "devDependencies": { diff --git a/chat2db-client/src/blocks/CreateConnection/index.tsx b/chat2db-client/src/blocks/CreateConnection/index.tsx index 4842b46e3..ee57ff13e 100644 --- a/chat2db-client/src/blocks/CreateConnection/index.tsx +++ b/chat2db-client/src/blocks/CreateConnection/index.tsx @@ -3,7 +3,7 @@ import styles from './index.less'; import classnames from 'classnames'; import { IConnectionDetails, IDatabase } from '@/typings'; import ConnectionEdit, { ICreateConnectionFunction } from '@/components/ConnectionEdit'; -import { DatabaseTypeCode, databaseMap, databaseTypeList } from '@/constants'; +import { databaseTypeList } from '@/constants'; import Iconfont from '@/components/Iconfont'; interface IProps { @@ -12,24 +12,18 @@ interface IProps { connectionDetail?: IConnectionDetails; // 如果你想编辑,就直接传入完成的数据就好 } -export default memo(function CreateConnection(props) { +export default memo((props) => { const { className, onSubmit, connectionDetail } = props; const [curConnection, setCurConnection] = useState>({}); const createConnectionRef = useRef(); - useEffect(() => { if (connectionDetail) { - setCurConnection(connectionDetail) + setCurConnection(connectionDetail); } else { - setCurConnection({}) + setCurConnection({}); } - }, [connectionDetail]) - - - function getData() { - console.log(createConnectionRef.current?.getData()) - } + }, [connectionDetail]); function handleCreateConnections(database: IDatabase) { setCurConnection({ @@ -38,50 +32,52 @@ export default memo(function CreateConnection(props) { } function handleSubmit(data: IConnectionDetails) { - onSubmit?.(data) + onSubmit?.(data); } - return
    - {curConnection && Object.keys(curConnection).length ? ( -
    - { - { - setCurConnection({}); - }} - connectionData={curConnection as any} - submit={handleSubmit} - /> - } -
    - ) : ( -
    - {databaseTypeList.map((t) => { - return ( -
    -
    -
    -
    - + return ( +
    + {curConnection && Object.keys(curConnection).length ? ( +
    + { + { + setCurConnection({}); + }} + connectionData={curConnection as any} + submit={handleSubmit} + /> + } +
    + ) : ( +
    + {databaseTypeList.map((t) => { + return ( +
    +
    +
    +
    + +
    + {t.name} +
    +
    +
    - {t.name} -
    -
    -
    -
    - ); - })} - {Array.from({ length: 20 }).map((t, index) => { - return
    ; - })} -
    - )} -
    -}) + ); + })} + {Array.from({ length: 20 }).map((t, index) => { + return
    ; + })} +
    + )} +
    + ); +}); diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 5da77f531..c5be1acc2 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -107,17 +107,12 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) form.setFieldsValue({ ...record }); setEditingData(record); // 根据当前字段类型,设置编辑配置 - console.log(databaseSupportField.columnTypes, record.columnType); databaseSupportField.columnTypes.forEach((i) => { if (i.typeName === record.columnType) { setEditingConfig({ ...i, editKey: record.key!, }); - console.log({ - ...i, - editKey: record.key!, - }); } }); } @@ -161,6 +156,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) title: i18n('editTable.label.columnName'), dataIndex: 'name', width: '160px', + fixed: 'left', render: (text: string, record: IColumnItemNew) => { const editable = isEditing(record); return ( @@ -319,7 +315,6 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } return item; }); - console.log(newData); setDataSource(newData); }; diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index 6286c762a..d20d348b4 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -87,7 +87,6 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) const addData = () => { const newData = createInitialData(); setDataSource([...dataSource, newData]); - console.log([...dataSource, newData]); edit(newData); setTimeout(() => { tableRef.current?.scrollTo(0, tableRef.current?.scrollHeight + 100); diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index 5d6c94b1b..b097f1cb2 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -8,7 +8,6 @@ import SearchResult from '@/components/SearchResult'; import { DatabaseTypeCode, ConsoleStatus, TreeNodeType } from '@/constants'; import { IManageResultData, IResultConfig } from '@/typings'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; -import historyServer, { ISaveBasicInfo } from '@/service/history'; import { IAIState } from '@/models/ai'; import sqlServer, { IExecuteSqlParams } from '@/service/sql'; import { v4 as uuidV4 } from 'uuid'; diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index b468ee7e9..8f12279d2 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -297,6 +297,15 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e newRange = new monaco.Range(lastLine, lastLineLength, lastLine, lastLineLength); newText = `${text}`; break; + // 在光标处添加内容 + case 'cursor': + { + const position = editor.getPosition(); + if (position) { + newRange = new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column); + } + } + break; default: break; } @@ -309,10 +318,14 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e // decorations?: IModelDeltaDecoration[]: 一个数组类型的参数,用于指定插入的文本的装饰。可以用来设置文本的样式、颜色、背景色等。如果不需要设置装饰,可以忽略此参数。 const decorations = [{}]; // 解决新增的文本默认背景色为灰色 editor.executeEdits('setValue', [op], decorations); + const addedLastLine = editor.getModel().getLineCount(); + const addedLastLineLength = editor.getModel().getLineMaxColumn(lastLine); + if (range === 'end') { setTimeout(() => { - editor.revealLine(lastLine + 1); - editor.setPosition({ lineNumber: lastLine, column: 1 }); + editor.revealLine(addedLastLine + 1); + editor.setPosition({ lineNumber: addedLastLine, column: addedLastLineLength }); + editor.focus(); }, 0); } }; diff --git a/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx b/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx index 4ac44f3eb..578c3638a 100644 --- a/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/NewMonacoEditor/index.tsx @@ -63,7 +63,6 @@ function SQLEditor({ id, dataSource, database }) { useEffect(() => { // - console.log('dataSource', dataSource); providerKeyword.dispose(); providerKeyword = monaco.languages.registerCompletionItemProvider('sql', { provideCompletionItems: (model, position) => { diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 3199a89da..a6ded368b 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useRef, useState, useImperativeHandle, ForwardedRef, forwardRef } from 'react'; import { connect } from 'umi'; import { formatParams } from '@/utils/common'; import connectToEventSource from '@/utils/eventSource'; @@ -21,7 +21,7 @@ import configService from '@/service/config'; // import NewEditor from './NewMonacoEditor'; import styles from './index.less'; import indexedDB from '@/indexedDB'; -import { isEmpty, set } from 'lodash'; +import { isEmpty } from 'lodash'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -79,7 +79,11 @@ interface IProps { tables: any[]; } -function Console(props: IProps) { +export interface IConsoleRef { + editorRef: IExportRefFunction | undefined; +} + +function Console(props: IProps, ref: ForwardedRef) { const { hasAiChat = true, executeParams, @@ -126,6 +130,10 @@ function Console(props: IProps) { } }, [appendValue]); + useImperativeHandle(ref, () => ({ + editorRef: editorRef?.current, + })); + useEffect(() => { indexedDB .getDataByCursor('chat2db', 'workspaceConsoleDDL', { @@ -265,7 +273,6 @@ function Console(props: IProps) { }); const handleMessage = (message: string) => { - // console.log('message', message); setIsLoading(false); setIsAiDrawerLoading(false); try { @@ -282,7 +289,7 @@ function Console(props: IProps) { }); } if (isNL2SQL) { - editorRef?.current?.setValue('\n\n\n'); + editorRef?.current?.setValue('\n\n'); } else { setIsAiDrawerLoading(false); chatResult.current += '\n\n\n'; @@ -452,7 +459,7 @@ function Console(props: IProps) { }; return ( -
    +
    {hasAiChat && ( { - closeEventSource.current() + closeEventSource.current(); setIsStream(false); setIsLoading(false); }} @@ -550,4 +557,4 @@ const dvaModel = connect(({ ai, loading }: { ai: IAIState; loading: any }) => ({ aiModel: ai, remainingBtnLoading: loading.effects['ai/fetchRemainingUse'], })); -export default dvaModel(Console); +export default dvaModel(forwardRef(Console)); diff --git a/chat2db-client/src/components/ImportConnection/index.tsx b/chat2db-client/src/components/ImportConnection/index.tsx index f7857c609..5a9ed2594 100644 --- a/chat2db-client/src/components/ImportConnection/index.tsx +++ b/chat2db-client/src/components/ImportConnection/index.tsx @@ -42,7 +42,6 @@ const ImportConnection: React.FC = ({ open, onClose, onC formData.append('file', selectedFile); const fileExtension = selectedFile.name.split('.').pop() || ''; - console.log('fileExtension', fileExtension); const { uploadUrl } = uploadFileType[fileExtension] || {}; diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 0a49aa154..9e151f6fb 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -169,20 +169,20 @@ const WorkspaceModel: IWorkspaceModelType = { } }, // 获取当前连接下的表列表 - *fetchGetCurTableList({ payload, callback }, { put, call }) { + *fetchGetCurTableList({ payload, callback }, { put }) { try { const res = (yield treeConfig[TreeNodeType.TABLES].getChildren!({ pageNo: 1, pageSize: 999, ...payload, - })) as ITreeNode[]; + })) as any; // 异步操作完成后调用回调函数 if (callback && typeof callback === 'function') { - callback(res); + callback(res.data); } yield put({ type: 'setCurTableList', - payload: res, + payload: res.data, }); } catch { 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 6bfa172ef..dc0ca0548 100644 --- a/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/chart-item/index.tsx @@ -104,7 +104,6 @@ function ChartItem(props: IChartItemProps) { if (!curConnection) { return; } - console.log(chartData); setChartData({ ...chartData, dataSourceId: curConnection.id, @@ -384,8 +383,7 @@ function ChartItem(props: IChartItemProps) { value={cascaderValue} loadData={loadData} onChange={(value, selectedOptions) => { - console.log('onChange', value, selectedOptions); - let p: any = { + const p: any = { dataSourceId: '', }; //包含了dataSourceId、databaseName、schemaName diff --git a/chat2db-client/src/pages/main/dashboard/index.tsx b/chat2db-client/src/pages/main/dashboard/index.tsx index 3485b7aae..e0958b8f8 100644 --- a/chat2db-client/src/pages/main/dashboard/index.tsx +++ b/chat2db-client/src/pages/main/dashboard/index.tsx @@ -242,7 +242,6 @@ function Chart(props: IProps) { ); }; - // console.log('form', form); return ( <> @@ -264,7 +263,6 @@ function Chart(props: IProps) { onOk={async () => { try { const values = await form.validateFields(); - console.log('Success:', values); const formValue = form.getFieldsValue(true); const { id } = formValue; @@ -278,7 +276,6 @@ function Chart(props: IProps) { form.resetFields(); } catch (errorInfo) { form.resetFields(); - console.log('Failed:', errorInfo); } }} onCancel={() => { diff --git a/chat2db-client/src/pages/main/team/user-management/index.tsx b/chat2db-client/src/pages/main/team/user-management/index.tsx index 0a22501ac..967fbc12a 100644 --- a/chat2db-client/src/pages/main/team/user-management/index.tsx +++ b/chat2db-client/src/pages/main/team/user-management/index.tsx @@ -30,7 +30,7 @@ function UserManagement() { const [isModalVisible, setIsModalVisible] = useState(false); const [drawerInfo, setDrawerInfo] = useState<{ open: boolean; type?: AffiliationType; id?: number }>({ open: false, - }) + }); const columns = useMemo( () => [ @@ -56,29 +56,38 @@ function UserManagement() { width: 260, render: (_: any, record: IUserVO) => ( <> - - - { - form.setFieldsValue(record) - setIsModalVisible(true) - } + form.setFieldsValue(record); + setIsModalVisible(true); + }; const handleDelete = async (id: number) => { - await deleteUser({ id }) - message.success(i18n('common.text.successfullyDelete')) - queryUserList() - } - + await deleteUser({ id }); + message.success(i18n('common.text.successfullyDelete')); + queryUserList(); + }; const isEditing = useMemo(() => { return form.getFieldValue('id') !== undefined; - }, [form.getFieldValue('id')]) - + }, [form.getFieldValue('id')]); - console.log('form', form.getFieldsValue(true)) return (
    @@ -160,10 +166,14 @@ function UserManagement() { onSearch={handleSearch} enterButton={} /> -
    @@ -193,7 +203,7 @@ function UserManagement() { }) .finally(() => { form.resetFields(); - }) + }); }} onCancel={() => { form.resetFields(); @@ -210,19 +220,26 @@ function UserManagement() { }} > - + - - + + - + @@ -245,8 +262,8 @@ function UserManagement() { onClose={() => { setDrawerInfo({ ...drawerInfo, - open: false - }) + open: false, + }); }} />
    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 c671348b8..a6da9e00b 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.less @@ -78,6 +78,16 @@ width: 0px; flex: 1; .f-single-line(); + display: flex; + align-items: center; + .iconBox { + width: 20px; + flex-shrink: 0; + } + .itemName { + flex: 1; + .f-single-line(); + } } .moreButton { flex-shrink: 0; 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 4aee13583..f220af4bc 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -6,7 +6,7 @@ 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 } from '@/constants'; +import { ConsoleStatus, ConsoleOpenedStatus, WorkspaceTabType, workspaceTabConfig } from '@/constants'; import { IConsole, ITreeNode } from '@/typings'; import styles from './index.less'; import { approximateList } from '@/utils'; @@ -166,7 +166,10 @@ const SaveList = dvaModel((props: any) => { className={styles.saveItem} >
    - +
    + +
    +
    { return t.type === data.extraParams?.databaseType; })!; + const monacoEditorRef = useRef(null); const OperationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { [OperationColumn.Refresh]: (data) => { return { @@ -70,7 +70,7 @@ function TreeNodeRightClick(props: IProps) { tableName: data.key, } as any) .then((res) => { - setMonacoDefaultValue(res); + monacoEditorRef.current?.setValue(res); setMonacoVerifyDialog(true); }); }, @@ -81,8 +81,8 @@ function TreeNodeRightClick(props: IProps) { text: '移出', icon: '\ue62a', handle: () => { - connectionServer.remove({ id: +data.key }).then((res) => { - treeConfig[TreeNodeType.DATA_SOURCES]?.getChildren!({} as any).then((res) => { + connectionServer.remove({ id: +data.key }).then(() => { + treeConfig[TreeNodeType.DATA_SOURCES]?.getChildren!({} as any).then(() => { // setTreeData(res); }); }); @@ -163,7 +163,7 @@ function TreeNodeRightClick(props: IProps) { mysqlServer[api]({ ...curWorkspaceParams, tableName: data.key, - } as any).then((res) => { + } as any).then(() => { dispatch({ type: 'workspace/fetchGetCurTableList', payload: { @@ -208,11 +208,11 @@ function TreeNodeRightClick(props: IProps) { function handleOk() { if (verifyTableName === data.key) { - let p: any = { + const p: any = { ...data.extraParams, tableName: data.key, }; - mysqlServer.deleteTable(p).then((res) => { + mysqlServer.deleteTable(p).then(() => { // notificationApi.success( // { // message: i18n('common.text.successfullyDelete'), @@ -278,6 +278,9 @@ function TreeNodeRightClick(props: IProps) { {notificationDom} {!!dropdowns.length && (
    - +
    )} 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 18aa54649..67cbb792c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -344,7 +344,6 @@ const WorkspaceRight = memo((props: IProps) => { registerIntelliSenseTable(data, databaseType, dataSourceId, databaseName, schemaName); registerIntelliSenseField(tableList.current, dataSourceId, databaseName, schemaName); }); - console.log('getAllTable Before:', window._BaseURL); } }, [curWorkspaceParams.databaseType, curWorkspaceParams.databaseName, curWorkspaceParams.schemaName]); @@ -453,7 +452,6 @@ const WorkspaceRight = memo((props: IProps) => { uniqueData: newConsole, }, ]; - console.log(newList); setWorkspaceTabList(newList); setActiveConsoleId(res); }); diff --git a/chat2db-client/src/pages/test/index.tsx b/chat2db-client/src/pages/test/index.tsx index fa1c84da6..912fb434b 100644 --- a/chat2db-client/src/pages/test/index.tsx +++ b/chat2db-client/src/pages/test/index.tsx @@ -17,8 +17,7 @@ function Test() { language={'sql'} // value={value} onChange={(v, e) => { - console.log('onChange', v); - setValue(v) + setValue(v); }} /> diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 3c54272c8..4d7d26ae6 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -211,7 +211,6 @@ export default function createRequest

    (url: string, options?: I }, delayTime); }) .catch((error) => { - console.log('catch error', error); delayTimeFn(() => { errorHandler(error, errorLevel); reject(error); diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index 05d379f96..28a1f3877 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -64,7 +64,6 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa let word; if (match) { word = match[1]; - console.log(word); // 输出: text } if (!word) { diff --git a/chat2db-client/src/utils/eventSource.ts b/chat2db-client/src/utils/eventSource.ts index bc9cc9032..e5df4c683 100644 --- a/chat2db-client/src/utils/eventSource.ts +++ b/chat2db-client/src/utils/eventSource.ts @@ -27,7 +27,6 @@ const connectToEventSource = (params: { }; eventSource.onmessage = (event) => { - // console.log('onmessage', event); onMessage(event.data); }; From 1f10ce56a260f152d003c15619ce7482d2b983a3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 17 Oct 2023 15:46:25 +0800 Subject: [PATCH 0957/1069] style --- chat2db-client/src/components/Console/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index a4f4b7c15..b3fd41437 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -289,10 +289,10 @@ function Console(props: IProps, ref: ForwardedRef) { }); } if (isNL2SQL) { - editorRef?.current?.setValue('\n\n'); + editorRef?.current?.setValue('\n'); } else { setIsAiDrawerLoading(false); - chatResult.current += '\n\n\n'; + chatResult.current += '\n'; setAiContent(chatResult.current); chatResult.current = ''; } From 030ca72640e8102b10e633626a394dc6e64209ff Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 17 Oct 2023 16:38:43 +0800 Subject: [PATCH 0958/1069] feat: Optimize editor --- .../components/Console/ChatInput/index.less | 2 +- .../components/Console/MonacoEditor/index.tsx | 26 ++++++++++++------- .../src/components/Console/index.tsx | 2 ++ chat2db-client/src/models/ai.ts | 16 ++++++------ 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/chat2db-client/src/components/Console/ChatInput/index.less b/chat2db-client/src/components/Console/ChatInput/index.less index 7b6388792..5a78ff68e 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.less +++ b/chat2db-client/src/components/Console/ChatInput/index.less @@ -1,7 +1,7 @@ .chatWrapper { display: flex; align-items: center; - padding: 2px 20px; + padding: 2px 4px 2px 20px; height: 42px; box-sizing: border-box; border-bottom: 1px solid var(--color-border-secondary); diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 8f12279d2..3281679c5 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -34,6 +34,7 @@ export interface IExportRefFunction { getCurrentSelectContent: () => string; getAllContent: () => string; setValue: (text: any, range?: IRangeType) => void; + // toFocus: () => void; } function MonacoEditor(props: IProps, ref: ForwardedRef) { @@ -181,6 +182,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { getCurrentSelectContent, getAllContent, setValue, + // toFocus, })); useEffect(() => { @@ -193,6 +195,10 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { appendMonacoValue(editorRef.current, text, range); }; + const toFocus = () => { + editorRef.current?.focus(); + }; + /** * 获取当前选中的内容 * @returns @@ -318,16 +324,16 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e // decorations?: IModelDeltaDecoration[]: 一个数组类型的参数,用于指定插入的文本的装饰。可以用来设置文本的样式、颜色、背景色等。如果不需要设置装饰,可以忽略此参数。 const decorations = [{}]; // 解决新增的文本默认背景色为灰色 editor.executeEdits('setValue', [op], decorations); - const addedLastLine = editor.getModel().getLineCount(); - const addedLastLineLength = editor.getModel().getLineMaxColumn(lastLine); - - if (range === 'end') { - setTimeout(() => { - editor.revealLine(addedLastLine + 1); - editor.setPosition({ lineNumber: addedLastLine, column: addedLastLineLength }); - editor.focus(); - }, 0); - } + // const addedLastLine = editor.getModel().getLineCount(); + // const addedLastLineLength = editor.getModel().getLineMaxColumn(lastLine); + + // if (range === 'end') { + // setTimeout(() => { + // editor.revealLine(addedLastLine + 1); + // editor.setPosition({ lineNumber: addedLastLine, column: addedLastLineLength }); + // editor.focus(); + // }, 0); + // } }; export default forwardRef(MonacoEditor); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index b3fd41437..a4ca2326d 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -455,6 +455,7 @@ function Console(props: IProps, ref: ForwardedRef) { if (!hasAiAccess || isEmpty(syncModel)) { setSyncTableModel(SyncModelType.MANUAL); + return; } setSyncTableModel(syncModel); @@ -472,6 +473,7 @@ function Console(props: IProps, ref: ForwardedRef) { remainingBtnLoading={props.remainingBtnLoading} tables={tableListName} onPressEnter={(value: string) => { + // editorRef?.current?.toFocus(); handleAiChat(value, IPromptType.NL_2_SQL); }} selectedTables={selectedTables} diff --git a/chat2db-client/src/models/ai.ts b/chat2db-client/src/models/ai.ts index 4d0a5c177..7c6ca4f2a 100644 --- a/chat2db-client/src/models/ai.ts +++ b/chat2db-client/src/models/ai.ts @@ -72,10 +72,10 @@ const AIModel: IAIModelType = { payload: res, }); if (res?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { - yield put({ - type: 'fetchRemainingUse', - payload: { apiKey: res.apiKey }, - }); + // yield put({ + // type: 'fetchRemainingUse', + // payload: { apiKey: res.apiKey }, + // }); yield put({ type: 'updateAiWithWhite', payload: { apiKey: res.apiKey }, @@ -93,10 +93,10 @@ const AIModel: IAIModelType = { payload: aiConfig, }); - yield put({ - type: 'fetchRemainingUse', - payload: { apiKey: aiConfig?.apiKey }, - }); + // yield put({ + // type: 'fetchRemainingUse', + // payload: { apiKey: aiConfig?.apiKey }, + // }); if (aiConfig?.aiSqlSource === AiSqlSourceType.CHAT2DBAI) { yield put({ From 6b6b3b1133b0a3b63b1b6a8c81b5f73576c4b0da Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 17 Oct 2023 17:29:24 +0800 Subject: [PATCH 0959/1069] style --- .../DatabaseTableEditor/ColumnList/index.tsx | 2 ++ .../DatabaseTableEditor/IndexList/index.tsx | 6 ++++-- .../components/Console/MonacoEditor/index.tsx | 16 ++++++++-------- .../src/components/ShortcutKey/index.tsx | 8 ++++---- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index c5be1acc2..35dbb9244 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -138,6 +138,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) key: 'sort', width: '40px', align: 'center', + fixed: 'left', }, // { // title: 'O T', @@ -481,6 +482,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) pagination={false} rowKey="key" columns={columns as any} + scroll={{ x: '100%' }} dataSource={dataSource.filter((i) => i.editStatus !== EditColumnOperationType.Delete)} /> diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx index 30dea5973..9fda707f5 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IndexList/index.tsx @@ -180,6 +180,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = key: 'sort', width: '40px', align: 'center', + fixed: 'left', }, // { // title: i18n('editTable.label.index'), @@ -193,6 +194,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = title: i18n('editTable.label.indexName'), dataIndex: 'name', width: '180px', + fixed: 'left', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( @@ -207,7 +209,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = { title: i18n('editTable.label.indexType'), dataIndex: 'type', - width: '180px', + width: '100px', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( @@ -289,7 +291,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = _columns.splice(3, 0, { title: i18n('editTable.label.indexMethod'), dataIndex: 'method', - width: '180px', + width: '120px', render: (text: string, record: IIndexItem) => { const editable = isEditing(record); return editable ? ( diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 3281679c5..38682808b 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -324,16 +324,16 @@ export const appendMonacoValue = (editor: any, text: any, range: IRangeType = 'e // decorations?: IModelDeltaDecoration[]: 一个数组类型的参数,用于指定插入的文本的装饰。可以用来设置文本的样式、颜色、背景色等。如果不需要设置装饰,可以忽略此参数。 const decorations = [{}]; // 解决新增的文本默认背景色为灰色 editor.executeEdits('setValue', [op], decorations); - // const addedLastLine = editor.getModel().getLineCount(); + const addedLastLine = editor.getModel().getLineCount(); // const addedLastLineLength = editor.getModel().getLineMaxColumn(lastLine); - // if (range === 'end') { - // setTimeout(() => { - // editor.revealLine(addedLastLine + 1); - // editor.setPosition({ lineNumber: addedLastLine, column: addedLastLineLength }); - // editor.focus(); - // }, 0); - // } + if (range === 'end') { + setTimeout(() => { + editor.revealLine(addedLastLine + 1); + // editor.setPosition({ lineNumber: addedLastLine, column: addedLastLineLength }); + // editor.focus(); + }, 0); + } }; export default forwardRef(MonacoEditor); diff --git a/chat2db-client/src/components/ShortcutKey/index.tsx b/chat2db-client/src/components/ShortcutKey/index.tsx index 7f7b99787..99442020e 100644 --- a/chat2db-client/src/components/ShortcutKey/index.tsx +++ b/chat2db-client/src/components/ShortcutKey/index.tsx @@ -36,12 +36,12 @@ const shortcutsList = [ keys: [keyboardKey.command, 'Enter'], }, { - title: i18n('common.text.saveConsole'), - keys: [keyboardKey.command, 'S'], + title: i18n('common.text.executeSelectedSQL'), + keys: [keyboardKey.command, 'R'], }, { - title: i18n('common.text.refreshPage'), - keys: [keyboardKey.command, 'R'], + title: i18n('common.text.saveConsole'), + keys: [keyboardKey.command, 'S'], }, { title: i18n('common.button.createConsole'), From 96cf823bb4ee3caaa333cb40f9de9a953a8bd0f2 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 17 Oct 2023 17:44:34 +0800 Subject: [PATCH 0960/1069] chore: Optimize connection icon --- chat2db-client/src/components/Iconfont/index.tsx | 6 +++--- chat2db-client/src/i18n/en-us/connection.ts | 1 + chat2db-client/src/i18n/zh-cn/connection.ts | 3 ++- chat2db-client/src/pages/main/connection/index.tsx | 12 +++++++++++- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index dd93ec62d..119efe151 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_xwv1edd4b7n.woff2?t=1697513390352') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_xwv1edd4b7n.woff?t=1697513390352') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_xwv1edd4b7n.ttf?t=1697513390352') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_1ru1h66minc.woff2?t=1697535684560') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_1ru1h66minc.woff?t=1697535684560') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_1ru1h66minc.ttf?t=1697535684560') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/i18n/en-us/connection.ts b/chat2db-client/src/i18n/en-us/connection.ts index 56bdceeeb..e886c58e4 100644 --- a/chat2db-client/src/i18n/en-us/connection.ts +++ b/chat2db-client/src/i18n/en-us/connection.ts @@ -2,6 +2,7 @@ export default { 'connection.title.connections': 'Connections', 'connection.title.createConnection': 'New Connection', 'connection.title.editConnection': 'Edit Connection', + 'connection.title.importConnection': 'Import Connection', 'connection.label.name': 'name', 'connection.label.host': 'host', 'connection.label.authentication': 'authentication', diff --git a/chat2db-client/src/i18n/zh-cn/connection.ts b/chat2db-client/src/i18n/zh-cn/connection.ts index 29e72b046..ec479ae2a 100644 --- a/chat2db-client/src/i18n/zh-cn/connection.ts +++ b/chat2db-client/src/i18n/zh-cn/connection.ts @@ -2,6 +2,7 @@ export default { 'connection.title.connections': '连接', 'connection.title.createConnection': '创建数据源', 'connection.title.editConnection': '修改数据源', + 'connection.title.importConnection': '导入数据源', 'connection.label.name': '名称', 'connection.label.host': '主机', 'connection.label.authentication': '身份验证', @@ -29,4 +30,4 @@ export default { 'connection.label.shared': '共享', 'connection.button.createConnection': '创建连接', 'connection.tips.noConnection': '您当前还没有创建任何连接', -} +}; diff --git a/chat2db-client/src/pages/main/connection/index.tsx b/chat2db-client/src/pages/main/connection/index.tsx index 02fc75cdd..7d7a62afc 100644 --- a/chat2db-client/src/pages/main/connection/index.tsx +++ b/chat2db-client/src/pages/main/connection/index.tsx @@ -297,7 +297,17 @@ function Connections(props: IProps) { ); })}

    )} From 8e5ed009dc47da5abc92156dacb42710d92930e2 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 17 Oct 2023 18:09:36 +0800 Subject: [PATCH 0961/1069] fix:index order --- .../src/blocks/DatabaseTableEditor/IncludeCol/index.tsx | 6 +++--- chat2db-client/src/components/Iconfont/index.tsx | 6 +++--- .../src/components/SearchResult/TableBox/index.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index d20d348b4..c53553247 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -133,14 +133,14 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef)
    tables(Connection connection, String databaseName, String schemaName, String tableName) { - return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE"}); + return SQLExecutor.getInstance().tables(connection, StringUtils.isEmpty(databaseName) ? null : databaseName, StringUtils.isEmpty(schemaName) ? null : schemaName, tableName, new String[]{"TABLE","SYSTEM TABLE"}); } @Override diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index f40311fe2..e6aef38de 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -1,60 +1,17 @@ package ai.chat2db.spi.util; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import ai.chat2db.server.tools.common.util.EasyIntegerUtils; +import ai.chat2db.server.tools.base.excption.BusinessException; import ai.chat2db.spi.enums.DataTypeEnum; +import ai.chat2db.spi.model.ExecuteResult; import com.alibaba.druid.DbType; import com.alibaba.druid.sql.SQLUtils; -import com.alibaba.druid.sql.ast.SQLDataTypeImpl; import com.alibaba.druid.sql.ast.SQLStatement; -import com.alibaba.druid.sql.ast.expr.SQLCharExpr; -import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddColumn; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableAddConstraint; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropColumnItem; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableDropPrimaryKey; -import com.alibaba.druid.sql.ast.statement.SQLAlterTableStatement; -import com.alibaba.druid.sql.ast.statement.SQLAssignItem; -import com.alibaba.druid.sql.ast.statement.SQLColumnDefinition; -import com.alibaba.druid.sql.ast.statement.SQLColumnPrimaryKey; -import com.alibaba.druid.sql.ast.statement.SQLCreateIndexStatement; -import com.alibaba.druid.sql.ast.statement.SQLDropIndexStatement; import com.alibaba.druid.sql.ast.statement.SQLExprTableSource; import com.alibaba.druid.sql.ast.statement.SQLJoinTableSource; -import com.alibaba.druid.sql.ast.statement.SQLNotNullConstraint; -import com.alibaba.druid.sql.ast.statement.SQLNullConstraint; -import com.alibaba.druid.sql.ast.statement.SQLSelectOrderByItem; import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.ast.statement.SQLTableSource; -import com.alibaba.druid.sql.dialect.mysql.ast.MySqlPrimaryKey; -import com.alibaba.druid.sql.dialect.mysql.ast.MySqlUnique; -import com.alibaba.druid.sql.dialect.mysql.ast.expr.MySqlCharExpr; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableChangeColumn; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlAlterTableModifyColumn; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlCreateTableStatement; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlRenameTableStatement.Item; -import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlTableIndex; import com.alibaba.druid.sql.parser.SQLParserUtils; - -import ai.chat2db.server.tools.base.excption.BusinessException; -import ai.chat2db.server.tools.common.util.EasyBooleanUtils; -import ai.chat2db.server.tools.common.util.EasyCollectionUtils; -import ai.chat2db.server.tools.common.util.EasyEnumUtils; -import ai.chat2db.spi.enums.CollationEnum; -import ai.chat2db.spi.enums.IndexTypeEnum; -import ai.chat2db.spi.model.Sql; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; -import ai.chat2db.spi.model.TableIndexColumn; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; @@ -62,10 +19,13 @@ import net.sf.jsqlparser.statement.select.Select; import net.sf.jsqlparser.statement.select.SelectExpressionItem; import net.sf.jsqlparser.statement.select.SelectItem; -import org.apache.commons.collections4.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + /** * @author jipengfei * @version : SqlUtils.java @@ -74,8 +34,7 @@ public class SqlUtils { public static final String DEFAULT_TABLE_NAME = "table1"; - - public static boolean canEdit(String sql) { + public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult executeResult) { try { Statement statement = CCJSqlParserUtil.parse(sql); if (statement instanceof Select) { @@ -86,388 +45,28 @@ public static boolean canEdit(String sql) { if (item instanceof SelectExpressionItem) { SelectExpressionItem expressionItem = (SelectExpressionItem) item; if (expressionItem.getAlias() != null) { - return false; // 找到了一个别名 + //canEdit = false; // 找到了一个别名 + executeResult.setCanEdit(false); + return ; } } } - return true; - } - } - } catch (Exception e) { - return false; - } - return false; - } - - public static List buildSql(Table oldTable, Table newTable) { - List sqlList = new ArrayList<>(); - // 创建表 - if (oldTable == null) { - MySqlCreateTableStatement mySqlCreateTableStatement = new MySqlCreateTableStatement(); - mySqlCreateTableStatement.setDbType(DbType.mysql); - mySqlCreateTableStatement.setTableName(newTable.getName()); - if (!Objects.isNull(newTable.getComment())) { - mySqlCreateTableStatement.setComment(new MySqlCharExpr(newTable.getComment())); - } - List columnList = newTable.getColumnList(); - if (!CollectionUtils.isEmpty(columnList)) { - for (TableColumn tableColumn : columnList) { - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - mySqlCreateTableStatement.addColumn(sqlColumnDefinition); - sqlColumnDefinition.setName(tableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(tableColumn.getColumnType())); - if (tableColumn.getNullable() == 1) { - sqlColumnDefinition.addConstraint(new SQLNullConstraint()); - } else { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(tableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(tableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(tableColumn.getAutoIncrement())); - if (!Objects.isNull(tableColumn.getComment())) { - sqlColumnDefinition.setComment(tableColumn.getComment()); - } - if (BooleanUtils.isTrue(tableColumn.getPrimaryKey())) { - sqlColumnDefinition.addConstraint(new SQLColumnPrimaryKey()); - } - } - //// 主键 - //List primaryKeyColumnList = EasyCollectionUtils.stream(columnList) - // .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - // .collect(Collectors.toList()); - //if (!CollectionUtils.isEmpty(primaryKeyColumnList)) { - // MySqlPrimaryKey mySqlPrimaryKey = new MySqlPrimaryKey(); - // mySqlCreateTableStatement.getTableElementList().add(mySqlPrimaryKey); - // for (TableColumn tableColumn : primaryKeyColumnList) { - // mySqlPrimaryKey.addColumn(new SQLIdentifierExpr(tableColumn.getName())); - // } - //} - } - - // 索引 - List indexList = newTable.getIndexList(); - if (!CollectionUtils.isEmpty(indexList)) { - for (TableIndex tableIndex : indexList) { - if (IndexTypeEnum.UNIQUE.getCode().equals(tableIndex.getType())) { - MySqlUnique mySqlUnique = new MySqlUnique(); - mySqlCreateTableStatement.getTableElementList().add(mySqlUnique); - mySqlUnique.setName(tableIndex.getName()); - mySqlUnique.setComment(new SQLCharExpr(tableIndex.getComment())); - mySqlUnique.getIndexDefinition().setType("unique"); - if (!CollectionUtils.isEmpty(tableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : tableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - mySqlUnique.addColumn(sqlSelectOrderByItem); - } - } - } else { - MySqlTableIndex mySqlTableIndex = new MySqlTableIndex(); - mySqlCreateTableStatement.getTableElementList().add(mySqlTableIndex); - mySqlTableIndex.setName(tableIndex.getName()); - mySqlTableIndex.setComment(new SQLCharExpr(tableIndex.getComment())); - if (!CollectionUtils.isEmpty(tableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : tableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - mySqlTableIndex.addColumn(sqlSelectOrderByItem); - } - } + executeResult.setCanEdit(true); + SQLStatement sqlStatement = SQLUtils.parseSingleStatement(sql, dbType); + if ((sqlStatement instanceof SQLSelectStatement sqlSelectStatement)) { + SQLExprTableSource sqlExprTableSource = (SQLExprTableSource) getSQLExprTableSource( + sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); + executeResult.setTableName(getMetaDataTableName(sqlExprTableSource.getCatalog(), sqlExprTableSource.getSchema(), sqlExprTableSource.getTableName())); } } } - - sqlList.add(Sql.builder().sql(mySqlCreateTableStatement + ";").build()); - return sqlList; - } - - // 修改表结构 - // 修改表名字 - if (!StringUtils.equals(oldTable.getName(), newTable.getName())) { - MySqlRenameTableStatement mySqlRenameTableStatement = new MySqlRenameTableStatement(); - mySqlRenameTableStatement.setDbType(DbType.mysql); - Item item = new Item(); - item.setName(new SQLIdentifierExpr(oldTable.getName())); - item.setTo(new SQLIdentifierExpr(newTable.getName())); - mySqlRenameTableStatement.addItem(item); - sqlList.add(Sql.builder().sql(mySqlRenameTableStatement + ";").build()); - } - // 修改注释 - if (!StringUtils.equals(oldTable.getComment(), newTable.getComment())) { - SQLAlterTableStatement sqlAlterTableStatement = new SQLAlterTableStatement(); - sqlAlterTableStatement.setDbType(DbType.mysql); - SQLAssignItem sqlAssignItem = new SQLAssignItem(); - sqlAssignItem.setTarget(new SQLIdentifierExpr("COMMENT")); - sqlAssignItem.setValue(new MySqlCharExpr(newTable.getComment())); - sqlAlterTableStatement.getTableOptions().add(sqlAssignItem); - sqlList.add(Sql.builder().sql(sqlAlterTableStatement + ";").build()); - } - // 修改字段 - modifyColumn(sqlList, oldTable, newTable); - - // 修改索引 - modifyIndex(sqlList, oldTable, newTable); - return sqlList; - } - - private static void modifyColumn(List sqlList, Table oldTable, Table newTable) { - Map oldColumnMap = EasyCollectionUtils.toIdentityMap(oldTable.getColumnList(), - tableColumn -> { - if (tableColumn.getOldName() != null) { - return tableColumn.getOldName(); - } - return tableColumn.getName(); - }); - Map newColumnMap = EasyCollectionUtils.toIdentityMap(newTable.getColumnList(), - tableColumn -> { - if (tableColumn.getOldName() != null) { - return tableColumn.getOldName(); - } - return tableColumn.getName(); - }); - - SQLAlterTableStatement sqlAlterTableStatement = new SQLAlterTableStatement(); - sqlAlterTableStatement.setDbType(DbType.mysql); - sqlAlterTableStatement.setTableSource(new SQLIdentifierExpr(newTable.getName())); - - newColumnMap.forEach((newTableColumnName, newTableColumn) -> { - TableColumn oldTableColumn = oldColumnMap.get(newTableColumnName); - // 代表新增字段 - if (oldTableColumn == null) { - - SQLAlterTableAddColumn sqlAlterTableAddColumn = new SQLAlterTableAddColumn(); - sqlAlterTableStatement.addItem(sqlAlterTableAddColumn); - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - sqlAlterTableAddColumn.addColumn(sqlColumnDefinition); - sqlColumnDefinition.setName(newTableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (newTableColumn.getNullable() != 1) { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(newTableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(newTableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(newTableColumn.getAutoIncrement())); - if (!Objects.isNull(newTableColumn.getComment())) { - sqlColumnDefinition.setComment(newTableColumn.getComment()); - } - return; - } - // 代表可能修改字段 或者没变 - boolean hasChange = !StringUtils.equals(oldTableColumn.getName(), newTableColumn.getName()) - || !StringUtils.equals(oldTableColumn.getColumnType(), newTableColumn.getColumnType()) - || !EasyIntegerUtils.equals(oldTableColumn.getNullable(), newTableColumn.getNullable(), 1) - || !StringUtils.equals(oldTableColumn.getDefaultValue(), newTableColumn.getDefaultValue()) - || !EasyBooleanUtils.equals(oldTableColumn.getAutoIncrement(), newTableColumn.getAutoIncrement(), - Boolean.FALSE) - || !StringUtils.equals(oldTableColumn.getComment(), newTableColumn.getComment()); - - // 没有修改字段 - if (!hasChange) { - return; - } - - // 修改字段包含字段名 - if (!StringUtils.equals(oldTableColumn.getName(), newTableColumn.getName())) { - MySqlAlterTableChangeColumn mySqlAlterTableChangeColumn = new MySqlAlterTableChangeColumn(); - sqlAlterTableStatement.addItem(mySqlAlterTableChangeColumn); - mySqlAlterTableChangeColumn.setColumnName(new SQLIdentifierExpr(newTableColumn.getOldName())); - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - mySqlAlterTableChangeColumn.setNewColumnDefinition(sqlColumnDefinition); - sqlColumnDefinition.setName(newTableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (newTableColumn.getNullable() != 1) { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(newTableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(newTableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(newTableColumn.getAutoIncrement())); - if (!Objects.isNull(newTableColumn.getComment())) { - sqlColumnDefinition.setComment(newTableColumn.getComment()); - } - } else { - // 修改字段不包括字段名 - MySqlAlterTableModifyColumn mySqlAlterTableModifyColumn = new MySqlAlterTableModifyColumn(); - sqlAlterTableStatement.addItem(mySqlAlterTableModifyColumn); - SQLColumnDefinition sqlColumnDefinition = new SQLColumnDefinition(); - mySqlAlterTableModifyColumn.setNewColumnDefinition(sqlColumnDefinition); - sqlColumnDefinition.setName(newTableColumn.getName()); - sqlColumnDefinition.setDataType(new SQLDataTypeImpl(newTableColumn.getColumnType())); - if (newTableColumn.getNullable() != 1) { - sqlColumnDefinition.addConstraint(new SQLNotNullConstraint()); - } - if (!Objects.isNull(newTableColumn.getDefaultValue())) { - sqlColumnDefinition.setDefaultExpr(new MySqlCharExpr(newTableColumn.getDefaultValue())); - } - sqlColumnDefinition.setAutoIncrement(BooleanUtils.isTrue(newTableColumn.getAutoIncrement())); - if (!Objects.isNull(newTableColumn.getComment())) { - sqlColumnDefinition.setComment(newTableColumn.getComment()); - } - } - }); - - oldColumnMap.forEach((oldTableColumnName, oldTableColumn) -> { - TableColumn newTableColumn = newColumnMap.get(oldTableColumnName); - // 代表删除字段 - if (newTableColumn == null) { - SQLAlterTableDropColumnItem sqlAlterTableDropColumnItem = new SQLAlterTableDropColumnItem(); - sqlAlterTableStatement.addItem(sqlAlterTableDropColumnItem); - sqlAlterTableDropColumnItem.addColumn(new SQLIdentifierExpr(oldTableColumn.getName())); - } - }); - - // 比较主键是否有修改 - // 主键 - Set oldPrimaryKeySet = EasyCollectionUtils.stream(oldTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .collect(Collectors.toSet()); - Set newPrimaryKeySet = EasyCollectionUtils.stream(newTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .collect(Collectors.toSet()); - boolean primaryKeyChange = oldPrimaryKeySet.stream() - .anyMatch(oldPrimaryKey -> !newPrimaryKeySet.contains(oldPrimaryKey)) - || newPrimaryKeySet.stream() - .anyMatch(newPrimaryKey -> !oldPrimaryKeySet.contains(newPrimaryKey)); - if (primaryKeyChange) { - sqlAlterTableStatement.addItem(new SQLAlterTableDropPrimaryKey()); - SQLAlterTableAddConstraint sqlAlterTableAddConstraint = new SQLAlterTableAddConstraint(); - sqlAlterTableStatement.addItem(sqlAlterTableAddConstraint); - MySqlPrimaryKey mySqlPrimaryKey = new MySqlPrimaryKey(); - sqlAlterTableAddConstraint.setConstraint(mySqlPrimaryKey); - mySqlPrimaryKey.setIndexType("PRIMARY"); - // 排序 - EasyCollectionUtils.stream(newTable.getColumnList()) - .filter(tableColumn -> BooleanUtils.isTrue(tableColumn.getPrimaryKey())) - .map(TableColumn::getName) - .forEach(tableColumnName -> mySqlPrimaryKey.addColumn( - new SQLSelectOrderByItem(new SQLIdentifierExpr(tableColumnName)))); - } - - if (CollectionUtils.isNotEmpty(sqlAlterTableStatement.getItems())) { - sqlList.add(Sql.builder().sql(sqlAlterTableStatement + ";").build()); + } catch (Exception e) { + executeResult.setCanEdit(false); } } - private static void modifyIndex(List sqlList, Table oldTable, Table newTable) { - Map oldIndexMap = EasyCollectionUtils.toIdentityMap(oldTable.getIndexList(), - TableIndex::getName); - Map newIndexMap = EasyCollectionUtils.toIdentityMap(newTable.getIndexList(), - TableIndex::getName); - newIndexMap.forEach((newTableIndexName, newTableIndex) -> { - TableIndex oldTableIndex = oldIndexMap.get(newTableIndexName); - // 代表新增索引 - if (oldTableIndex == null) { - SQLCreateIndexStatement sqlCreateIndexStatement = new SQLCreateIndexStatement(); - sqlCreateIndexStatement.setTable(new SQLExprTableSource(newTable.getName())); - sqlCreateIndexStatement.setName(new SQLIdentifierExpr(newTableIndex.getName())); - if (!Objects.isNull(newTableIndex.getComment())) { - sqlCreateIndexStatement.setComment(new SQLCharExpr(newTableIndex.getComment())); - } - if (!CollectionUtils.isEmpty(newTableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : newTableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - sqlCreateIndexStatement.getColumns().add(sqlSelectOrderByItem); - } - } - sqlList.add(Sql.builder().sql(sqlCreateIndexStatement + ";").build()); - return; - } - // 代表可能修改索引 或者没变 - boolean hasChange = !StringUtils.equals(oldTableIndex.getName(), newTableIndex.getName()) - || !StringUtils.equals(oldTableIndex.getComment(), newTableIndex.getComment()) - || !Objects.equals(oldTableIndex.getUnique(), newTableIndex.getUnique()); - if (!hasChange) { - Map oldTableIndexColumnMap = EasyCollectionUtils.toIdentityMap( - oldTableIndex.getColumnList(), TableIndexColumn::getColumnName); - Map newTableIndexColumnMap = EasyCollectionUtils.toIdentityMap( - newTableIndex.getColumnList(), TableIndexColumn::getColumnName); - hasChange = oldTableIndexColumnMap.entrySet() - .stream() - .anyMatch(oldTableIndexColumnEntry -> { - TableIndexColumn newTableIndexColumn = newTableIndexColumnMap.get( - oldTableIndexColumnEntry.getKey()); - if (newTableIndexColumn == null) { - return true; - } - TableIndexColumn oldTableIndexColumn = oldTableIndexColumnEntry.getValue(); - return !StringUtils.equals(oldTableIndexColumn.getColumnName(), - newTableIndexColumn.getColumnName()) - || !CollationEnum.equals(oldTableIndexColumn.getCollation(), - newTableIndexColumn.getCollation()); - }) - || newTableIndexColumnMap.entrySet() - .stream() - .anyMatch(newTableIndexColumnEntry -> { - TableIndexColumn oldTableIndexColumn = oldTableIndexColumnMap.get( - newTableIndexColumnEntry.getKey()); - return oldTableIndexColumn == null; - }); - } - - // 没有修改索引 - if (!hasChange) { - return; - } - // 先删除 - SQLDropIndexStatement sqlDropIndexStatement = new SQLDropIndexStatement(); - sqlDropIndexStatement.setDbType(DbType.mysql); - sqlDropIndexStatement.setTableName(new SQLExprTableSource(newTable.getName())); - sqlDropIndexStatement.setIndexName(new SQLIdentifierExpr(newTableIndex.getName())); - sqlList.add(Sql.builder().sql(sqlDropIndexStatement + ";").build()); - - // 再新增 - SQLCreateIndexStatement sqlCreateIndexStatement = new SQLCreateIndexStatement(); - sqlCreateIndexStatement.setTable(new SQLExprTableSource(newTable.getName())); - sqlCreateIndexStatement.setName(new SQLIdentifierExpr(newTableIndex.getName())); - if (!Objects.isNull(newTableIndex.getComment())) { - sqlCreateIndexStatement.setComment(new SQLCharExpr(newTableIndex.getComment())); - } - if (!CollectionUtils.isEmpty(newTableIndex.getColumnList())) { - for (TableIndexColumn tableIndexColumn : newTableIndex.getColumnList()) { - SQLSelectOrderByItem sqlSelectOrderByItem = new SQLSelectOrderByItem(); - sqlSelectOrderByItem.setExpr(new SQLIdentifierExpr(tableIndexColumn.getColumnName())); - CollationEnum collation = EasyEnumUtils.getEnum(CollationEnum.class, - tableIndexColumn.getCollation()); - if (collation != null) { - sqlSelectOrderByItem.setType(collation.getSqlOrderingSpecification()); - } - sqlCreateIndexStatement.getColumns().add(sqlSelectOrderByItem); - } - } - sqlList.add(Sql.builder().sql(sqlCreateIndexStatement + ";").build()); - }); - - oldIndexMap.forEach((oldTableIndexName, oldTableIndex) -> { - TableIndex newTableIndex = newIndexMap.get(oldTableIndexName); - // 代表删除索引 - if (newTableIndex == null) { - SQLDropIndexStatement sqlDropIndexStatement = new SQLDropIndexStatement(); - sqlDropIndexStatement.setDbType(DbType.mysql); - sqlDropIndexStatement.setTableName(new SQLExprTableSource(newTable.getName())); - sqlDropIndexStatement.setIndexName(new SQLIdentifierExpr(oldTableIndex.getName())); - sqlList.add(Sql.builder().sql(sqlDropIndexStatement + ";").build()); - } - }); + private static String getMetaDataTableName(String... names) { + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> name ).collect(Collectors.joining(".")); } public static String formatSQLString(Object para) { @@ -511,7 +110,7 @@ public static List parse(String sql, DbType dbType) { } public static String getSqlValue(String value, String dataType) { - if(value == null){ + if (value == null) { return null; } DataTypeEnum dataTypeEnum = DataTypeEnum.getByCode(dataType); From e2bd2a0194552bef931d6735ce958c1938ee97a1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 18 Oct 2023 20:18:37 +0800 Subject: [PATCH 0992/1069] fix:umi file timestamp --- .github/workflows/pushdocker.yml | 1 + .github/workflows/release.yml | 1 + .github/workflows/release_test.yml | 1 + .vscode/settings.json | 1 + chat2db-client/.umirc.prod.desktop.ts | 1 + chat2db-client/.umirc.prod.ts | 1 + chat2db-client/.umirc.ts | 8 +++ chat2db-client/src/i18n/en-us/common.ts | 4 +- .../src/main/resources/thymeleaf/index.html | 67 ++++++------------- 9 files changed, 35 insertions(+), 50 deletions(-) diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index 10a55bbc1..fbad111a8 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -50,6 +50,7 @@ jobs: yarn install yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} 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: Install Java and Maven diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 730645742..71c096913 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -140,6 +140,7 @@ jobs: yarn install yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} 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 diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index e518c8093..2f0d40be9 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -134,6 +134,7 @@ jobs: 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 diff --git a/.vscode/settings.json b/.vscode/settings.json index 4dc5665e6..fcf08b845 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -46,6 +46,7 @@ "Sercurity", "sortablejs", "temurin", + "thymeleaf", "Tigger", "togglefullscreen", "umijs", diff --git a/chat2db-client/.umirc.prod.desktop.ts b/chat2db-client/.umirc.prod.desktop.ts index 8fc0c0132..6207631e9 100644 --- a/chat2db-client/.umirc.prod.desktop.ts +++ b/chat2db-client/.umirc.prod.desktop.ts @@ -10,6 +10,7 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); + config.output.filename(`[name].${yarn_config.app_version || new Date().getTime()}.js`); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.prod.ts b/chat2db-client/.umirc.prod.ts index c0ff508ed..b7c86ba41 100644 --- a/chat2db-client/.umirc.prod.ts +++ b/chat2db-client/.umirc.prod.ts @@ -10,6 +10,7 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); + config.output.filename(`[name].${yarn_config.app_version || new Date().getTime()}.js`); }; export default defineConfig({ diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index e19ed77c9..063056d9c 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -12,6 +12,7 @@ const chainWebpack = (config: any, { webpack }: any) => { languages: ['mysql', 'pgsql', 'sql'], }, ]); + config.output.filename(`[name].${yarn_config.app_version || new Date().getTime()}.js`); }; export default defineConfig({ @@ -50,6 +51,13 @@ export default defineConfig({ // rel: 'manifest', // href: 'manifest.json', // }], + links: [ + { rel:"icon", + type:"image/ico", + sizes:"32x32", + href:"/static/front/logo.ico" + } + ], headScripts: [ `if (localStorage.getItem('app-local-storage-versions') !== 'v3') { localStorage.clear(); diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index cd8006d56..182121d9b 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -1,6 +1,6 @@ export default { - 'common.text.no': 'is', - 'common.text.is': 'no', + 'common.text.no': 'no', + 'common.text.is': 'is', 'common.button.affirm': 'Affirm', 'common.button.edit': 'Edit', 'common.button.modify': 'Modify', diff --git a/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html index 9916759a3..9d238cfc2 100644 --- a/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html +++ b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/index.html @@ -1,52 +1,23 @@ - - - - - - Chat2DB - - - - - - - + + + + + +Chat2DB + + + -
    - + - - - + \ No newline at end of file From dc6bafaa4537dbf9b011336294d887196e450db3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Wed, 18 Oct 2023 23:29:28 +0800 Subject: [PATCH 0993/1069] readme --- README.md | 29 +++++---------- README_CN.md | 36 ++++++++----------- .../workspace/components/TableList/index.less | 2 +- .../components/WorkspaceLeft/index.less | 2 +- 4 files changed, 25 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index d1dcf1327..334e6bdea 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/79e9dded-375b-44cf-9979-bb757 [Downloading installation package from GitHub](https://github.com/chat2db/Chat2DB/releases) -[Downloading installation package from official website](https://sqlgpt.cn/docs/guides/download) +[Downloading installation package from official website](https://sqlgpt.cn) @@ -94,25 +94,7 @@ Redis and MongoDB are partially supported , Hbase、Elasticsearch、openGauss、 ## 🔥 AI Configuration -### CONFIGURE OPENAI - -Option 1 (recommended): To use the ChatSql function of OPENAI, two conditions must be met: - -- You need an OPENAI_API_KEY. -- The client's network can connect to the OPENAI website, and for users in China, a VPN is required. Note: If the local VPN is not fully effective, the network connectivity can be ensured by setting the network proxy HOST and PORT in the client. - -3 - - -Option 2 (recommended): We provide a unified proxy service. - -- No OPENAI_API_KEY is required. -- No proxy or VPN is required, as long as the network is connected. - -To facilitate users' quick use of AI capabilities, you can scan the QR code below to follow our WeChat public account and apply for our custom API_KEY. - -4 - +### Use Chat2DB AI to get started ### CONFIGURE CUSTOM AI @@ -169,6 +151,13 @@ $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar #java 17 or later must be installed, To launch the chat application, you need to enter the ChatGPT key for the chatgpt.apiKey. Without entering it, you won't be able to use the AIGC function. ``` +- If you need to deploy independently + +```bash +$ npm run build:web:prod / 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 +# Repackage the back-end services +``` + ## 📑 Documentation -
    Official website document diff --git a/README_CN.md b/README_CN.md index ef901439f..b64785ee7 100644 --- a/README_CN.md +++ b/README_CN.md @@ -52,7 +52,7 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 或 -[官网下载安装包](https://sqlgpt.cn/docs/guides/download) +[官网下载安装包](https://sqlgpt.cn) ## 🚀 支持的数据库 @@ -72,7 +72,7 @@ Chat2DB 支持的数据库连接有: - Hive - KingBase -Redis and MongoDB are partially supported , Hbase、Elasticsearch、openGauss、TiDB、InfluxDB will support in the future. +Redis和MongoDB得到部分支持,Hbase、Elasticsearch、openGauss、TiDB、InfluxDB将在未来得到支持。 ## 🌰 使用 Demo @@ -85,31 +85,17 @@ Redis and MongoDB are partially supported , Hbase、Elasticsearch、openGauss、 -### SQL 控制台 及 AI 智能助手 +### SQL 控制台 -#### 使用前需要配置 OpenAI 的 Api Key 及本地代理配置 +2 - - +### AI 智能助手 -## 🔥 AI 配置 +![image](https://github.com/chat2db/Chat2DB/assets/22975773/2dfc4aaa-c5a3-42c3-bc61-28ebc237a27b) -### 使用 ChatGPT +## 🔥 AI -方式一(推荐):使用 OPENAI 的 ChatSql 功能需要满足两个条件 - -- 1、需要有一个 openAI 的 key:OPENAI_API_KEY -- 2、客户端网络可以连接到 OPENAI 官网,国内需要科学上网。注意:如果本地 VPN 未能全局生效,可以通过在客户端中设置网络代理 HOST 和 PORT 来保证网络连通性 -- - -方式二(推荐):使用我们提供了一个统一的代理服务。 - -- 1、不需要 openAI 的 key -- 2、不需要代理,不需要 VPN 只要可以联网即可使用。 - -为了方便大家更快速的使用 AI 的能力,可以关注微信公众号,回复"AI" 获得我们的自定义 API_KEY,申请完成之后参考下图进行配置即可进行使用 - - +### 使用Chat2DB AI 上手即用 ### 使用自定义大模型 - [参考这里部署本地ChatGLM-6B模型](https://github.com/chat2db/chat2db-chatglm-6b-deploy/blob/main/README_CN.md) @@ -163,6 +149,12 @@ $ cd chat2db-server/chat2db-server-start/target/ $ java -jar -Dloader.path=./lib -Dchatgpt.apiKey=xxxxx chat2db-server-start.jar # 需要安装java 17以上版本,启动应用 chatgpt.apiKey 需要输入ChatGPT的key,如果不输入无法使用AIGC功能 ``` +- 如果你需要独立部署 +```bash +$ npm run build:web:prod / 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 +# 再打包后端服务 +``` + ## 📑 文档 - 官方文档 diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index f897ee7b8..24690e33a 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -10,7 +10,7 @@ .leftModuleTitle { flex-shrink: 0; margin-bottom: 4px; - padding: 0px 10px; + padding: 4px 10px; .leftModuleTitleText { // 下阴影 display: flex; 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 f3e578060..5ceea91b3 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceLeft/index.less @@ -14,5 +14,5 @@ } .divider { - margin: 0px 0px 10px; + margin: 0px; } From ce0cb660f81732dcdd15dbd1fe31cd62bc980d22 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Wed, 18 Oct 2023 23:48:36 +0800 Subject: [PATCH 0994/1069] =?UTF-8?q?chroe:=20=E4=BF=AE=E6=94=B9changelog?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b1573ff2..19f8d69ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # 3.0.0 `2023-10-17` -## Changelog +**Changelog** - 🔥【New Features】Support for team collaboration mode - 🔥【New Features】Support for visual table structure creation, editing, and deletion - 🔥【New Features】Support for editing, adding, and deleting query data results @@ -18,7 +18,8 @@ - ⚡️【Optimize】`Cmd/Ctrl + R` Run SQL, `Cmd/Ctrl + Shift + R` Refresh Page - 🐞【Fixed】Table operation columns are overridden by table comments - 🐞【Fixed】The last Tab in the query result cannot be closed -## 更新日志 + +**更新日志** - 🔥【新功能】支持团队协作模式 - 🔥【新功能】支持可视化表结构新增、编辑、删除 - 🔥【新功能】支持查询数据结果编辑、新增、删除 From 8ff8d3519a4ab873b83d9d7e5ae583409a830a03 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 00:02:40 +0800 Subject: [PATCH 0995/1069] feat:Add a switch database prompt when the table is empty --- chat2db-client/src/i18n/en-us/common.ts | 2 ++ chat2db-client/src/i18n/zh-cn/common.ts | 2 ++ .../workspace/components/TableList/index.less | 16 ++++++++++++++++ .../workspace/components/TableList/index.tsx | 10 +++++++++- 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index 182121d9b..fd1387383 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -92,4 +92,6 @@ export default { 'common.button.executionError': 'Execution Error', 'common.text.affectedRows': 'Affected rows: {1}', 'common.text.selectFile' : 'Select File', + 'common.text.noTableFoundUp' : 'No tables in this database', + 'common.text.noTableFoundDown' : 'Switch databases at the top' }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 971a3aae8..770848652 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -90,4 +90,6 @@ export default { 'common.button.executionError': '执行错误', 'common.text.affectedRows': '受影响行:{1}', 'common.text.selectFile' : '选择文件', + 'common.text.noTableFoundUp' : '当前库没有查询到表', + 'common.text.noTableFoundDown' : '你可以在顶部切换数据库' }; diff --git a/chat2db-client/src/pages/main/workspace/components/TableList/index.less b/chat2db-client/src/pages/main/workspace/components/TableList/index.less index 24690e33a..fb2b8d336 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.less +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.less @@ -62,6 +62,22 @@ box-shadow: 0px 1px 2px 0px var(--color-border); } +.emptyBox{ + display: flex; + flex-direction: column; + align-items: center; + height: 100%; + width: 80%; + margin: 0 auto; + padding-top: 40px; + box-sizing: border-box; + color: var(--color-text-quaternary); + >div{ + text-align: center; + line-height: 20px; + } +} + .treeBox { flex: 1; height: 0; 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 38605a080..47e975822 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -439,7 +439,15 @@ const TableList = dvaModel((props: any) => {
    - + { + (curType.value === TreeNodeType.TABLES && !curList.length) ? +
    +
    {i18n('common.text.noTableFoundUp')}
    +
    {i18n('common.text.noTableFoundDown')}
    +
    + : + + }
    From 3969aaf9635aceece3b01d4eeacdb6688e739251 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 09:51:52 +0800 Subject: [PATCH 0996/1069] fix:Scroll bar width Auto-save bug --- .../src/components/Console/index.tsx | 32 ++++++++----------- .../SearchResult/TableBox/index.less | 6 ++++ .../SearchResult/TableBox/index.tsx | 19 +++++++++++ chat2db-client/src/styles/global.less | 4 +-- 4 files changed, 41 insertions(+), 20 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 2eb830ba1..ace767ab0 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -21,7 +21,6 @@ import configService from '@/service/config'; // import NewEditor from './NewMonacoEditor'; import styles from './index.less'; import indexedDB from '@/indexedDB'; -import { isEmpty } from 'lodash'; enum IPromptType { NL_2_SQL = 'NL_2_SQL', @@ -135,18 +134,7 @@ function Console(props: IProps, ref: ForwardedRef) { })); useEffect(() => { - indexedDB - .getDataByCursor('chat2db', 'workspaceConsoleDDL', { - consoleId: executeParams.consoleId!, - userId: getCookie('CHAT2DB.USER_ID'), - }) - .then((res: any) => { - const value = res?.[0]?.ddl || ''; - if (value) { - editorRef?.current?.setValue(value, 'reset'); - initializeSuccessful.current = true; - } - }); + }, []); useEffect(() => { @@ -166,7 +154,19 @@ function Console(props: IProps, ref: ForwardedRef) { } } else { // 活跃时自动保存 - timingAutoSave(); + indexedDB + .getDataByCursor('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + userId: getCookie('CHAT2DB.USER_ID'), + }) + .then((res: any) => { + const value = res?.[0]?.ddl || ''; + if (value) { + editorRef?.current?.setValue(value, 'reset'); + initializeSuccessful.current = true; + timingAutoSave(); + } + }); } return () => { if (timerRef.current) { @@ -176,10 +176,6 @@ function Console(props: IProps, ref: ForwardedRef) { }, [isActive]); function timingAutoSave() { - // 如果没有初始化那就不自动保存 - if (!initializeSuccessful.current) { - return; - } timerRef.current = setInterval(() => { indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { consoleId: executeParams.consoleId!, diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index df6101022..68993c056 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -61,6 +61,12 @@ .art-table-row{ height: 32px; } + .art-table-header { + background: transparent !important; + } + .art-table-body-scroll{ + padding-right: 6px; + } .art-table-header-cell{ padding: 0px 4px; } diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 4b08195be..ebd849b25 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -120,6 +120,25 @@ export default function TableBox(props: ITableProps) { }); }, [queryResultData]); + // 判断art-table-body是否出现了滚动条 + // useEffect(() => { + // const tableBody = document.querySelector('.art-table-body'); + // const tableHeader = document.querySelector('.art-table-header'); + // if (!tableBody) { + // return; + // } + // const tableBodyHeight = tableBody.clientHeight; + // const tableBoxHeight = tableBoxRef.current?.clientHeight || 0; + // if(!tableHeader){ + // return; + // } + // if (tableBodyHeight > tableBoxHeight) { + // tableHeader.classList.add('art-table-body-scroll'); + // } else { + // tableHeader.classList.remove('art-table-body-scroll'); + // } + // }, [tableData]); + useEffect(() => { // 每次dataList变化,都需要重新计算tableData if (!columns?.length) { diff --git a/chat2db-client/src/styles/global.less b/chat2db-client/src/styles/global.less index 1fc431b5c..fee203c5e 100644 --- a/chat2db-client/src/styles/global.less +++ b/chat2db-client/src/styles/global.less @@ -18,8 +18,8 @@ input:-webkit-autofill { } ::-webkit-scrollbar { - width: 4px; - height: 4px; + width: 6px; + height: 6px; } From 8b41c4dc9784e0b6d1ce1c87387276fb18a5c2a3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 10:04:09 +0800 Subject: [PATCH 0997/1069] Changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f8d69ad..1fb023868 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ # 3.0.0 `2023-10-17` +**Changelog** +- ⚡️【Optimize】Search result scroll bar +- ⚡️【Fixed】Oracle update result data bug + +**更新日志** +- ⚡️【优化】查询结果滚动条 +- 🐞【修复】Oracle更新结果数据错误 + +# 3.0.0 +`2023-10-17` + **Changelog** - 🔥【New Features】Support for team collaboration mode - 🔥【New Features】Support for visual table structure creation, editing, and deletion From e7579c3a938ec1b5a1fa20f43f9a9d53d6ef6570 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 10:04:26 +0800 Subject: [PATCH 0998/1069] Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fb023868..3b748af4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ # 3.0.0 -`2023-10-17` +`2023-10-19` **Changelog** - ⚡️【Optimize】Search result scroll bar From d2d4a6a45b5efb49e43385ffb2060fd14be07c18 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 10:04:48 +0800 Subject: [PATCH 0999/1069] Changelog3.0.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b748af4d..04b350c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# 3.0.0 +# 3.0.1 `2023-10-19` **Changelog** From 650aaa86da1ddf197f6bcc33997ec5cc96d91040 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 19 Oct 2023 10:50:23 +0800 Subject: [PATCH 1000/1069] perf: Optimize ai function --- .../components/Console/MonacoEditor/index.tsx | 34 +++++++++++++--- .../src/components/Console/index.tsx | 39 ++++++++++--------- 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 38682808b..9c0a36fce 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -2,7 +2,9 @@ import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef import cs from 'classnames'; import { useTheme } from '@/hooks'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import { editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; +import { DatabaseTypeCode, editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; +import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; + import styles from './index.less'; export type IEditorIns = monaco.editor.IStandaloneCodeEditor; @@ -14,6 +16,12 @@ export type IAppendValue = { range?: IRangeType; }; +const databaseTypeList = Object.keys(DatabaseTypeCode).map((d) => ({ + type: d, + id: d, + label: d, +})); + interface IProps { id: string; isActive?: boolean; @@ -21,7 +29,7 @@ interface IProps { className?: string; options?: IEditorOptions; needDestroy?: boolean; - addAction?: Array<{ id: string; label: string; action: (selectedText: string) => void }>; + addAction?: Array<{ id: string; label: string; action: (selectedText: string, ext?: string) => void }>; defaultValue?: string; appendValue?: IAppendValue; // onChange?: (v: string, e?: IEditorContentChangeEvent) => void; @@ -51,6 +59,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { appendValue, } = props; const editorRef = useRef(); + const quickInputCommand = useRef(); const [appTheme] = useTheme(); // init @@ -65,6 +74,13 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { editorRef.current = editorIns; didMount && didMount(editorIns); + // Add a new command, for getting an accessor. + quickInputCommand.current = editorIns.addCommand(0, (accessor, func) => { + // a hacker way to get the input service + const quickInputService = accessor.get(IQuickInputService); + func(quickInputService); + }); + const { colorPrimary } = window._AppThemePack; const colors = { 'editor.lineHighlightBackground': colorPrimary + '14', // 当前行背景色 @@ -239,9 +255,17 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { contextMenuGroupId: 'navigation', contextMenuOrder: 1.5, // 点击该菜单键后运行 - run: () => { - const selectedText = editor.getModel()?.getValueInRange(editor.getSelection()!) || ''; - runFn(selectedText); + run: (ed: IEditorIns) => { + if (_id === 'changeSQL') { + ed.trigger('', quickInputCommand.current, (quickInput) => { + quickInput.pick(databaseTypeList).then((selected) => { + console.log(selected); + }); + }); + } else { + const selectedText = editor.getModel()?.getValueInRange(editor.getSelection()!) || ''; + runFn(selectedText); + } }, }); }); diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index ace767ab0..4f17a1480 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -133,9 +133,7 @@ function Console(props: IProps, ref: ForwardedRef) { editorRef: editorRef?.current, })); - useEffect(() => { - - }, []); + useEffect(() => {}, []); useEffect(() => { if (source !== 'workspace') { @@ -155,18 +153,18 @@ function Console(props: IProps, ref: ForwardedRef) { } else { // 活跃时自动保存 indexedDB - .getDataByCursor('chat2db', 'workspaceConsoleDDL', { - consoleId: executeParams.consoleId!, - userId: getCookie('CHAT2DB.USER_ID'), - }) - .then((res: any) => { - const value = res?.[0]?.ddl || ''; - if (value) { - editorRef?.current?.setValue(value, 'reset'); - initializeSuccessful.current = true; - timingAutoSave(); - } - }); + .getDataByCursor('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + userId: getCookie('CHAT2DB.USER_ID'), + }) + .then((res: any) => { + const value = res?.[0]?.ddl || ''; + if (value) { + editorRef?.current?.setValue(value, 'reset'); + initializeSuccessful.current = true; + timingAutoSave(); + } + }); } return () => { if (timerRef.current) { @@ -238,12 +236,12 @@ function Console(props: IProps, ref: ForwardedRef) { } }; - const handleAIChatInEditor = async (content: string, promptType: IPromptType) => { + const handleAIChatInEditor = async (content: string, promptType: IPromptType, ext?: string) => { const aiConfig = await configService.getAiSystemConfig({}); - handleAiChat(content, promptType, aiConfig); + handleAiChat(content, promptType, aiConfig, ext); }; - const handleAiChat = async (content: string, promptType: IPromptType, aiConfig?: IAiConfig) => { + const handleAiChat = async (content: string, promptType: IPromptType, aiConfig?: IAiConfig, ext?: string) => { const { apiKey } = aiConfig || props.aiModel?.aiConfig || {}; if (!apiKey && isChat2DBAI) { handleApiKeyEmptyOrGetQrCode(true); @@ -266,6 +264,7 @@ function Console(props: IProps, ref: ForwardedRef) { databaseName, schemaName, tableNames: syncTableModel ? selectedTables : null, + ext, }); const handleMessage = (message: string) => { @@ -398,7 +397,9 @@ function Console(props: IProps, ref: ForwardedRef) { { id: 'changeSQL', label: i18n('common.text.conversionSQL'), - action: (selectedText: string) => handleAIChatInEditor(selectedText, IPromptType.SQL_2_SQL), + action: (selectedText: string, ext?: string) => { + handleAIChatInEditor(selectedText, IPromptType.SQL_2_SQL, ext); + }, }, ]; From eb7e08119ed7c5c17804b66b7f83ea390c104575 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Thu, 19 Oct 2023 11:07:45 +0800 Subject: [PATCH 1001/1069] fix: fix bug --- chat2db-client/src/components/Console/MonacoEditor/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 9c0a36fce..296fc3c67 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -256,14 +256,15 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { contextMenuOrder: 1.5, // 点击该菜单键后运行 run: (ed: IEditorIns) => { + const selectedText = editor.getModel()?.getValueInRange(editor.getSelection()!) || ''; if (_id === 'changeSQL') { ed.trigger('', quickInputCommand.current, (quickInput) => { quickInput.pick(databaseTypeList).then((selected) => { console.log(selected); + runFn(selectedText, selected?.label); }); }); } else { - const selectedText = editor.getModel()?.getValueInRange(editor.getSelection()!) || ''; runFn(selectedText); } }, From bf9221e8e33cf41c29b32027b4c07ff925628ac3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 11:33:13 +0800 Subject: [PATCH 1002/1069] feat:Query data. The column width can be adjusted --- .../components/SearchResult/TableBox/index.less | 15 ++++++++++++--- .../components/SearchResult/TableBox/index.tsx | 10 ++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 68993c056..84080fde0 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -63,6 +63,7 @@ } .art-table-header { background: transparent !important; + overflow: hidden !important; } .art-table-body-scroll{ padding-right: 6px; @@ -154,22 +155,30 @@ } .tableItem { - height: 100%; + height: 30px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; position: relative; padding-left: 4px; - overflow: hidden; user-select: none; background-color: var(--color-bg-base); + + .tableItemContent{ + overflow: hidden; + max-height: 30px; + line-height: 15px; + &:hover{ + overflow: auto; + } + } + &:hover { .tableHoverBox{ display: flex; } - overflow: auto; } .cellValueNull{ diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index ebd849b25..fee45de1f 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -393,7 +393,9 @@ export default function TableBox(props: ITableProps) { /> ) : ( <> - {renderTableCellValue(value)} +
    + {renderTableCellValue(value)} +
    @@ -420,7 +422,11 @@ export default function TableBox(props: ITableProps) { // sorts, // onChangeSorts, }), - ); + ) + .use(features.columnResize({ + fallbackSize: 120, + handleActiveBackground: `var(--color-primary-bg-hover)`, + })); // .use( // features.columnResize({ // fallbackSize: 120, From c667eb521d96c700107ec037f1c78dce51a1b092 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 12:04:58 +0800 Subject: [PATCH 1003/1069] fix:1.windos shortcut key problem 2.Table list switching page problem --- .../components/SearchResult/TableBox/index.less | 4 +++- .../src/components/ShortcutKey/index.tsx | 4 ---- .../main/workspace/components/TableList/index.tsx | 2 ++ .../workspace/components/WorkspaceRight/index.tsx | 14 +++++++++++--- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 84080fde0..98bcda841 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -169,9 +169,11 @@ .tableItemContent{ overflow: hidden; max-height: 30px; - line-height: 15px; + line-height: 30px; + flex: 1; &:hover{ overflow: auto; + line-height: 16px; } } diff --git a/chat2db-client/src/components/ShortcutKey/index.tsx b/chat2db-client/src/components/ShortcutKey/index.tsx index 99442020e..4e18c321f 100644 --- a/chat2db-client/src/components/ShortcutKey/index.tsx +++ b/chat2db-client/src/components/ShortcutKey/index.tsx @@ -31,10 +31,6 @@ const shortcutsList = [ title: i18n('common.text.optimizeSQL'), keys: [i18n('common.text.editorRightClick')], }, - { - title: i18n('common.text.executeSelectedSQL'), - keys: [keyboardKey.command, 'Enter'], - }, { title: i18n('common.text.executeSelectedSQL'), keys: [keyboardKey.command, 'R'], 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 47e975822..b3e9ed182 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -235,6 +235,7 @@ const TableList = dvaModel((props: any) => { useEffect(() => { setCurList([]); + setPagingData(defaultPaddingData); if (isReady) { setCurType({ ...optionsList[0] }); } @@ -367,6 +368,7 @@ const TableList = dvaModel((props: any) => { const handleChangePageSize = (value: number) => { setPagingData({ ...pagingData, + pageNo: 1, pageSize: value, }); }; 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 a59e47835..8353c8e3c 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -3,7 +3,7 @@ import { connect } from 'umi'; import i18n from '@/i18n'; import styles from './index.less'; import classnames from 'classnames'; -import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType, WorkspaceTabType, workspaceTabConfig } from '@/constants'; +import { ConsoleOpenedStatus, ConsoleStatus, OSType, TreeNodeType, WorkspaceTabType, workspaceTabConfig } from '@/constants'; import historyService from '@/service/history'; import sqlService from '@/service/sql'; import TabsNew, { ITabItem } from '@/components/TabsNew'; @@ -26,6 +26,7 @@ import { registerIntelliSenseTable, } from '@/utils/IntelliSense'; import indexedDB from '@/indexedDB'; +import { osNow } from '@/utils'; interface IProps { className?: string; @@ -69,8 +70,15 @@ const WorkspaceRight = memo((props: IProps) => { // 注册快捷键command+shift+L新建console useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { - if (e.metaKey && e.shiftKey && e.code === 'KeyL') { - addConsole(); + // 如果是mac系统 + if(osNow().isMac){ + if (e.metaKey && e.shiftKey && e.code === 'KeyL') { + addConsole(); + } + }else{ + if (e.ctrlKey && e.shiftKey && e.code === 'KeyL') { + addConsole(); + } } }; window.addEventListener('keydown', handleKeyDown); From be1e2f11967c647b4b784012c06d822d6f95ee6f Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 13:01:09 +0800 Subject: [PATCH 1004/1069] Update README.md --- README.md | 6 ++++++ README_CN.md | 12 +++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 334e6bdea..df89f1a1c 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,12 @@ An intelligent and versatile general-purpose SQL client and reporting tool for d Languages: English | [中文](README_CN.md) +Official website:[Chat2DB](https://sqlgpt.cn/en) + +
    + If you find Chat2DB helpful, please helpgithub starClick ⭐ Star and Fork in the top right corner, and your support is the biggest motivation for Chat2DB to get bette +
    +
    ## DEMO diff --git a/README_CN.md b/README_CN.md index b64785ee7..0673a5a7c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -16,9 +16,13 @@
    Languages: 中文 [English](README.md) - -如果觉得 Chat2DB 对您有帮助的话,请帮忙github star -的右上角点个⭐ Star 和 Fork,您的支持是 Chat2DB 变得更好最大的动力 + + 官网:[Chat2DB](https://sqlgpt.cn/zh) + +
    + 如果觉得 Chat2DB 对您有帮助的话,请帮忙github star + 的右上角点个⭐ Star 和 Fork,您的支持是 Chat2DB 变得更好最大的动力 +
    @@ -50,8 +54,6 @@ https://github.com/chat2db/Chat2DB/assets/22975773/b58db908-5768-4a71-aa30-135d2 [GitHub下载安装包](https://github.com/chat2db/Chat2DB/releases) -或 - [官网下载安装包](https://sqlgpt.cn) ## 🚀 支持的数据库 From 949b4758d68ef0946d52d76038a762d37d8ee585 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 13:26:50 +0800 Subject: [PATCH 1005/1069] Update README.md --- README.md | 8 ++++---- README_CN.md | 4 ++-- chat2db-client/src/components/Console/ChatInput/index.tsx | 1 - chat2db-client/src/constants/social.ts | 1 - chat2db-client/src/service/base.ts | 2 +- chat2db-server/README.md | 1 - 6 files changed, 7 insertions(+), 10 deletions(-) delete mode 100644 chat2db-client/src/constants/social.ts diff --git a/README.md b/README.md index df89f1a1c..705e16965 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Chat2DB supports connecting to the following databases: - H2 - Oracle - SQLServer -- SQLLite +- SQLite - MariaDB - ClickHouseare - DM @@ -135,7 +135,7 @@ Note: If local debugging is required - git clone to local ```bash -$ git clone git@github.com:alibaba/Chat2DB.git +$ git clone git@github.com:chat2db/Chat2DB.git ``` - Front-End debug @@ -166,8 +166,8 @@ $ npm run build:web:prod / cp -r dist ../chat2db-server/chat2db-server-start/src ## 📑 Documentation -- Official website document -- Issue +- Official website document +- Issue ## Stargazers diff --git a/README_CN.md b/README_CN.md index 0673a5a7c..7502121cd 100644 --- a/README_CN.md +++ b/README_CN.md @@ -64,7 +64,7 @@ Chat2DB 支持的数据库连接有: - H2 - Oracle - SQLServer -- SQLLite +- SQLite - MariaDB - ClickHouseare - DM @@ -159,7 +159,7 @@ $ npm run build:web:prod / cp -r dist ../chat2db-server/chat2db-server-start/src ## 📑 文档 -- 官方文档 +- 官方文档 - Issue ## 常见问题 diff --git a/chat2db-client/src/components/Console/ChatInput/index.tsx b/chat2db-client/src/components/Console/ChatInput/index.tsx index dbb9d858d..08a846b6b 100644 --- a/chat2db-client/src/components/Console/ChatInput/index.tsx +++ b/chat2db-client/src/components/Console/ChatInput/index.tsx @@ -6,7 +6,6 @@ import i18n from '@/i18n/'; import Iconfont from '@/components/Iconfont'; import { WarningOutlined } from '@ant-design/icons'; import { AiSqlSourceType, IRemainingUse } from '@/typings/ai'; -import { WECHAT_MP_URL } from '@/constants/social'; export const enum SyncModelType { AUTO = 0, diff --git a/chat2db-client/src/constants/social.ts b/chat2db-client/src/constants/social.ts deleted file mode 100644 index e44168542..000000000 --- a/chat2db-client/src/constants/social.ts +++ /dev/null @@ -1 +0,0 @@ -export const WECHAT_MP_URL = 'https://oss-chat2db.alibaba.com/static/wechat.webp'; diff --git a/chat2db-client/src/service/base.ts b/chat2db-client/src/service/base.ts index 4d7d26ae6..c9889b778 100644 --- a/chat2db-client/src/service/base.ts +++ b/chat2db-client/src/service/base.ts @@ -40,7 +40,7 @@ enum ErrorCode { const noNeedToastErrorCode = [ErrorCode.NEED_LOGGED_IN]; // yapi mock地址 -const mockUrl = 'https://yapi.alibaba.com/mock/1000160'; +const mockUrl = 'https://yapi.com/mock/1000160'; // 桌面端的服务器地址 const desktopServiceUrl = `http://127.0.0.1:${__APP_PORT__ || '10824'}`; diff --git a/chat2db-server/README.md b/chat2db-server/README.md index 6b892e903..5f51e53ad 100644 --- a/chat2db-server/README.md +++ b/chat2db-server/README.md @@ -1,6 +1,5 @@ # 内部协作规范 ## 接口规范 -统一使用yapi,空间地址: https://yapi.alibaba.com/project/1000160/interface/api 不会使用的参照:https://yuque.antfin-inc.com/docs/share/8a5ff21a-6367-4c77-9e3c-1d5ae9570060?# 《yapi》 ## 国际化处理方案 * 在`messages.properties` 文件下新增code From 85c1147abc6a7823b3f6235eb7fe1b1a0ea2abba Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 15:46:33 +0800 Subject: [PATCH 1006/1069] updataPack command: --- .github/workflows/release.yml | 2 +- .github/workflows/release_test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 71c096913..552cd732d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -140,7 +140,7 @@ jobs: yarn install yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} 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 + cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ # 编译服务端java版本 - name: Build Java diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 2f0d40be9..8d6d78f39 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -134,7 +134,7 @@ jobs: 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 + cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ # 编译服务端java版本 - name: Build Java From e2113fcc42b9da671fbe6594ed1904f32189776e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 16:34:03 +0800 Subject: [PATCH 1007/1069] Update:workflows --- .github/workflows/pushdocker.yml | 2 +- .../src/main/resources/thymeleaf/template.html | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/template.html diff --git a/.github/workflows/pushdocker.yml b/.github/workflows/pushdocker.yml index fbad111a8..dea932014 100644 --- a/.github/workflows/pushdocker.yml +++ b/.github/workflows/pushdocker.yml @@ -50,7 +50,7 @@ jobs: yarn install yarn run build:web:prod --app_version=${{ steps.chat2db_version.outputs.substring }} 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 + cp -r dist/index.html ../chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/ # 安装java - name: Install Java and Maven diff --git a/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/template.html b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/template.html new file mode 100644 index 000000000..175c03dbe --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/resources/thymeleaf/template.html @@ -0,0 +1,11 @@ + + + + + + Chat2DB + + + + + \ No newline at end of file From b42d8d9b46cbfbf4c554bb30b4a399bc7c03ce1a Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 19 Oct 2023 20:57:29 +0800 Subject: [PATCH 1008/1069] fix: Bugs are displayed when more than 100 data items are queried --- .../SearchResult/TableBox/index.less | 77 ++++++++++--------- .../SearchResult/TableBox/index.tsx | 61 +++++++-------- .../src/pages/main/connection/index.less | 1 + 3 files changed, 71 insertions(+), 68 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index 98bcda841..e0778e36b 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -2,8 +2,7 @@ .tableBox { height: 100%; - overflow-y: auto; - overflow-x: hidden; + overflow: hidden; display: flex; flex-direction: column; :global { @@ -44,45 +43,51 @@ .art-table-cell { position: relative; } - .art-sticky-scroll { - display: none !important; - } - .art-sticky-scroll.art-horizontal-scroll-container { - position: sticky; - bottom: 26px !important; - } - .art-loading-wrapper,.art-loading-content-wrapper,.art-table{ - height: 100%; - } - .art-table-body{ - height: calc(100% - 32px); - overflow-y: auto !important; - } - .art-table-row{ + // .art-sticky-scroll { + // display: none !important; + // } + + .art-table-row { height: 32px; } .art-table-header { background: transparent !important; - overflow: hidden !important; + overflow: hidden !important; + top: 0px !important; } - .art-table-body-scroll{ + .art-table-body { + // 隐藏滚动条 + ::-webkit-scrollbar { + display: none; + } + } + .art-table-body-scroll { padding-right: 6px; } - .art-table-header-cell{ + .art-table-header-cell { padding: 0px 4px; } } } -.noDataTableBox{ +.noDataTableBox { + :global { + .art-loading-wrapper, + .art-loading-content-wrapper, + .art-table { + height: 100%; + } + .art-table-body { + height: calc(100% - 32px); + overflow-y: auto !important; + } + } table { height: 100%; } } .toolBar { - position: sticky; - top: 0; z-index: 30; display: flex; justify-content: space-between; @@ -145,13 +150,14 @@ } } -.tableSpin { +.supportBaseTableBox { flex: 1; + overflow-y: auto; + height: 0px; } .table { - height: 0px; - flex: 1; + height: 100%; } .tableItem { @@ -165,25 +171,24 @@ user-select: none; background-color: var(--color-bg-base); - - .tableItemContent{ + .tableItemContent { overflow: hidden; max-height: 30px; line-height: 30px; flex: 1; - &:hover{ + &:hover { overflow: auto; line-height: 16px; } } &:hover { - .tableHoverBox{ + .tableHoverBox { display: flex; } } - .cellValueNull{ + .cellValueNull { color: var(--color-text-tertiary); } @@ -194,25 +199,25 @@ .tableItemEdit { background-color: var(--color-success-bg); - .tableHoverBox{ + .tableHoverBox { background-color: var(--color-success-bg); } } .tableItemEditing { background-color: var(--color-primary-bg-hover); - .tableHoverBox{ + .tableHoverBox { background-color: var(--color-primary-bg-hover); } } .tableItemSuccess { background-color: var(--color-success-bg); - .tableHoverBox{ + .tableHoverBox { background-color: var(--color-success-bg); } } -.tableItemError{ +.tableItemError { background-color: var(--color-error-bg); - .tableHoverBox{ + .tableHoverBox { background-color: var(--color-error-bg); } } diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index fee45de1f..657531b2f 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -53,6 +53,7 @@ const SupportBaseTable: any = styled(BaseTable)` --border-color: var(--color-border-secondary); --cell-padding: 0px; --row-height: 32px; + --lock-shadow: 0px 1px 2px 0px var(--color-border); } `; @@ -289,10 +290,10 @@ export default function TableBox(props: ITableProps) { // 渲染单元格的值 const renderTableCellValue = (value) => { if (value === null) { - return {''} + return {''}; } else if (!value) { // 如果为空需要展位 - return + return ; } else { return value; } @@ -393,9 +394,7 @@ export default function TableBox(props: ITableProps) { /> ) : ( <> -
    - {renderTableCellValue(value)} -
    +
    {renderTableCellValue(value)}
    @@ -423,10 +422,12 @@ export default function TableBox(props: ITableProps) { // onChangeSorts, }), ) - .use(features.columnResize({ - fallbackSize: 120, - handleActiveBackground: `var(--color-primary-bg-hover)`, - })); + .use( + features.columnResize({ + fallbackSize: 120, + handleActiveBackground: `var(--color-primary-bg-hover)`, + }), + ); // .use( // features.columnResize({ // fallbackSize: 120, @@ -768,24 +769,24 @@ export default function TableBox(props: ITableProps) {
    {allDataReady && ( - // -

    {i18n('common.text.noData')}

    }} - isStickyHead - stickyTop={31} - getRowProps={(record) => { - const rowNo = record[`${preCode}0No.`]; - return { - // style: tableRowStyle(rowNo), - onClick() { - setCurOperationRowNo(rowNo); - }, - }; - }} - {...pipeline.getProps()} - /> - //
    +
    +

    {i18n('common.text.noData')}

    }} + isStickyHead + stickyTop={31} + getRowProps={(record) => { + const rowNo = record[`${preCode}0No.`]; + return { + // style: tableRowStyle(rowNo), + onClick() { + setCurOperationRowNo(rowNo); + }, + }; + }} + {...pipeline.getProps()} + /> +
    )} {/* {bottomStatus} */} @@ -796,11 +797,7 @@ export default function TableBox(props: ITableProps) { return (
    {renderContent()} Date: Fri, 20 Oct 2023 09:08:24 +0800 Subject: [PATCH 1009/1069] =?UTF-8?q?=E4=BF=AE=E5=A4=8DWindows=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E6=A8=A1=E6=9D=BF=E5=A4=B1=E8=B4=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../start/config/util/CopyTemplate.java | 63 +++++++++++++++++++ .../server/tools/common/util/ConfigUtils.java | 43 ------------- .../api/controller/rdb/RdbDocController.java | 3 - 3 files changed, 63 insertions(+), 46 deletions(-) create mode 100644 chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/CopyTemplate.java diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/CopyTemplate.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/CopyTemplate.java new file mode 100644 index 000000000..b0f6c164d --- /dev/null +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/CopyTemplate.java @@ -0,0 +1,63 @@ +package ai.chat2db.server.start.config.util; + +import ai.chat2db.server.tools.common.util.ConfigUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; + +/** + * CopyTemplate + * + * @author lzy + **/ +@Component +@Slf4j +public class CopyTemplate { + /** + * 模板文件 + **/ + public static final List TEMPLATE_FILE = Arrays.asList("template.html", "template_diy.docx", "sub_template_diy.docx"); + + static { + //复制模板 + copyTemplateFile(); + } + + public static void copyTemplateFile() { + String templateDir = ConfigUtils.CONFIG_BASE_PATH + File.separator + "template"; + File file = new File(templateDir); + if (!file.exists()) { + file.mkdir(); + } + for (String template : TEMPLATE_FILE) { + saveFile(templateDir, template, true); + } + } + + public static void saveFile(String dir, String path, boolean isOverride) { + if (!isOverride) { + File file = new File(dir + File.separator + path); + if (file.exists()) { + return; + } + } + try (// 模板文件输入输出地址 读取resources下文件 + FileOutputStream outputStream = new FileOutputStream(dir + File.separator + path); + //返回读取指定资源的输入流 + InputStream inputStream = ConfigUtils.class.getClassLoader().getResourceAsStream("template" + File.separator + path)) { + byte[] buffer = new byte[4096]; + int n = 0; + while (-1 != (n = inputStream.read(buffer))) { + outputStream.write(buffer, 0, n); + } + outputStream.flush(); + } catch (Exception e) { + log.error("saveFile error", e); + } + } +} diff --git a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java index ac0780ce8..4fdb3d929 100644 --- a/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java +++ b/chat2db-server/chat2db-server-tools/chat2db-server-tools-common/src/main/java/ai/chat2db/server/tools/common/util/ConfigUtils.java @@ -7,10 +7,6 @@ import org.apache.commons.lang3.StringUtils; import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; /** * Configure information on the user side @@ -27,10 +23,6 @@ public class ConfigUtils { public static File versionFile = null; public static File configFile; private static ConfigJson config = null; - /** - * 模板文件 - **/ - public static final List TEMPLATE_FILE = Arrays.asList("template.html", "template_diy.docx", "sub_template_diy.docx"); static { String environment = StringUtils.defaultString(System.getProperty("spring.profiles.active"), "dev"); @@ -46,8 +38,6 @@ public class ConfigUtils { if (!configFile.exists()) { FileUtil.writeUtf8String(JSON.toJSONString(new ConfigJson()), configFile); } - //复制模板 - copyTemplateFile(); } public static void updateVersion(String version) { @@ -97,37 +87,4 @@ private static String getAppPath() { return null; } } - - public static void copyTemplateFile() { - String templateDir = CONFIG_BASE_PATH + File.separator + "template"; - File file = new File(templateDir); - if (!file.exists()) { - file.mkdir(); - } - for (String template : TEMPLATE_FILE) { - saveFile(templateDir, template, true); - } - } - - public static void saveFile(String dir, String path, boolean isOverride) { - if (!isOverride) { - File file = new File(dir + File.separator + path); - if (file.exists()) { - return; - } - } - try (// 模板文件输入输出地址 读取resources下文件 - FileOutputStream outputStream = new FileOutputStream(dir + File.separator + path); - //返回读取指定资源的输入流 - InputStream inputStream = ConfigUtils.class.getClassLoader().getResourceAsStream("template" + File.separator + path)) { - byte[] buffer = new byte[4096]; - int n = 0; - while (-1 != (n = inputStream.read(buffer))) { - outputStream.write(buffer, 0, n); - } - outputStream.flush(); - } catch (Exception e) { - log.error("saveFile error", e); - } - } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java index 552a6cf24..a765b1534 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDocController.java @@ -15,10 +15,7 @@ import ai.chat2db.server.web.api.controller.rdb.request.DataExportRequest; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.sql.Chat2DBContext; -import ai.chat2db.spi.util.JdbcUtils; import cn.hutool.core.date.DatePattern; -import com.alibaba.druid.DbType; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; From ef9dc8c811393db4579af0f9287de2b0d082a609 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 20 Oct 2023 09:27:36 +0800 Subject: [PATCH 1010/1069] Update:Changelog --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b350c6a..b92bd1047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# 3.0.4 +`2023-10-20` + +**Changelog** +- 🐞【Fixed】Bugs are displayed when more than 100 data items are queried + +**更新日志** +- 🐞【修复】查询数据超过100条时显示bug + # 3.0.1 `2023-10-19` From 116577476eaf78c1039ab1e56101f8fce0cfb8a3 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 20 Oct 2023 10:08:29 +0800 Subject: [PATCH 1011/1069] chore: modify i18n --- chat2db-client/src/i18n/en-us/editTable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/i18n/en-us/editTable.ts b/chat2db-client/src/i18n/en-us/editTable.ts index bce44febc..e31029531 100644 --- a/chat2db-client/src/i18n/en-us/editTable.ts +++ b/chat2db-client/src/i18n/en-us/editTable.ts @@ -12,8 +12,8 @@ export default { 'editTable.label.indexType': 'Type', 'editTable.label.indexMethod': 'Index method', 'editTable.label.includeColumn': 'Include column', - 'editTable.button.createTable': 'Create table', - 'editTable.button.importTable': 'Import table', + 'editTable.button.createTable': 'Create Table', + 'editTable.button.importTable': 'Export Table', 'editTable.label.index': 'Index', 'editTable.label.columnName': 'Name', 'editTable.label.columnSize': 'Size', From c60f5d2c23bb3ef36209b5156e17c70a44006b46 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Fri, 20 Oct 2023 15:02:27 +0800 Subject: [PATCH 1012/1069] chroe: modify code --- .../components/SearchResult/TableBox/index.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 657531b2f..0aec121e1 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -468,7 +468,7 @@ export default function TableBox(props: ITableProps) { }; // 处理创建数据 - const handelCreateData = () => { + const handleCreateData = () => { // 如果加的这行数据是删除过的,则恢复 const index = updateData.findIndex((item) => item.rowNo === curOperationRowNo && item.type === CRUD.DELETE); if (index !== -1) { @@ -504,7 +504,7 @@ export default function TableBox(props: ITableProps) { }; // 处理删除数据 - const handelDeleteData = () => { + const handleDeleteData = () => { if (curOperationRowNo === null) { return; } @@ -545,7 +545,7 @@ export default function TableBox(props: ITableProps) { }; // 查看更新数据的sql - const handelViewSql = () => { + const handleViewSql = () => { if (!updateData.length) { return; } @@ -556,7 +556,7 @@ export default function TableBox(props: ITableProps) { }; // 更新数据的sql - const handelUpdateSubmit = () => { + const handleUpdateSubmit = () => { if (!updateData.length) { return; } @@ -620,6 +620,7 @@ export default function TableBox(props: ITableProps) { }; return sqlService.executeSql(executeSQLParams).then((res) => { + debugger; setQueryResultData(res?.[0]); setUpdateData([]); }); @@ -711,7 +712,7 @@ export default function TableBox(props: ITableProps) {
    @@ -719,7 +720,7 @@ export default function TableBox(props: ITableProps) {
    Date: Fri, 20 Oct 2023 16:17:09 +0800 Subject: [PATCH 1013/1069] Support auto update --- .../start/config/config/AutomaticUpgrade.java | 156 ------------------ .../DbhubTomcatConnectorCustomizer.java | 3 +- .../FailedEventApplicationListener.java | 3 +- .../server/start/config/util/SystemUtils.java | 36 ---- .../controller/system/AutomaticUpgrade.java | 21 +++ .../controller/system/SystemController.java | 50 +++++- .../controller/system/util/SystemUtils.java | 91 ++++++++++ .../controller/system/vo/AppVersionVO.java | 68 ++++++++ 8 files changed, 227 insertions(+), 201 deletions(-) delete mode 100644 chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java delete mode 100644 chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/AppVersionVO.java diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java deleted file mode 100644 index af9f3c96a..000000000 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/AutomaticUpgrade.java +++ /dev/null @@ -1,156 +0,0 @@ -package ai.chat2db.server.start.config.config; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.Serializable; -import java.time.Duration; -import java.util.List; -import java.util.Map; - -import ai.chat2db.server.tools.common.util.ConfigUtils; -import cn.hutool.core.io.FileUtil; -import com.dtflys.forest.Forest; -import com.dtflys.forest.utils.TypeReference; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import org.apache.commons.lang3.StringUtils; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class AutomaticUpgrade { - - private static final OkHttpClient client = new OkHttpClient(); - - private static final String VERSION_URL = "https://sqlgpt.cn/api/version.json"; - - @Scheduled(fixedRate = 3600000) // 每小时运行一次 - public void checkVersionUpdates() { - String appPath = ConfigUtils.APP_PATH; - - log.info("appPath: {}", appPath); - if (StringUtils.isBlank(appPath) || !appPath.contains("app")) { - return; - } - try { - Upgrade dataResult = getUpgrade(); - - String oldVersion = ConfigUtils.getLocalVersion(); - - if (!needUpdate(dataResult, oldVersion)) { - return; - } - - File versionFile = new File( - appPath + File.separator + "versions" + File.separator + dataResult.getVersion()); - if (!versionFile.exists()) { - versionFile.mkdir(); - } - File oldLib = new File( - appPath + File.separator + "versions" + File.separator + oldVersion + File.separator + "static" - + File.separator + "lib"); - - File newLib = new File( - appPath + File.separator + "versions" + File.separator + dataResult.getVersion() + File.separator - + "static"); - - if (oldLib.exists()) { - FileUtil.copy(oldLib, newLib, true); - } - - for (Map downloadFile : dataResult.getDownloadFiles()) { - downloadUpgrade(downloadFile, versionFile); - } - - ConfigUtils.updateVersion(dataResult.getVersion()); - } catch (Exception e) { - log.error("checkVersionUpdates error", e); - } - - } - - private void downloadUpgrade(Map downloadFile, File versionFile) throws IOException { - String url = downloadFile.get("url"); - String fileName = downloadFile.get("fileName"); - String[] paths = fileName.split("/"); - String filePath = versionFile.getPath(); - for (int i = 0; i < paths.length; i++) { - filePath = filePath + File.separator + paths[i]; - if (i < paths.length - 1) { - File file = new File(filePath); - if (!file.exists()) { - file.mkdir(); - } - } - } - download(url, filePath); - } - - /** - * Find upgrade files - * - * @return - */ - private Upgrade getUpgrade() { - return Forest.get(VERSION_URL) - .connectTimeout(Duration.ofMillis(5000)) - .readTimeout(Duration.ofMillis(10000)) - .execute(new TypeReference<>() {}); - } - - private boolean needUpdate(Upgrade upgrade, String localVersion) { - if (upgrade == null || StringUtils.isBlank(upgrade.getVersion()) || StringUtils.isBlank(localVersion) - || upgrade.getVersion().equals(localVersion)) { - return false; - } - String[] versionArray = upgrade.getVersion().split("\\."); - String[] localVersionArray = localVersion.split("\\."); - for (int i = 0; i < versionArray.length; i++) { - if (Long.parseLong(versionArray[i]) < Long.parseLong(localVersionArray[i])) { - return false; - } - if (Long.parseLong(versionArray[i]) > Long.parseLong(localVersionArray[i])) { - return true; - } - } - return false; - } - - private void download(String url, String outputPath) throws IOException { - File file = new File(outputPath); - if (file.exists()) { - file.delete(); - } - Request request = new Request.Builder().url(url).build(); - try (Response response = client.newCall(request).execute()) { - if (!response.isSuccessful()) { - throw new IOException("Unexpected code " + response); - } - try (InputStream is = response.body().byteStream(); FileOutputStream fos = new FileOutputStream( - outputPath)) { - - byte[] buffer = new byte[2048]; - int length; - while ((length = is.read(buffer)) != -1) { - fos.write(buffer, 0, length); - } - fos.flush(); - } - // System.out.println("File downloaded: " + outputPath); - } - } - - @Data - public static class Upgrade implements Serializable { - - private String version; - - private List> downloadFiles; - } -} diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/DbhubTomcatConnectorCustomizer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/DbhubTomcatConnectorCustomizer.java index 54d174940..b9be9aa8c 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/DbhubTomcatConnectorCustomizer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/DbhubTomcatConnectorCustomizer.java @@ -1,7 +1,6 @@ package ai.chat2db.server.start.config.listener; -import ai.chat2db.server.start.config.util.SystemUtils; - +import ai.chat2db.server.web.api.controller.system.util.SystemUtils; import lombok.extern.slf4j.Slf4j; import org.apache.catalina.LifecycleState; import org.apache.catalina.connector.Connector; diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/FailedEventApplicationListener.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/FailedEventApplicationListener.java index 3ee3b2fcc..4185ae7fe 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/FailedEventApplicationListener.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/listener/FailedEventApplicationListener.java @@ -1,7 +1,6 @@ package ai.chat2db.server.start.config.listener; -import ai.chat2db.server.start.config.util.SystemUtils; - +import ai.chat2db.server.web.api.controller.system.util.SystemUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationFailedEvent; import org.springframework.context.ApplicationListener; diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java deleted file mode 100644 index abb9fdaa9..000000000 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/util/SystemUtils.java +++ /dev/null @@ -1,36 +0,0 @@ -package ai.chat2db.server.start.config.util; - - -import ai.chat2db.spi.ssh.SSHManager; -import lombok.extern.slf4j.Slf4j; - -/** - * 系统工具包 - * - * @author Jiaju Zhuang - */ -@Slf4j -public class SystemUtils { - - /** - * 停止当前应用 - */ - public static void stop() { - new Thread(() -> { - log.info("1秒以后退出应用"); - // 1秒以后自动退出应用 - try { - Thread.sleep(1000L); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - // 直接系统退出 - log.info("开始退出系统应用"); - SSHManager.close(); - try { - System.exit(0); - } catch (Exception ignore) { - } - }).start(); - } -} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java new file mode 100644 index 000000000..40833045e --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.web.api.controller.system; + +import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.web.api.controller.system.util.SystemUtils; +import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +public class AutomaticUpgrade { + + @Scheduled(fixedRate = 3600000) // 每小时运行一次 + public void checkVersionUpdates() { + AppVersionVO appVersion = SystemUtils.getLatestVersion(ConfigUtils.getLocalVersion(), "auto", ""); + if (appVersion != null) { + SystemUtils.upgrade(appVersion); + } + } +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java index 58c506e3a..d7d6f88f3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/SystemController.java @@ -4,20 +4,28 @@ */ package ai.chat2db.server.web.api.controller.system; +import ai.chat2db.server.domain.api.model.Config; +import ai.chat2db.server.domain.api.service.ConfigService; import ai.chat2db.server.domain.core.cache.CacheManage; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.common.config.Chat2dbProperties; +import ai.chat2db.server.tools.common.enums.ModeEnum; import ai.chat2db.server.tools.common.model.ConfigJson; import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.tools.common.util.EasyEnumUtils; +import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient; +import ai.chat2db.server.web.api.controller.system.util.SystemUtils; +import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; import ai.chat2db.server.web.api.controller.system.vo.SystemVO; +import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.spi.ssh.SSHManager; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.context.ApplicationContext; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; + /** * @author jipengfei @@ -43,10 +51,42 @@ public class SystemController { public DataResult get() { ConfigJson configJson = ConfigUtils.getConfig(); return DataResult.of(SystemVO.builder() - .systemUuid(configJson.getSystemUuid()) - .build()); + .systemUuid(configJson.getSystemUuid()) + .build()); + } + + + @GetMapping("/get_latest_version") + public DataResult getLatestVersion(String currentVersion) { + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); + AppVersionVO appVersionVO = SystemUtils.getLatestVersion(currentVersion, "manual", keyConfig.getContent()); + ModeEnum mode = EasyEnumUtils.getEnum(ModeEnum.class, System.getProperty("chat2db.mode")); + if (mode == ModeEnum.DESKTOP) { + // In this mode, no user login is required, so only local access is available + appVersionVO.setDesktop(true); + } + return DataResult.of(appVersionVO); } + @PostMapping("/update_desktop_version") + public DataResult updateDesktopVersion(@RequestBody AppVersionVO version) { + new Thread(() -> { + SystemUtils.upgrade(version); + }).start(); + return DataResult.of(version.getVersion()); + } + + @GetMapping("/is_update_success") + public DataResult isUpdateSuccess(String version) { + String localVersion = ConfigUtils.getLocalVersion(); + if (StringUtils.isEmpty(localVersion)) { + return DataResult.of(false); + } + return DataResult.of(localVersion.equals(version)); + } + + /** * 获取当前版本号 * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java new file mode 100644 index 000000000..65f1870ab --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/util/SystemUtils.java @@ -0,0 +1,91 @@ +package ai.chat2db.server.web.api.controller.system.util; + + +import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.tools.common.util.ConfigUtils; +import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; +import ai.chat2db.spi.ssh.SSHManager; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.ZipUtil; +import cn.hutool.http.HttpUtil; +import com.dtflys.forest.Forest; +import com.dtflys.forest.utils.TypeReference; +import lombok.extern.slf4j.Slf4j; +import okhttp3.OkHttpClient; +import org.apache.commons.lang3.StringUtils; + +import java.io.File; +import java.time.Duration; + +/** + * 系统工具包 + * + * @author Jiaju Zhuang + */ +@Slf4j +public class SystemUtils { + + /** + * 停止当前应用 + */ + public static void stop() { + new Thread(() -> { + log.info("1秒以后退出应用"); + // 1秒以后自动退出应用 + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + // 直接系统退出 + log.info("开始退出系统应用"); + SSHManager.close(); + try { + System.exit(0); + } catch (Exception ignore) { + } + }).start(); + } + + private static final OkHttpClient client = new OkHttpClient(); + + private static final String VERSION_URL = "https://sqlgpt.cn/api/version.json"; + + private static final String ZIP_FILE_PATH = ConfigUtils.APP_PATH + File.separator + "versions" + File.separator; + + public static void upgrade(AppVersionVO appVersion) { + + String appPath = ConfigUtils.APP_PATH; + + log.info("appPath: {}", appPath); + if (StringUtils.isBlank(appPath) || !appPath.contains("app")) { + return; + } + try { + String zipPath = ZIP_FILE_PATH + appVersion.getVersion() + ".zip"; + + HttpUtil.downloadFile(appVersion.getHotUpgradeUrl(), ZIP_FILE_PATH + appVersion.getVersion() + ".zip"); + + ZipUtil.unzip(zipPath); + + FileUtil.del(zipPath); + + ConfigUtils.updateVersion(appVersion.getVersion()); + } catch (Exception e) { + log.error("checkVersionUpdates error", e); + } + } + + private static final String LATEST_VERSION_URL = "https://test.sqlgpt.cn/api/client/version/check/v3?version=%s&type=wechat&userId=%s"; + + public static AppVersionVO getLatestVersion(String version, String type, String userId) { + String url = String.format(LATEST_VERSION_URL, version, type, userId); + DataResult result = Forest.get(url) + .connectTimeout(Duration.ofMillis(5000)) + .readTimeout(Duration.ofMillis(10000)) + .execute(new TypeReference<>() { + }); + return result.getData(); + } + +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/AppVersionVO.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/AppVersionVO.java new file mode 100644 index 000000000..789754e84 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/vo/AppVersionVO.java @@ -0,0 +1,68 @@ +package ai.chat2db.server.web.api.controller.system.vo; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class AppVersionVO { + /** + * 主键 + */ + private Long id; + + /** + * 创建时间 + */ + private LocalDateTime gmtCreate; + + /** + * 修改时间 + */ + private LocalDateTime gmtModified; + + /** + * 新版本 + */ + private String version; + + /** + * 哪些版本可以升级到该版本 + */ + private String versionUse; + + /** + * 状态 + */ + private String status; + + /** + * 下载地址 + */ + private String downloadLink; + + /** + * 手工更新,自动强制更新 + */ + private String type; + + /** + * 白名单,用于测试 + */ + private String whiteList; + + /** + * 热更新包地址 + */ + private String hotUpgradeUrl; + + /** + * 更新日志 + */ + private String updateLog; + + /** + * 桌面 + */ + private boolean desktop; +} From 97ad124d42fd807ea29f785c3941f008ce1b0c6c Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 20 Oct 2023 17:47:17 +0800 Subject: [PATCH 1014/1069] fix:edit table data paging --- chat2db-client/.umirc.ts | 2 +- chat2db-client/.vscode/settings.json | 1 + .../src/blocks/Setting/About/index.less | 57 ++++++++++++++++--- .../src/blocks/Setting/About/index.tsx | 46 +++++++++++++-- chat2db-client/src/blocks/Setting/index.less | 18 ++++-- chat2db-client/src/blocks/Setting/index.tsx | 12 +++- .../src/components/BrandLogo/index.tsx | 4 +- .../src/components/Iconfont/index.tsx | 6 +- .../SearchResult/Pagination/index.tsx | 50 ++++++++-------- .../SearchResult/StatusBar/index.less | 19 +++++++ .../SearchResult/StatusBar/index.tsx | 22 +++++++ .../SearchResult/TableBox/index.tsx | 56 +++++++++--------- .../src/components/SearchResult/index.less | 20 +------ .../src/components/SearchResult/index.tsx | 21 +++---- chat2db-client/src/constants/appConfig.ts | 4 +- chat2db-client/src/i18n/zh-cn/common.ts | 3 +- chat2db-client/src/pages/main/index.less | 1 - .../Tree/TreeNodeRightClick/index.tsx | 12 ++-- 18 files changed, 238 insertions(+), 116 deletions(-) create mode 100644 chat2db-client/src/components/SearchResult/StatusBar/index.less create mode 100644 chat2db-client/src/components/SearchResult/StatusBar/index.tsx diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 063056d9c..f7ac88845 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -98,7 +98,7 @@ export default defineConfig({ define: { __ENV__: process.env.UMI_ENV, __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), - __APP_VERSION__: yarn_config.app_version || '0.0.0', + __APP_VERSION__: yarn_config.app_version || '9.9.9', __APP_PORT__: yarn_config.app_port, }, esbuildMinifyIIFE: true diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 2ff54c435..7ae4734e2 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -84,6 +84,7 @@ "uuidv", "VARCHAR", "VIEWCOLUMN", + "webp", "wireframe", "Wppk", "yapi" diff --git a/chat2db-client/src/blocks/Setting/About/index.less b/chat2db-client/src/blocks/Setting/About/index.less index 1c2a2ddaf..3f3a65236 100644 --- a/chat2db-client/src/blocks/Setting/About/index.less +++ b/chat2db-client/src/blocks/Setting/About/index.less @@ -1,16 +1,55 @@ .aboutUs { - display: flex; - flex-direction: column; - align-items: center; - - .brief { + .versionsInfo { display: flex; - flex-direction: column; align-items: center; - + padding-bottom: 10px; + border-bottom: 1px solid var(--color-border); + margin-bottom: 10px; + } + .brandLogo { + margin-right: 15px; + } + .currentVersion { + font-size: 20px; + line-height: 24px; .appName { - font-size: 20px; + margin-right: 10px; + } + } + .newVersion { + color: var(--color-text-tertiary); + font-size: 14px; + margin-top: 2px; + } + .updateButton { + margin-top: 10px; + button:nth-of-type(1) { + margin-right: 20px; } + } + .updateRule { + .updateRuleTitle { + font-size: 16px; + margin-bottom: 10px; + } + .updateRuleGroup { + display: flex; + flex-direction: column; + .updateRuleRadioContent { + display: flex; + align-items: center; + i { + margin-left: 4px; + color: var(--color-text-tertiary); + font-size: 12px; + } + } + } + } + .brief { + display: flex; + flex-direction: column; + align-items: center; .env, .version { @@ -28,4 +67,4 @@ } } } -} \ No newline at end of file +} diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index b09ed4712..2ef6875fa 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -1,16 +1,52 @@ import BrandLogo from '@/components/BrandLogo'; -import { APP_NAME, GITHUB_URL } from '@/constants/appConfig'; +import { APP_NAME, GITHUB_URL, WEBSITE_DOC } from '@/constants/appConfig'; import i18n from '@/i18n'; import React from 'react'; import styles from './index.less'; -import { formatDate, getUserTimezoneTimestamp } from '@/utils/date'; +// import { formatDate, getUserTimezoneTimestamp } from '@/utils/date'; +import { Button, Radio, Space } from 'antd'; // 关于我们 export default function AboutUs() { + const [updateRule, setUpdateRule] = React.useState(1); // 1:自动下载并安装更新 2:仅在新版本发布时提醒我 + const onChangeUpdateRul = () => { + setUpdateRule(updateRule === 1 ? 2 : 1); + }; + + const jumpDoc = () => { + window.open(WEBSITE_DOC, '_blank'); + }; + return (
    - -
    +
    + +
    +
    + {APP_NAME} + {__APP_VERSION__} +
    +
    发现新版本10.0.0
    +
    + + +
    +
    +
    +
    +
    软件更新
    + + + + 新版自动下载并安装更新 + + + 仅在新版本发布时提醒我 + + + +
    + {/*
    {APP_NAME}
    {i18n('setting.text.currentEnv')}:{__ENV__} @@ -22,7 +58,7 @@ export default function AboutUs() { {i18n('setting.text.viewingUpdateLogs')} -
    +
    */}
    ); } diff --git a/chat2db-client/src/blocks/Setting/index.less b/chat2db-client/src/blocks/Setting/index.less index 136cf5a2e..bc7e374d3 100644 --- a/chat2db-client/src/blocks/Setting/index.less +++ b/chat2db-client/src/blocks/Setting/index.less @@ -23,7 +23,6 @@ .title { font-size: 14px; margin-bottom: 10px; - i { margin-left: 10px; color: var(--color-primary); @@ -44,7 +43,7 @@ .menus { width: 200px; - // background-color: var(--color-bg-subtle); + background-color: var(--color-bg-subtle); padding: 20px; position: sticky; top: 0; @@ -62,11 +61,22 @@ display: flex; justify-content: flex-start; align-items: center; - - i { + .prefixIcon{ font-size: 16px; margin-right: 10px; } + .rightSlot{ + flex: 1; + display: flex; + justify-content: flex-end; + } + .rightSlotAbout{ + i{ + font-size: 18px; + color: var(--color-success); + } + } + } .activeMenu { diff --git a/chat2db-client/src/blocks/Setting/index.tsx b/chat2db-client/src/blocks/Setting/index.tsx index e6289c072..a57684f01 100644 --- a/chat2db-client/src/blocks/Setting/index.tsx +++ b/chat2db-client/src/blocks/Setting/index.tsx @@ -1,7 +1,7 @@ import React, { ReactNode, useEffect, useState } from 'react'; import classnames from 'classnames'; import Iconfont from '@/components/Iconfont'; -import { Modal } from 'antd'; +import { Modal, Tooltip } from 'antd'; import i18n from '@/i18n'; import BaseSetting from './BaseSetting'; import AISetting from './AiSetting'; @@ -89,6 +89,13 @@ function Setting(props: IProps) { { label: i18n('setting.nav.aboutUs'), icon: '\ue60c', + rightSlot: ( +
    + + + +
    + ), body: , }, ]; @@ -123,8 +130,9 @@ function Setting(props: IProps) { [styles.activeMenu]: t.label === menusList[currentMenu].label, })} > - + {t.label} + {t.rightSlot}
    ); })} diff --git a/chat2db-client/src/components/BrandLogo/index.tsx b/chat2db-client/src/components/BrandLogo/index.tsx index 76349757f..546316e0a 100644 --- a/chat2db-client/src/components/BrandLogo/index.tsx +++ b/chat2db-client/src/components/BrandLogo/index.tsx @@ -8,9 +8,9 @@ interface IProps extends React.DetailedHTMLProps(function BrandLogo({ className, size = 48, ...res }) { +export default memo(({ className, size = 48, ...res }) => { return ( -
    +
    ); diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 975f31027..617eb34a4 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_sze67iyjern.woff2?t=1697545966975') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_sze67iyjern.woff?t=1697545966975') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_sze67iyjern.ttf?t=1697545966975') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_a5gsjgy5ehb.woff2?t=1697773653083') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_a5gsjgy5ehb.woff?t=1697773653083') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_a5gsjgy5ehb.ttf?t=1697773653083') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/components/SearchResult/Pagination/index.tsx b/chat2db-client/src/components/SearchResult/Pagination/index.tsx index 0f45d47fe..5948bc44d 100644 --- a/chat2db-client/src/components/SearchResult/Pagination/index.tsx +++ b/chat2db-client/src/components/SearchResult/Pagination/index.tsx @@ -11,17 +11,17 @@ interface IProps { onPageSizeChange?: (pageSize: number) => void; onPageNoChange?: (pageNo: number) => void; onClickTotalBtn?: () => Promise; - data?: IResultConfig | null; + paginationConfig: IResultConfig; } type IIconType = 'pre' | 'next' | 'first' | 'last'; export default function Pagination(props: IProps) { - const { onPageNoChange, onPageSizeChange, data } = props; + const { onPageNoChange, onPageSizeChange, paginationConfig } = props; const [inputValue, setInputValue] = useState(1); const [totalLoading, setTotalLoading] = useState(false); useEffect(() => { - setInputValue(data?.pageNo ?? 1); - }, [data?.pageNo]); + setInputValue(paginationConfig?.pageNo ?? 1); + }, [paginationConfig?.pageNo]); const onInputNumberChange = (value: number | null) => { setInputValue(value); @@ -40,30 +40,32 @@ export default function Pagination(props: IProps) { if (!props.onClickTotalBtn) return; setTotalLoading(true); - let res = await props.onClickTotalBtn(); + const res = await props.onClickTotalBtn(); setTotalLoading(false); return res; }; const handleClickIcon = async (type: IIconType) => { - if (!onPageNoChange || !data) return; + if (!onPageNoChange || !paginationConfig) return; if (handleIsDisabled(type)) return; switch (type) { case 'first': onPageNoChange(1); break; case 'last': - let total = await handleClickTotalBtn(); - const { pageSize } = data || {}; - if (_.isNumber(total) && _.isNumber(pageSize)) { - props.onPageNoChange && props.onPageNoChange(Math.ceil(total / pageSize)); + { + const total = await handleClickTotalBtn(); + const { pageSize } = paginationConfig || {}; + if (_.isNumber(total) && _.isNumber(pageSize)) { + props.onPageNoChange && props.onPageNoChange(Math.ceil(total / pageSize)); + } } break; case 'pre': - onPageNoChange(data?.pageNo - 1); + onPageNoChange(paginationConfig?.pageNo - 1); break; case 'next': - onPageNoChange(data?.pageNo + 1); + onPageNoChange(paginationConfig?.pageNo + 1); break; default: break; @@ -71,30 +73,30 @@ export default function Pagination(props: IProps) { }; const handleIsDisabled = (type: IIconType) => { - if (!data) { + if (!paginationConfig) { return false; } if (type === 'first') { - return data?.pageNo === 1; + return paginationConfig?.pageNo === 1; } if (type === 'pre') { - return data?.pageNo === 1; + return paginationConfig?.pageNo === 1; } - const isNumber = _.isNumber(data.total); - const totalShow = data.pageNo * data.pageSize; - if (type === 'next' || 'last') { + const isNumber = _.isNumber(paginationConfig.total); + const totalShow = paginationConfig.pageNo * paginationConfig.pageSize; + if (type === 'next' || type === 'last') { if (isNumber) { - return totalShow > (data.total as number); + return totalShow > (paginationConfig.total as number); } - return !data?.hasNextPage; + return !paginationConfig?.hasNextPage; } // if (type === 'last') { // if (isNumber) { - // return totalShow > (data.total as number); + // return totalShow > (paginationConfig.total as number); // } - // return return !data?.hasNextPage;; + // return return !paginationConfig?.hasNextPage;; // } return true; @@ -141,7 +143,7 @@ export default function Pagination(props: IProps) {
    diff --git a/chat2db-client/src/components/SearchResult/StatusBar/index.less b/chat2db-client/src/components/SearchResult/StatusBar/index.less new file mode 100644 index 000000000..591e5a971 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/StatusBar/index.less @@ -0,0 +1,19 @@ +@import '../../../styles/var.less'; + +.statusBar { + height: 26px; + box-sizing: border-box; + padding: 4px 8px; + font-size: 12px; + display: flex; + justify-content: start; + align-items: center; + border-top: 1px solid var(--color-border-secondary); + background-color: var(--color-bg-subtle); + overflow: hidden; + flex-shrink: 0; + .f-single-line(); + & > span { + margin-right: 16px; + } +} diff --git a/chat2db-client/src/components/SearchResult/StatusBar/index.tsx b/chat2db-client/src/components/SearchResult/StatusBar/index.tsx new file mode 100644 index 000000000..c116e62e4 --- /dev/null +++ b/chat2db-client/src/components/SearchResult/StatusBar/index.tsx @@ -0,0 +1,22 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import i18n from '@/i18n'; + +interface IProps { + className?: string; + description?: string; + duration?: number; + dataLength?: number; +} + +export default memo((props) => { + const { className, description, duration, dataLength } = props; + return ( +
    + {`【${i18n('common.text.result')}】${description}.`} + {`【${i18n('common.text.timeConsuming')}】${duration}ms.`} + {!!dataLength && {`【${i18n('common.text.searchRow')}】${dataLength} ${i18n('common.text.row')}.`}} +
    + ); +}); diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index d46ac6c62..17aa52c07 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -16,6 +16,7 @@ import Iconfont from '../../Iconfont'; import StateIndicator from '../../StateIndicator'; import MonacoEditor from '../../Console/MonacoEditor'; import MyPagination from '../Pagination'; +import StatusBar from '../StatusBar'; import styles from './index.less'; import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; import { downloadFile } from '@/utils/common'; @@ -114,32 +115,28 @@ export default function TableBox(props: ITableProps) { }; useEffect(() => { + let total: any = queryResultData.fuzzyTotal; + + // 如果total是数字,且不为0,则还是使用原先的total + if (lodash.isNumber(paginationConfig.total) && paginationConfig.total !== 0) { + total = paginationConfig.total; + } + + if (!lodash.isNumber(paginationConfig.total)) { + const oldTotal = Number(paginationConfig.total.split('+')[0]); + const newTotal = Number(queryResultData.fuzzyTotal.split('+')[0]); + if (oldTotal > newTotal) { + total = paginationConfig.total; + } + } + setPaginationConfig({ ...paginationConfig, - total: queryResultData.fuzzyTotal, + total, hasNextPage: queryResultData.hasNextPage, }); }, [queryResultData]); - // 判断art-table-body是否出现了滚动条 - // useEffect(() => { - // const tableBody = document.querySelector('.art-table-body'); - // const tableHeader = document.querySelector('.art-table-header'); - // if (!tableBody) { - // return; - // } - // const tableBodyHeight = tableBody.clientHeight; - // const tableBoxHeight = tableBoxRef.current?.clientHeight || 0; - // if(!tableHeader){ - // return; - // } - // if (tableBodyHeight > tableBoxHeight) { - // tableHeader.classList.add('art-table-body-scroll'); - // } else { - // tableHeader.classList.remove('art-table-body-scroll'); - // } - // }, [tableData]); - useEffect(() => { // 每次dataList变化,都需要重新计算tableData if (!columns?.length) { @@ -449,10 +446,12 @@ export default function TableBox(props: ITableProps) { }; const onClickTotalBtn = async () => { - return sqlService.getDMLCount({ + const res = await sqlService.getDMLCount({ sql: queryResultData.sql, ...(props.executeSqlParams || {}), }); + setPaginationConfig({ ...paginationConfig, total: res }); + return res; }; // 处理撤销 @@ -701,7 +700,7 @@ export default function TableBox(props: ITableProps) {
    {allDataReady && ( -
    +

    {i18n('common.text.noData')}

    }} @@ -788,17 +787,18 @@ export default function TableBox(props: ITableProps) { />
    )} - {/* {bottomStatus} */} + ); } }; return ( -
    +
    {renderContent()} span { - margin-right: 16px; - } -} diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 0fee8b6dc..d60b9191f 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -6,6 +6,7 @@ import StateIndicator from '@/components/StateIndicator'; import Output from '@/components/Output'; import { IManageResultData } from '@/typings'; import TableBox from './TableBox'; +import StatusBar from './StatusBar'; import styles from './index.less'; import EmptyImg from '@/assets/img/empty.svg'; import i18n from '@/i18n'; @@ -42,16 +43,16 @@ export default memo((props) => { executeSqlParams={props.executeSqlParams} /> ) : ( -
    {i18n('common.text.affectedRows', queryResultData.updateCount)}
    - )} -
    -
    - {`【${i18n('common.text.result')}】${queryResultData.description}.`} - {`【${i18n('common.text.timeConsuming')}】${queryResultData.duration}ms.`} - {!!queryResultData?.dataList?.length && ( - {`【${i18n('common.text.searchRow')}】${queryResultData?.dataList?.length} ${i18n( - 'common.text.row', - )}.`} +
    +
    + {i18n('common.text.affectedRows', queryResultData.updateCount)} +
    + +
    )}
    diff --git a/chat2db-client/src/constants/appConfig.ts b/chat2db-client/src/constants/appConfig.ts index 0d46ae7d5..ef697308d 100644 --- a/chat2db-client/src/constants/appConfig.ts +++ b/chat2db-client/src/constants/appConfig.ts @@ -1,2 +1,4 @@ export const APP_NAME = 'Chat2DB'; -export const GITHUB_URL = 'https://github.com/chat2db/Chat2DB/blob/main/CHANGELOG.md' \ No newline at end of file +export const GITHUB_URL = 'https://github.com/chat2db/Chat2DB/blob/main/CHANGELOG.md' +export const WEBSITE_DOC = 'https://doc.sqlgpt.cn/changelog/' + diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 770848652..fb519096e 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -91,5 +91,6 @@ export default { 'common.text.affectedRows': '受影响行:{1}', 'common.text.selectFile' : '选择文件', 'common.text.noTableFoundUp' : '当前库没有查询到表', - 'common.text.noTableFoundDown' : '你可以在顶部切换数据库' + 'common.text.noTableFoundDown' : '你可以在顶部切换数据库', + 'common.text.updateNow' : '立即更新', }; diff --git a/chat2db-client/src/pages/main/index.less b/chat2db-client/src/pages/main/index.less index b74deb358..c539eefb4 100644 --- a/chat2db-client/src/pages/main/index.less +++ b/chat2db-client/src/pages/main/index.less @@ -34,7 +34,6 @@ .brandLogo { flex-shrink: 0; - border-radius: 50%; overflow: hidden; margin: 20px auto; cursor: pointer; diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index c0a8ef5b4..8ddaa8dc1 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -41,7 +41,7 @@ function TreeNodeRightClick(props: IProps) { })!; const monacoEditorRef = useRef(null); const OperationColumnConfig: { [key in OperationColumn]: (data: ITreeNode) => IOperationColumnConfigItem } = { - [OperationColumn.Refresh]: (data) => { + [OperationColumn.Refresh]: () => { return { text: i18n('common.button.refresh'), icon: '\uec08', @@ -50,7 +50,7 @@ function TreeNodeRightClick(props: IProps) { }, }; }, - [OperationColumn.EditTableData]: (data) => { + [OperationColumn.EditTableData]: () => { return { text: i18n('workspace.menu.editTableData'), icon: '\ue7b5', @@ -59,7 +59,7 @@ function TreeNodeRightClick(props: IProps) { }, }; }, - [OperationColumn.ExportDDL]: (data) => { + [OperationColumn.ExportDDL]: () => { return { text: i18n('workspace.menu.exportDDL'), icon: '\ue613', @@ -104,14 +104,14 @@ function TreeNodeRightClick(props: IProps) { }, }; }, - [OperationColumn.CreateConsole]: (data) => { + [OperationColumn.CreateConsole]: () => { return { text: '新建查询', icon: '\ue619', handle: () => {}, }; }, - [OperationColumn.DeleteTable]: (data) => { + [OperationColumn.DeleteTable]: () => { return { text: i18n('workspace.menu.deleteTable'), icon: '\ue6a7', @@ -139,7 +139,7 @@ function TreeNodeRightClick(props: IProps) { }, }; }, - [OperationColumn.EditSource]: (data) => { + [OperationColumn.EditSource]: () => { return { text: '编辑数据源', icon: '\ue623', From a852659fac2c4b93b2aca1826d12eb2a9f837e06 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 10:04:02 +0800 Subject: [PATCH 1025/1069] i18n --- chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx | 4 ++-- chat2db-client/src/i18n/en-us/setting.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx index ea07739f9..846a01263 100644 --- a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx +++ b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx @@ -213,9 +213,9 @@ const UpdateDetection = memo( const key = `open${Date.now()}`; notificationApi.open({ duration: 6, - message: i18n('setting.text.UpdatedLatestVersion',__APP_VERSION__), + message: i18n('setting.text.UpdatedLatestVersion', `v${__APP_VERSION__}`), style:{ - width: 260 + width: 310 }, btn: null, key, diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 3c71e343c..95e333249 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -45,8 +45,8 @@ export default { 'setting.text.autoUpdate': 'The new version automatically downloads and installs updates', 'setting.text.manualUpdate': 'Only alert me when a new version is released', 'setting.button.iSee': 'I see', - 'setting.text.newEditionIsReady': 'New version to download complete', - 'setting.text.RestartingInstall': 'Restarting the software will install the new version', + 'setting.text.newEditionIsReady': 'New version is ready', + 'setting.text.RestartingInstall': 'Restart to install the new version', 'setting.button.goToUpdate': 'Go to update', 'setting.text.UpdatedLatestVersion': 'Updated to the latest version {1}', }; From 8ce6f5a977154d699426786a9d67bbf9bd1469d6 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 22 Oct 2023 10:05:46 +0800 Subject: [PATCH 1026/1069] chore: Optimize sql editor internationalize --- chat2db-client/src/i18n/en-us/index.ts | 4 +++- chat2db-client/src/i18n/en-us/sqlEditor.ts | 7 +++++++ chat2db-client/src/i18n/zh-cn/index.ts | 4 +++- chat2db-client/src/i18n/zh-cn/sqlEditor.ts | 6 ++++++ .../src/pages/main/team/universal-drawer/index.tsx | 2 +- chat2db-client/src/utils/IntelliSense/field.ts | 5 +++-- chat2db-client/src/utils/IntelliSense/keyword.ts | 5 +++-- chat2db-client/src/utils/IntelliSense/table.ts | 3 ++- 8 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 chat2db-client/src/i18n/en-us/sqlEditor.ts create mode 100644 chat2db-client/src/i18n/zh-cn/sqlEditor.ts diff --git a/chat2db-client/src/i18n/en-us/index.ts b/chat2db-client/src/i18n/en-us/index.ts index 19d4ded20..ca3ea0641 100644 --- a/chat2db-client/src/i18n/en-us/index.ts +++ b/chat2db-client/src/i18n/en-us/index.ts @@ -9,6 +9,7 @@ import team from './team' import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; +import sqlEditor from './sqlEditor' export default { lang: 'en', @@ -22,5 +23,6 @@ export default { ...team, ...login, ...editTable, - ...editTableData + ...editTableData, + ...sqlEditor }; diff --git a/chat2db-client/src/i18n/en-us/sqlEditor.ts b/chat2db-client/src/i18n/en-us/sqlEditor.ts new file mode 100644 index 000000000..cea9e9516 --- /dev/null +++ b/chat2db-client/src/i18n/en-us/sqlEditor.ts @@ -0,0 +1,7 @@ +export default { + 'sqlEditor.text.keyword': 'Keyword', + 'sqlEditor.text.function': 'Function', + 'sqlEditor.text.tableName': 'TableName', + 'sqlEditor.text.fieldName': 'FieldName', + +}; diff --git a/chat2db-client/src/i18n/zh-cn/index.ts b/chat2db-client/src/i18n/zh-cn/index.ts index 618a48380..db58915d9 100644 --- a/chat2db-client/src/i18n/zh-cn/index.ts +++ b/chat2db-client/src/i18n/zh-cn/index.ts @@ -10,6 +10,7 @@ import team from './team' import login from './login'; import editTable from './editTable'; import editTableData from './editTableData'; +import sqlEditor from './sqlEditor' export default { lang: LangType.ZH_CN, @@ -24,5 +25,6 @@ export default { ...team, ...login, ...editTable, - ...editTableData + ...editTableData, + ...sqlEditor }; diff --git a/chat2db-client/src/i18n/zh-cn/sqlEditor.ts b/chat2db-client/src/i18n/zh-cn/sqlEditor.ts new file mode 100644 index 000000000..27207f6af --- /dev/null +++ b/chat2db-client/src/i18n/zh-cn/sqlEditor.ts @@ -0,0 +1,6 @@ +export default { + 'sqlEditor.text.keyword': '关键词', + 'sqlEditor.text.function': '函数', + 'sqlEditor.text.tableName': '表名', + 'sqlEditor.text.fieldName': '字段名', +}; diff --git a/chat2db-client/src/pages/main/team/universal-drawer/index.tsx b/chat2db-client/src/pages/main/team/universal-drawer/index.tsx index cd20d0be5..78686cd9b 100644 --- a/chat2db-client/src/pages/main/team/universal-drawer/index.tsx +++ b/chat2db-client/src/pages/main/team/universal-drawer/index.tsx @@ -338,7 +338,7 @@ function UniversalDrawer(props: IProps) { if (!requestApi || !isNumber(props.byId)) { return; } - let res = await requestApi({ + const res = await requestApi({ searchKey: searchKey || pagination.searchKey, pageNo, pageSize, diff --git a/chat2db-client/src/utils/IntelliSense/field.ts b/chat2db-client/src/utils/IntelliSense/field.ts index 28a1f3877..a39c63376 100644 --- a/chat2db-client/src/utils/IntelliSense/field.ts +++ b/chat2db-client/src/utils/IntelliSense/field.ts @@ -1,5 +1,6 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import sqlService from '@/service/sql'; +import i18n from '@/i18n'; let fieldList: Record> = {}; @@ -48,7 +49,7 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa intelliSenseField.dispose(); fieldList = {}; intelliSenseField = monaco.languages.registerCompletionItemProvider('sql', { - triggerCharacters: [' ', ',','.', '('], + triggerCharacters: [' ', ',', '.', '('], provideCompletionItems: async (model, position) => { // 获取到当前行文本 const textUntilPosition = model.getValueInRange({ @@ -84,7 +85,7 @@ const registerIntelliSenseField = (tableList: string[], dataSourceId, databaseNa label: { label: fieldObj.name, detail: `(${fieldObj.tableName})`, - description: '字段名', + description: i18n('sqlEditor.text.fieldName'), }, kind: monaco.languages.CompletionItemKind.Field, insertText: fieldObj.name, diff --git a/chat2db-client/src/utils/IntelliSense/keyword.ts b/chat2db-client/src/utils/IntelliSense/keyword.ts index f09dada45..ccae05991 100644 --- a/chat2db-client/src/utils/IntelliSense/keyword.ts +++ b/chat2db-client/src/utils/IntelliSense/keyword.ts @@ -1,6 +1,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { DatabaseTypeCode } from '@/constants'; import intelliSense from '@/constants/IntelliSense'; +import i18n from '@/i18n'; /** 关键词 */ const getSQLKeywords = (keywords: string[]) => { @@ -8,7 +9,7 @@ const getSQLKeywords = (keywords: string[]) => { label: { label: key, detail: '', - description: '关键词', + description: i18n('sqlEditor.text.keyword'), }, kind: monaco.languages.CompletionItemKind.Text, insertText: key, @@ -22,7 +23,7 @@ const getSQLFunctions = (functions: string[]) => { label: { label: key, detail: '', - description: '函数', + description: i18n('sqlEditor.text.function'), sortText: '09', }, // kind: monaco.languages.CompletionItemKind.Method, diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index 451c61b78..e7740bb7f 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -1,6 +1,7 @@ import { DatabaseTypeCode } from '@/constants'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import { addIntelliSenseField } from './field'; +import i18n from '@/i18n'; /** 当前库下的表 */ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { @@ -72,7 +73,7 @@ const registerIntelliSenseTable = ( label: { label: tableName.name, detail: databaseName ? `(${databaseName})` : null, - description: '表名', + description: i18n('sqlEditor.text.tableName'), }, kind: monaco.languages.CompletionItemKind.Folder, insertText: handleInsertText(tableName.name, databaseCode), From 8d0517c98628b6ebab139ee59dbfa81079e14fa1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 10:06:36 +0800 Subject: [PATCH 1027/1069] text:release_test --- .github/workflows/release_test.yml | 46 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 17eb6fe75..3ac5c7686 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -132,7 +132,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --app_version=99.0.${{ github.run_id }} --app_port=10822 + yarn run build:web:prod --app_version=99.1.${{ 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/ @@ -145,23 +145,23 @@ jobs: run: | cd chat2db-client mkdir versions - mkdir versions/99.0.${{ github.run_id }} - mkdir versions/99.0.${{ github.run_id }}/static + mkdir versions/99.1.${{ github.run_id }} + mkdir versions/99.1.${{ github.run_id }}/static touch version - echo -n 99.0.${{ github.run_id }} > version + echo -n 99.1.${{ 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 + cp chat2db-server/chat2db-server-start/target/chat2db-server-start.jar chat2db-client/versions/99.1.${{ github.run_id }}/static/ + cp -r chat2db-server/chat2db-server-start/target/lib chat2db-client/versions/99.1.${{ 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 }}/ + yarn run build:web:desktop --app_version=99.1.${{ github.run_id }} --app_port=10822 + cp -r dist ./versions/99.1.${{ github.run_id }}/ rm -r dist # windows @@ -174,7 +174,7 @@ jobs: 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" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.win.publisherName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.1.${{ github.run_id }}-Test --win --x64" # macos amd64 - name: Build/release Electron app for MacOS X64 @@ -186,13 +186,13 @@ jobs: 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" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.1.${{ github.run_id }}-Test --mac --x64" # amd64 notarization - name: Notarization amd64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} run: | - xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg + xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.1.${{ github.run_id }}-Test.dmg # macos arm64 - name: Build/release Electron app for MacOS arm64 @@ -204,13 +204,13 @@ jobs: 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" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.1.${{ github.run_id }}-Test --mac --arm64" # arm notarization - name: Notarization arm64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'arm64' }} run: | - xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg + xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.1.${{ github.run_id }}-Test-arm64.dmg # Linux - name: Delete File @@ -228,7 +228,7 @@ jobs: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} 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 --linux" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.1.${{ github.run_id }}-Test --linux" # 准备要需要的数据 Windows - name: Prepare upload for Windows @@ -242,11 +242,11 @@ jobs: if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} run: | mkdir oss_temp_file - cp chat2db-client/versions/99.0.${{ github.run_id }}/static/chat2db-server-start.jar ./oss_temp_file + cp chat2db-client/versions/99.1.${{ 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 + cp -r chat2db-client/versions/99.1.${{ github.run_id }}/dist ./oss_temp_file/dist + cd chat2db-client/versions/99.1.${{ github.run_id }}/ && zip -r 99.1.${{ github.run_id }}.zip ./ + cp -r 99.1.${{ github.run_id }}.zip ../../../oss_temp_file cd static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file @@ -274,7 +274,7 @@ jobs: 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 }}/ + ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.1.${{ github.run_id }}/ # 构建完成通知 - name: Send dingtalk message for Windows @@ -286,7 +286,7 @@ jobs: 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) " + "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.1.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.1.${{ github.run_id }}-Test.exe](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.1.${{ github.run_id }}-Test.exe) " } # 构建完成通知 @@ -299,7 +299,7 @@ jobs: content: | { "title": "MacOS-amd64-test-构建完成通知", - "text": "# MacOS-amd64-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) " + "text": "# MacOS-amd64-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.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.dmg](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/chat2db-server-start.zip) " } # 构建完成通知 @@ -312,7 +312,7 @@ jobs: 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) " + "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.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test-arm64.dmg](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test-arm64.dmg) " } # 构建完成通知 @@ -325,5 +325,5 @@ jobs: 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) " + "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.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.AppImage](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.AppImage) " } From 553a77c250ee7edf0f81b668014d664d39f6d621 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 13:33:12 +0800 Subject: [PATCH 1028/1069] style:UpdateDetection --- .github/workflows/release_test.yml | 46 ++--- .../src/blocks/Setting/About/index.tsx | 14 +- .../blocks/Setting/UpdateDetection/index.less | 57 ++++++ .../blocks/Setting/UpdateDetection/index.tsx | 166 +++++++++++------- .../src/components/Iconfont/index.tsx | 6 +- chat2db-client/src/i18n/en-us/setting.ts | 5 +- chat2db-client/src/i18n/zh-cn/setting.ts | 3 +- 7 files changed, 194 insertions(+), 103 deletions(-) create mode 100644 chat2db-client/src/blocks/Setting/UpdateDetection/index.less diff --git a/.github/workflows/release_test.yml b/.github/workflows/release_test.yml index 3ac5c7686..17eb6fe75 100644 --- a/.github/workflows/release_test.yml +++ b/.github/workflows/release_test.yml @@ -132,7 +132,7 @@ jobs: run: | cd chat2db-client yarn install - yarn run build:web:prod --app_version=99.1.${{ github.run_id }} --app_port=10822 + 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/ @@ -145,23 +145,23 @@ jobs: run: | cd chat2db-client mkdir versions - mkdir versions/99.1.${{ github.run_id }} - mkdir versions/99.1.${{ github.run_id }}/static + mkdir versions/99.0.${{ github.run_id }} + mkdir versions/99.0.${{ github.run_id }}/static touch version - echo -n 99.1.${{ github.run_id }} > 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.1.${{ github.run_id }}/static/ - cp -r chat2db-server/chat2db-server-start/target/lib chat2db-client/versions/99.1.${{ github.run_id }}/static/lib + 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.1.${{ github.run_id }} --app_port=10822 - cp -r dist ./versions/99.1.${{ github.run_id }}/ + 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 @@ -174,7 +174,7 @@ jobs: 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.1.${{ github.run_id }}-Test --win --x64" + 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 amd64 - name: Build/release Electron app for MacOS X64 @@ -186,13 +186,13 @@ jobs: 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.1.${{ github.run_id }}-Test --mac --x64" + 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" # amd64 notarization - name: Notarization amd64 App if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} run: | - xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.1.${{ github.run_id }}-Test.dmg + xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test.dmg # macos arm64 - name: Build/release Electron app for MacOS arm64 @@ -204,13 +204,13 @@ jobs: 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.1.${{ github.run_id }}-Test --mac --arm64" + 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 altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.1.${{ github.run_id }}-Test-arm64.dmg + xcrun altool --notarize-app --primary-bundle-id "${{secrets.MAC_PRIMARY_BUNDLE_ID}}" --username "${{secrets.MAC_APPLE_ID}}" --password "${{secrets.MAC_APPLE_PASSWORD}}" --asc-provider "${{secrets.MAC_ASC_PROVIDER}}" -t osx --file chat2db-client/release/Chat2DB-Test-99.0.${{ github.run_id }}-Test-arm64.dmg # Linux - name: Delete File @@ -228,7 +228,7 @@ jobs: package_root: "chat2db-client/" GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }} skip_build: true - args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.1.${{ github.run_id }}-Test --linux" + args: "-c.appId=com.chat2db.test -c.productName=Chat2DB-Test -c.nsis.shortcutName=Chat2DB-Test -c.extraMetadata.version=99.0.${{ github.run_id }}-Test --linux" # 准备要需要的数据 Windows - name: Prepare upload for Windows @@ -242,11 +242,11 @@ jobs: if: ${{ runner.os == 'macOS' && matrix.arch == 'amd64' }} run: | mkdir oss_temp_file - cp chat2db-client/versions/99.1.${{ github.run_id }}/static/chat2db-server-start.jar ./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.1.${{ github.run_id }}/dist ./oss_temp_file/dist - cd chat2db-client/versions/99.1.${{ github.run_id }}/ && zip -r 99.1.${{ github.run_id }}.zip ./ - cp -r 99.1.${{ github.run_id }}.zip ../../../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 @@ -274,7 +274,7 @@ jobs: ossutil-version: "1.7.16" - name: Upload to oss run: | - ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.1.${{ github.run_id }}/ + ossutil cp -rf --acl=public-read ./oss_temp_file/ oss://chat2db-client/test/99.0.${{ github.run_id }}/ # 构建完成通知 - name: Send dingtalk message for Windows @@ -286,7 +286,7 @@ jobs: 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.1.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.1.${{ github.run_id }}-Test.exe](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test%20Setup%2099.1.${{ github.run_id }}-Test.exe) " + "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) " } # 构建完成通知 @@ -299,7 +299,7 @@ jobs: content: | { "title": "MacOS-amd64-test-构建完成通知", - "text": "# MacOS-amd64-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.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.dmg](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.dmg) \n ### jar包下载地址:[https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/chat2db-server-start.zip](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/chat2db-server-start.zip) " + "text": "# MacOS-amd64-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) " } # 构建完成通知 @@ -312,7 +312,7 @@ jobs: 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.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test-arm64.dmg](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test-arm64.dmg) " + "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) " } # 构建完成通知 @@ -325,5 +325,5 @@ jobs: 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.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.AppImage](https://oss.sqlgpt.cn/test/99.1.${{ github.run_id }}/Chat2DB-Test-99.1.${{ github.run_id }}-Test.AppImage) " + "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/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index 2c762d4b6..18ae5ce07 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -1,15 +1,13 @@ import React, { useEffect, useMemo } from 'react'; import styles from './index.less'; -// import i18n from '@/i18n'; +import i18n from '@/i18n'; import BrandLogo from '@/components/BrandLogo'; import { APP_NAME, WEBSITE_DOC } from '@/constants/appConfig'; -// import { formatDate, getUserTimezoneTimestamp } from '@/utils/date'; import { Button, Radio, Space } from 'antd'; import configService from '@/service/config'; -import { DownloadOutlined,RedoOutlined } from '@ant-design/icons'; +import { DownloadOutlined } from '@ant-design/icons'; import { IUpdateDetectionData } from '../index'; import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection'; -import i18n from '@/i18n'; interface IProps { updateDetectionData: IUpdateDetectionData | null; @@ -44,7 +42,7 @@ export default function AboutUs(props: IProps) { return ( ); - // 超时后端如何处理 TODO: case UpdatedStatusEnum.TIMEOUT: return ( @@ -95,7 +91,7 @@ export default function AboutUs(props: IProps) {
    {updateDetectionData?.needUpdate ? ( UpdatedStatusEnum.UPDATED === updateDetectionData?.updatedStatusEnum ? - {i18n('setting.text.RestartingInstall')} + {i18n('setting.text.newEditionIsReady')} : {i18n('setting.text.discoverNewVersion', updateDetectionData?.version)} ) : ( diff --git a/chat2db-client/src/blocks/Setting/UpdateDetection/index.less b/chat2db-client/src/blocks/Setting/UpdateDetection/index.less new file mode 100644 index 000000000..5ef0cf42d --- /dev/null +++ b/chat2db-client/src/blocks/Setting/UpdateDetection/index.less @@ -0,0 +1,57 @@ +@import '../../../styles/var.less'; +.versionText{ + color: var(--color-primary); +} + +.notificationBtnBox{ + display: flex; + align-items: center; + justify-content: space-between; +} + +.updateReminder{ + display: flex; + align-items: center; + .bell{ + // 做个动画,当前div像铃铛一样摇晃,以div的上中心点为中心,动画循环10次以后停止 + animation: shake 0.2s linear 10; + margin-right: 4px; + } + i{ + color: var(--color-primary); + } +} + + +.notification{ + z-index: 999 !important; + width: 300px !important; + font-size: 12px !important; + padding: 8px 12px !important; + :global{ + .ant-notification-notice-close{ + top: 10px !important; + inset-inline-end: 8px !important; + } + } +} +// 做个动画,当前div像铃铛一样摇晃,以div的上中心点为中心,左右3px晃动 +@keyframes shake { + 0% { + transform: rotate(0deg); + } + 25% { + transform: rotate(8deg); + } + 50% { + transform: rotate(0deg); + } + 75% { + transform: rotate(-8deg); + } + 100% { + transform: rotate(0deg); + } +} + + diff --git a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx index 846a01263..eb31c04b1 100644 --- a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx +++ b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx @@ -1,10 +1,11 @@ import React, { memo, useEffect, forwardRef, ForwardedRef, useImperativeHandle } from 'react'; import configService from '@/service/config'; +import styles from './index.less'; import { notification, Button, Space } from 'antd'; -// import i18n from '@/i18n'; import { compareVersion } from '@/utils'; import { IUpdateDetectionData } from '../index'; -import i18n from '@/i18n'; +import i18n, { i18nElement } from '@/i18n'; +import Iconfont from '@/components/Iconfont'; export enum UpdatedStatusEnum { // 未更新 @@ -26,11 +27,11 @@ interface IProps { } export interface IUpdateDetectionRef { - openDownload: () => void; + openDownload: (data: IUpdateDetectionData) => void; } // 轮训间隔时间 -const INTERVAL_TIME = 10000; +const INTERVAL_TIME = 5000; // 最大轮训次数 const MAX_TIMES = 200; @@ -39,6 +40,7 @@ const UpdateDetection = memo( const { openSettingModal, updateDetectionData, setUpdateDetectionData } = props; const [notificationApi, notificationDom] = notification.useNotification(); const timesRef = React.useRef(0); + console.log(updateDetectionData); useEffect(() => { checkUpdate(); @@ -58,14 +60,22 @@ const UpdateDetection = memo( return; } + const _updateDetectionData = { + ...res, + needUpdate: compareVersion(res.version, __APP_VERSION__) === 1, + updatedStatusEnum: UpdatedStatusEnum.NOT_UPDATED, + }; + + setUpdateDetectionData(_updateDetectionData); + // 如果监测到localStorage里面有存的老版本,那么就提示首次更新 - const lastLatestVersion = localStorage.getItem('last-latest-version') - if(lastLatestVersion && compareVersion(lastLatestVersion, __APP_VERSION__) === -1){ + const lastLatestVersion = localStorage.getItem('last-latest-version'); + if (lastLatestVersion && compareVersion(lastLatestVersion, __APP_VERSION__) === -1) { openNotificationUpdated(); } // 最新版本存入localStorage - localStorage.setItem('last-latest-version',__APP_VERSION__) + localStorage.setItem('last-latest-version', __APP_VERSION__); // 如果是最新版本,那么就不用更新 if (compareVersion(res.version, __APP_VERSION__) !== 1) { @@ -73,83 +83,76 @@ const UpdateDetection = memo( } // 如果用户点过知道那么,就不用提示更新 - if (localStorage.getItem('i-see-latest-version') === res.version) { + if (localStorage.getItem('i-see-latest-version') === res.version && res.type === 'manual') { return; } - const _updateDetectionData = { - ...res, - needUpdate: compareVersion(res.version, __APP_VERSION__) === 1, - updatedStatusEnum: UpdatedStatusEnum.NOT_UPDATED, - } - - setUpdateDetectionData(_updateDetectionData); // 如果是自动更新那么就轮询调后端接口,判断是否更新完成 if (res.type === 'auto') { - timesRef.current = 0 - isUpdateSuccess(); - setUpdateDetectionData({ - ..._updateDetectionData, - updatedStatusEnum: UpdatedStatusEnum.UPDATING, - - }) + timesRef.current = 0; + openDownload(_updateDetectionData); } else { // 如果是手动更新,那么就提示下载 if (res.version) { - openNotificationManual(res.version); + openNotificationManual(res); } } }); } - function isUpdateSuccess() { + function isUpdateSuccess(_updateDetectionData) { if (timesRef.current > MAX_TIMES) { setUpdateDetectionData({ - ...updateDetectionData!, + ..._updateDetectionData!, updatedStatusEnum: UpdatedStatusEnum.TIMEOUT, }); return; } - timesRef.current = timesRef.current + 1 - - if (!updateDetectionData?.version) { + timesRef.current = timesRef.current + 1; + if (!_updateDetectionData?.version) { return; } configService .isUpdateSuccess({ - version: updateDetectionData.version, + version: _updateDetectionData.version, }) .then((res) => { if (res) { setUpdateDetectionData({ - ...updateDetectionData!, + ..._updateDetectionData!, updatedStatusEnum: UpdatedStatusEnum.UPDATED, }); - openNotificationAuto(); + openNotificationAuto(_updateDetectionData); } else { setTimeout(() => { - isUpdateSuccess(); + isUpdateSuccess(_updateDetectionData); }, INTERVAL_TIME); } }); } - function go() { - // window.open(responseText.downloadLink); - notificationApi.destroy(); - } + // function go() { + // // window.open(responseText.downloadLink); + // notificationApi.destroy(); + // } - const handleISee = () => { + const handleISee = (_updateDetectionData) => { // 存入localStorage - localStorage.setItem('i-see-latest-version', updateDetectionData?.version || ''); + localStorage.setItem('i-see-latest-version', _updateDetectionData?.version || ''); notificationApi.destroy(); }; - const openNotificationAuto = () => { + const openNotificationAuto = (_updateDetectionData) => { const key = `open${Date.now()}`; const btn = ( - {/* - +
    ); notificationApi.open({ duration: null, - message: i18n('setting.text.discoverNewVersion',version), - // description: version, - style:{ - width: 260 + className: styles.notification, + message: ( +
    +
    + +
    + {i18n('common.text.updateReminder')} +
    + ), + description: i18nElement( + 'setting.text.discoverNewVersion', + v{_updateDetectionData.version}, + ), + style: { + width: 260, + backgroundColor: 'var(--color-bg-subtle)', }, btn, key, @@ -212,10 +242,20 @@ const UpdateDetection = memo( const openNotificationUpdated = () => { const key = `open${Date.now()}`; notificationApi.open({ + className: styles.notification, duration: 6, - message: i18n('setting.text.UpdatedLatestVersion', `v${__APP_VERSION__}`), - style:{ - width: 310 + message: ( +
    +
    + +
    + {i18n('common.text.updateReminder')} +
    + ), + description: i18n('setting.text.UpdatedLatestVersion', `v${__APP_VERSION__}`), + style: { + width: 310, + backgroundColor: 'var(--color-bg-subtle)', }, btn: null, key, @@ -223,15 +263,15 @@ const UpdateDetection = memo( }); }; - const openDownload = () => { - if (!updateDetectionData) { + const openDownload = (_updateDetectionData: IUpdateDetectionData) => { + if (!_updateDetectionData) { return; } - configService.updateDesktopVersion({ ...updateDetectionData }).then(() => { - timesRef.current = 0 - isUpdateSuccess(); + configService.updateDesktopVersion(_updateDetectionData).then(() => { + timesRef.current = 0; + isUpdateSuccess(_updateDetectionData); setUpdateDetectionData({ - ...updateDetectionData, + ..._updateDetectionData, updatedStatusEnum: UpdatedStatusEnum.UPDATING, }); }); @@ -239,7 +279,7 @@ const UpdateDetection = memo( useImperativeHandle(ref, () => ({ openDownload, - })) + })); return <>{notificationDom}; }), diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 4542982d5..57921af95 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_awwgvqmgrl.woff2?t=1697796152988') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_awwgvqmgrl.woff?t=1697796152988') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_awwgvqmgrl.ttf?t=1697796152988') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_c0oi4jl53yr.woff2?t=1697950460220') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_c0oi4jl53yr.woff?t=1697950460220') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_c0oi4jl53yr.ttf?t=1697950460220') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index 95e333249..ac62d72f1 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -40,13 +40,12 @@ export default { 'setting.button.restart': 'Restart', 'setting.text.discoverNewVersion': 'Discover new version {1}', 'setting.text.isLatestVersion': 'This is the latest version', - 'setting.button.changeLog': 'Change log', + 'setting.button.changeLog': 'ChangeLog', 'setting.title.updateRule': 'Update rule', 'setting.text.autoUpdate': 'The new version automatically downloads and installs updates', 'setting.text.manualUpdate': 'Only alert me when a new version is released', 'setting.button.iSee': 'I see', - 'setting.text.newEditionIsReady': 'New version is ready', - 'setting.text.RestartingInstall': 'Restart to install the new version', + 'setting.text.newEditionIsReady': 'New version to download completed, restart the software will install the new version', 'setting.button.goToUpdate': 'Go to update', 'setting.text.UpdatedLatestVersion': 'Updated to the latest version {1}', }; diff --git a/chat2db-client/src/i18n/zh-cn/setting.ts b/chat2db-client/src/i18n/zh-cn/setting.ts index 247c0ca1e..c55c78e72 100644 --- a/chat2db-client/src/i18n/zh-cn/setting.ts +++ b/chat2db-client/src/i18n/zh-cn/setting.ts @@ -45,8 +45,7 @@ export default { 'setting.text.autoUpdate': '新版自动下载并安装更新', 'setting.text.manualUpdate': '仅在新版本发布时提醒我', 'setting.button.iSee': '我知道了', - 'setting.text.newEditionIsReady': '新版本以下载完成', - 'setting.text.RestartingInstall': '重启软件将会安装新版本', + 'setting.text.newEditionIsReady': '新版本以下载完成, 重启软件将会安装新版本', 'setting.button.goToUpdate': '前往更新', 'setting.text.UpdatedLatestVersion': '已更新到最新版本 {1}', From 349e57bb1463e54a20fb816c4f4976584412b8fd Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 15:19:31 +0800 Subject: [PATCH 1029/1069] electron: stopJavaService --- chat2db-client/.eslintrc.js | 1 + chat2db-client/.umirc.ts | 2 +- .../src/blocks/Setting/About/index.tsx | 18 ++++--- .../src/components/Iconfont/index.tsx | 6 +-- chat2db-client/src/layouts/index.tsx | 14 +++++ chat2db-client/src/main/index.js | 54 ++++--------------- chat2db-client/src/main/preload.js | 12 ++++- chat2db-client/src/service/config.ts | 8 ++- 8 files changed, 58 insertions(+), 57 deletions(-) diff --git a/chat2db-client/.eslintrc.js b/chat2db-client/.eslintrc.js index 4b0e38a63..14dabe6ce 100644 --- a/chat2db-client/.eslintrc.js +++ b/chat2db-client/.eslintrc.js @@ -28,6 +28,7 @@ module.exports = { ecmaVersion: 'latest', sourceType: 'module', }, + ignorePatterns: ['src/main'], rules: { 'func-names': 0, // 函数表达式必须有名字 'one-var': [1, 'never'], // 连续声明 diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index ebfa9a910..0ed67cc99 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -62,7 +62,7 @@ export default defineConfig({ localStorage.clear(); localStorage.setItem('app-local-storage-versions', 'v3'); }`, - `if (window.myAPI) { window.myAPI.startServerForSpawn() }`, + `if (window.electronApi) { window.electronApi.startServerForSpawn() }`, // `if ("serviceWorker" in navigator) { // window.addEventListener("load", function () { // navigator.serviceWorker diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index 18ae5ce07..780356bbd 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -8,6 +8,7 @@ import configService from '@/service/config'; import { DownloadOutlined } from '@ant-design/icons'; import { IUpdateDetectionData } from '../index'; import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection'; +import Iconfont from '@/components/Iconfont'; interface IProps { updateDetectionData: IUpdateDetectionData | null; @@ -33,6 +34,11 @@ export default function AboutUs(props: IProps) { window.open(WEBSITE_DOC, '_blank'); }; + const restartApp = () => { + console.log(window.electronApi) + window.electronApi?.quitApp(); + } + const updateButton = useMemo(() => { if (!updateDetectionData?.needUpdate) { return false; @@ -68,12 +74,12 @@ export default function AboutUs(props: IProps) { {i18n('setting.button.redownload')} ); - // case UpdatedStatusEnum.UPDATED: - // return ( - // - // ); + case UpdatedStatusEnum.UPDATED: + return ( + + ); default: return false; } diff --git a/chat2db-client/src/components/Iconfont/index.tsx b/chat2db-client/src/components/Iconfont/index.tsx index 57921af95..cd241c12d 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_c0oi4jl53yr.woff2?t=1697950460220') format('woff2'), - url('//at.alicdn.com/t/c/font_3633546_c0oi4jl53yr.woff?t=1697950460220') format('woff'), - url('//at.alicdn.com/t/c/font_3633546_c0oi4jl53yr.ttf?t=1697950460220') format('truetype'); + src: url('//at.alicdn.com/t/c/font_3633546_ios3b6gmgpb.woff2?t=1697956195651') format('woff2'), + url('//at.alicdn.com/t/c/font_3633546_ios3b6gmgpb.woff?t=1697956195651') format('woff'), + url('//at.alicdn.com/t/c/font_3633546_ios3b6gmgpb.ttf?t=1697956195651') format('truetype'); } `; const style = document.createElement('style'); diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 258e2f600..221342d09 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -6,6 +6,7 @@ import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig, injectThemeVar } from '@/theme'; import { IVersionResponse } from '@/typings'; import miscService from '@/service/misc'; +import configService from '@/service/config'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import { useTheme } from '@/hooks'; @@ -30,6 +31,11 @@ declare global { _appGatewayParams: IVersionResponse; _notificationApi: any; _indexedDB: any; + electronApi?: { + startServerForSpawn: () => void; + quitApp: () => void; + beforeQuitApp: (fn: () => void) => void; + }; } const __APP_VERSION__: string; const __BUILD_TIME__: string; @@ -92,12 +98,20 @@ function AppContainer() { // 初始化app function collectInitApp() { + registerElectronApi(); monitorOsTheme(); initLang(); initIndexedDB(); setInitEnd(true); } + // 注册Electron关闭时,关闭服务 + function registerElectronApi() { + window.electronApi?.beforeQuitApp(()=>{ + configService.stopJavaService(); + }) + } + // 初始化indexedDB function initIndexedDB() { indexedDB.createDB('chat2db', 1).then((db) => { diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index ad5ba65e2..cded6a0b0 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -69,51 +69,7 @@ app.on('window-all-closed', () => { }); app.on('before-quit', (event) => { - // const isWindows = os.platform() === 'win32'; - // let ports = [10821, 10822, 10824]; // 日常端口、测试包端口、线上包端口 - // for (let port of ports) { - // let command = ''; - // if (isWindows) { - // command = `netstat -ano | findstr:${port}`; - // } else { - // command = `lsof -i :${port} | awk '{print $2}'`; - // } - - // exec(command, (err, stdout) => { - // if (err) { - // console.error(`exec error: ${err}`); - // return; - // } - - // let pidArr = []; - // if (isWindows) { - // const lines = stdout.trim().split('\n'); - // pidArr = lines.map((line) => line.trim().split(/\s+/)[4]).filter((pid) => !isNaN(pid)); - // } else { - // pidArr = stdout.trim().split('\n'); - // } - - // if (pidArr.length) { - // try { - // (pidArr || []).forEach((pid) => { - // !!pid && !isNaN(pid) && process.kill(pid); - // }); - // } catch (error) { - // console.error(`Error killing process: ${error}`); - // } - // } - // }); - // } - try { - const request = net.request({ - headers: { - 'Content-Type': 'application/json', - }, - method: 'POST', - url: 'http://127.0.0.1:10824/api/system/stop', - }); - request.end(); - } catch (error) {} + mainWindow.webContents.send('before-quit-app'); }); ipcMain.handle('get-product-name', (event) => { @@ -121,3 +77,11 @@ ipcMain.handle('get-product-name', (event) => { const { name } = path.parse(exePath); return name; }); + +// 注册退出应用事件 +ipcMain.on('quit-app', (event) => { + app.quit(); +}); + + + diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index 8e1e1b9b6..64c344545 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -4,7 +4,7 @@ const { JAVA_APP_NAME, JAVA_PATH } = require('./constants'); const path = require('path'); const { readVersion } = require('./utils'); -contextBridge.exposeInMainWorld('myAPI', { +contextBridge.exposeInMainWorld('electronApi', { startServerForSpawn: async () => { const javaPath = path.join(__dirname, '../..', `./versions/${readVersion()}`, `./static/${JAVA_APP_NAME}`); const libPath = path.join(__dirname, '../..', `./versions/${readVersion()}`, './static/lib'); @@ -40,4 +40,14 @@ contextBridge.exposeInMainWorld('myAPI', { console.log(`child process exited with code ${code}`); }); }, + quitApp: () => { + ipcRenderer.send('quit-app'); + }, + + beforeQuitAPP: (callback)=>{ + ipcRenderer.on('before-quit-app', () => { + callback(); + }); + } }); + diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index d78c99d29..2ddc6b682 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -78,6 +78,11 @@ const setAppUpdateType = createRequest('/api/sy method: 'post', }); +// 退出electron时关闭后端服务 +const stopJavaService = createRequest('/api/system/stop', { + method: 'post', +}); + export default { getSystemConfig, setSystemConfig, @@ -87,5 +92,6 @@ export default { getLatestVersion, isUpdateSuccess, updateDesktopVersion, - setAppUpdateType + setAppUpdateType, + stopJavaService }; From ed44a29aed8530f8d24835a9bbed5528d0e76912 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 15:20:23 +0800 Subject: [PATCH 1030/1069] setting.json --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0bd22b7a1..3266af7df 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,8 +17,11 @@ "datasource", "DBAI", "DBHUB", + "Dchat", "dingtalk", + "Dloader", "Dmaven", + "Dproject", "Dserver", "Dspring", "echart", From 987b4f2ad79487d7a85be2290ca40c984de78e68 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 16:12:13 +0800 Subject: [PATCH 1031/1069] fix:electron version --- .../blocks/DatabaseTableEditor/ColumnList/index.tsx | 4 ++-- .../blocks/DatabaseTableEditor/IncludeCol/index.tsx | 4 ++-- .../blocks/DatabaseTableEditor/IndexList/index.tsx | 4 ++-- chat2db-client/src/layouts/index.tsx | 7 ++++++- chat2db-client/src/main/index.js | 13 +++++++------ chat2db-client/src/main/menu.js | 4 ++-- chat2db-client/src/main/preload.js | 6 ++++-- .../components/Tree/TreeNodeRightClick/index.tsx | 4 ++-- 8 files changed, 27 insertions(+), 19 deletions(-) diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx index 35dbb9244..195ba57e7 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/ColumnList/index.tsx @@ -273,7 +273,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef) } }; - const handelFieldsChange = (field: any) => { + const handleFieldsChange = (field: any) => { let { value } = field[0]; const { name: nameList } = field[0]; const name = nameList[0]; @@ -462,7 +462,7 @@ const ColumnList = forwardRef((props: IProps, ref: ForwardedRef)
    */} -
    +
    i.key!)} strategy={verticalListSortingStrategy}> diff --git a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx index c53553247..6590a1623 100644 --- a/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx +++ b/chat2db-client/src/blocks/DatabaseTableEditor/IncludeCol/index.tsx @@ -207,7 +207,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) }); } - const handelFieldsChange = (field: any) => { + const handleFieldsChange = (field: any) => { const { value } = field[0]; const { name: nameList } = field[0]; const name = nameList[0]; @@ -241,7 +241,7 @@ const IncludeCol = forwardRef((props: IProps, ref: ForwardedRef) {/* */}
    - +
    ) = setDataSource(newList || []); }; - const handelFieldsChange = (field: any) => { + const handleFieldsChange = (field: any) => { let { value } = field[0]; const { name: nameList } = field[0]; const name = nameList[0]; @@ -376,7 +376,7 @@ const IndexList = forwardRef((props: IProps, ref: ForwardedRef) = */} - +
    i.key!)} strategy={verticalListSortingStrategy}> diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 221342d09..98ebb67e5 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -35,6 +35,7 @@ declare global { startServerForSpawn: () => void; quitApp: () => void; beforeQuitApp: (fn: () => void) => void; + registerAppMenu: (data:any) => void; }; } const __APP_VERSION__: string; @@ -107,8 +108,12 @@ function AppContainer() { // 注册Electron关闭时,关闭服务 function registerElectronApi() { + window.electronApi?.registerAppMenu({ + version: __APP_VERSION__, + }) + window.electronApi?.beforeQuitApp(()=>{ - configService.stopJavaService(); + configService.stopJavaService() }) } diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index cded6a0b0..91fa470ce 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -52,7 +52,7 @@ app.commandLine.appendSwitch('--disable-gpu-sandbox'); app.on('ready', () => { createWindow(); - registerAppMenu(); + registerAppMenu(mainWindow); registerAnalysis(); app.on('activate', function () { @@ -68,20 +68,21 @@ app.on('window-all-closed', () => { } }); -app.on('before-quit', (event) => { +app.on('before-quit', () => { mainWindow.webContents.send('before-quit-app'); }); -ipcMain.handle('get-product-name', (event) => { +ipcMain.handle('get-product-name', () => { const exePath = app.getPath('exe'); const { name } = path.parse(exePath); return name; }); // 注册退出应用事件 -ipcMain.on('quit-app', (event) => { +ipcMain.on('quit-app', () => { app.quit(); }); - - +ipcMain.on('register-app-menu', (event, orgs) => { + registerAppMenu(mainWindow, orgs); +}); diff --git a/chat2db-client/src/main/menu.js b/chat2db-client/src/main/menu.js index 9896d1ae8..e6ac52975 100644 --- a/chat2db-client/src/main/menu.js +++ b/chat2db-client/src/main/menu.js @@ -1,7 +1,7 @@ const { shell, app, dialog, BrowserWindow, Menu } = require('electron'); const os = require('os'); const path = require('path'); -const registerAppMenu = (mainWindow) => { +const registerAppMenu = (mainWindow, orgs) => { const menuBar = [ { label: 'Chat2DB', @@ -11,7 +11,7 @@ const registerAppMenu = (mainWindow) => { click() { dialog.showMessageBox({ title: '关于Chat2DB', - message: `关于Chat2DB v${app.getVersion()}`, + message: `关于Chat2DB v${orgs?.version || app.getVersion()}`, detail: // An intelligent database client and smart BI reporting tool with integrated AI capabilities. '一个集成AI能力的智能数据库客户端和智能BI报表工具。', diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index 64c344545..094133596 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -43,11 +43,13 @@ contextBridge.exposeInMainWorld('electronApi', { quitApp: () => { ipcRenderer.send('quit-app'); }, - - beforeQuitAPP: (callback)=>{ + beforeQuitApp: (callback)=>{ ipcRenderer.on('before-quit-app', () => { callback(); }); + }, + registerAppMenu: (menuProps)=>{ + ipcRenderer.send('register-app-menu', menuProps); } }); diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx index 8ddaa8dc1..80ae1e602 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/TreeNodeRightClick/index.tsx @@ -155,12 +155,12 @@ function TreeNodeRightClick(props: IProps) { return { text: data.pinned ? i18n('workspace.menu.unPin') : i18n('workspace.menu.pin'), icon: data.pinned ? '\ue61d' : '\ue627', - handle: handelTop, + handle: handleTop, }; }, }; - function handelTop() { + function handleTop() { const api = data.pinned ? 'deleteTablePin' : 'addTablePin'; mysqlServer[api]({ ...curWorkspaceParams, From f9acd4857946ea4225c7d00937c6445f1f7f8f25 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 22 Oct 2023 16:28:53 +0800 Subject: [PATCH 1032/1069] refactor: Add function to compatible database name --- .../src/utils/IntelliSense/table.ts | 2 +- chat2db-client/src/utils/database.ts | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/utils/IntelliSense/table.ts b/chat2db-client/src/utils/IntelliSense/table.ts index e7740bb7f..d0883d499 100644 --- a/chat2db-client/src/utils/IntelliSense/table.ts +++ b/chat2db-client/src/utils/IntelliSense/table.ts @@ -13,7 +13,7 @@ let intelliSenseTable = monaco.languages.registerCompletionItemProvider('sql', { }); /** 根据不同的数据库,插入不同的表名 */ -const handleInsertText = (text, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { +const handleInsertText = (text: string, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { if ( [DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.ORACLE, DatabaseTypeCode.DB2, DatabaseTypeCode.SQLITE].includes( databaseCode, diff --git a/chat2db-client/src/utils/database.ts b/chat2db-client/src/utils/database.ts index 2b10ae669..454efdb5d 100644 --- a/chat2db-client/src/utils/database.ts +++ b/chat2db-client/src/utils/database.ts @@ -1,3 +1,4 @@ +import { DatabaseTypeCode } from '@/constants/common'; import { IWorkspaceModelType } from '@/models/workspace'; import { Option } from '@/typings/common'; @@ -36,3 +37,32 @@ export function handleDatabaseAndSchema(databaseAndSchema: IWorkspaceModelType[' } return newCascaderOptions; } + +/** + * 兼容处理数据库名称 + * @param databaseName + * @param databaseType + * @returns + */ +export function compatibleDataBaseName(databaseName: string, databaseType: DatabaseTypeCode) { + //"" oracele sqlite postgrsql h2 dm + // ` MYSQL clickhouse MariaDB + // [ sqlserver + if ( + [ + DatabaseTypeCode.ORACLE, + DatabaseTypeCode.SQLITE, + DatabaseTypeCode.POSTGRESQL, + DatabaseTypeCode.H2, + DatabaseTypeCode.DB2, + ].includes(databaseType) + ) { + return `"${databaseName}"`; + } else if ([DatabaseTypeCode.SQLSERVER].includes(databaseType)) { + return `[${databaseName}]`; + } else if ([DatabaseTypeCode.MYSQL, DatabaseTypeCode.CLICKHOUSE, DatabaseTypeCode.MARIADB].includes(databaseType)) { + return `\`${databaseName}\``; + } else { + return `${databaseName}`; + } +} From f0e6af42ceb4fe14d77b6e06200672a0ba613103 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sun, 22 Oct 2023 18:45:42 +0800 Subject: [PATCH 1033/1069] feat: change pageNo loading --- .../SearchResult/Pagination/index.tsx | 2 +- .../SearchResult/TableBox/index.less | 19 +++ .../SearchResult/TableBox/index.tsx | 115 ++++++++++++++---- .../src/components/SearchResult/index.less | 6 +- .../src/components/TabsNew/index.tsx | 2 +- chat2db-client/src/styles/antd.less | 1 + 6 files changed, 115 insertions(+), 30 deletions(-) diff --git a/chat2db-client/src/components/SearchResult/Pagination/index.tsx b/chat2db-client/src/components/SearchResult/Pagination/index.tsx index 5948bc44d..5e1489a71 100644 --- a/chat2db-client/src/components/SearchResult/Pagination/index.tsx +++ b/chat2db-client/src/components/SearchResult/Pagination/index.tsx @@ -155,7 +155,7 @@ export default function Pagination(props: IProps) { ]} /> - + diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.less b/chat2db-client/src/components/SearchResult/TableBox/index.less index e0778e36b..8b45c5b46 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.less +++ b/chat2db-client/src/components/SearchResult/TableBox/index.less @@ -154,6 +154,23 @@ flex: 1; overflow-y: auto; height: 0px; + position: relative; + .supportBaseTableSpin { + position: absolute; + top: 0px; + left: 0px; + right: 0px; + bottom: 0px; + display: flex; + justify-content: center; + align-items: center; + z-index: 10; + background-color: var(--color-fill-quaternary); + } +} + +.supportBaseTableBoxHidden { + overflow: hidden; } .table { @@ -272,6 +289,8 @@ overflow: hidden; height: 60vh; margin-top: -15px; + .modifyButton { + } } .errorDetail { diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 17aa52c07..cf0549a7e 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { Dropdown, Input, MenuProps, message, Modal, Space, Popover, Spin } from 'antd'; +import { Dropdown, Input, MenuProps, message, Modal, Space, Popover, Spin, Button } from 'antd'; import { BaseTable, ArtColumn, useTablePipeline, features, SortItem } from 'ali-react-table'; import styled from 'styled-components'; import classnames from 'classnames'; @@ -31,6 +31,8 @@ interface ITableProps { interface IViewTableCellData { name: string; value: any; + colIndex: number; + rowNo: string; } interface IUpdateData { @@ -82,7 +84,7 @@ export default function TableBox(props: ITableProps) { // DataList不带列标识的表数据 // 保存原始的表数据,用于对比新老数据看是否有变化 const [oldDataList, setOldDataList] = useState([]); - // 正在标记的单元格的坐标 + // 正在编辑的单元格的坐标 const [editingCell, setEditingCell] = useState<[string, string] | null>(null); // input受控的正在编辑的数据 const [editingData, setEditingData] = useState(''); @@ -102,6 +104,10 @@ export default function TableBox(props: ITableProps) { const [allDataReady, setAllDataReady] = useState(false); // 编辑数据的inputRef const editDataInputRef = React.useRef(null); + // monacoEditorRef + const monacoEditorRef = React.useRef(null); + // 表格loading + const [tableLoading, setTableLoading] = useState(false); const handleExportSQLResult = async (exportType: ExportTypeEnum, exportSize: ExportSizeEnum) => { const params: IExportParams = { @@ -210,6 +216,42 @@ export default function TableBox(props: ITableProps) { messageApi.success(i18n('common.button.copySuccessfully')); } + function monacoEditorEditData() { + const editorData = monacoEditorRef?.current?.getAllContent(); + // 获取原始的该单元格的数据 + // let _oldData = ''; + const { rowNo, colIndex } = viewTableCellData as any; + oldDataList.forEach((item) => { + if (item[0] === rowNo) { + if (item[colIndex] !== editorData) { + const newTableData = lodash.cloneDeep(tableData); + let newRowDataList: any = []; + newTableData.forEach((i) => { + if (i[`${preCode}0No.`] === rowNo) { + i[`${preCode}${colIndex}${columns[colIndex].name}`] = editorData; + newRowDataList = Object.keys(i).map((_i) => i[_i]); + } + }); + setTableData(newTableData); + console.log(newTableData); + + // 添加更新记录 + setUpdateData([ + ...updateData, + { + type: CRUD.UPDATE, + oldDataList: item, + dataList: newRowDataList, + rowNo, + }, + ]); + } + return; + } + }); + setViewTableCellData(null); + } + function handleCancel() { setViewTableCellData(null); } @@ -230,9 +272,9 @@ export default function TableBox(props: ITableProps) { setEditingCell(null); setEditingData(''); const [colIndex, rowNo] = editingCell!; - const newTableData = lodash.cloneDeep(tableData); let oldRowDataList: string[] = []; let newRowDataList: string[] = []; + const newTableData = lodash.cloneDeep(tableData); newTableData.forEach((item) => { if (item[`${preCode}0No.`] === rowNo) { item[`${preCode}${colIndex}${columns[colIndex].name}`] = editingData; @@ -393,8 +435,14 @@ export default function TableBox(props: ITableProps) { <>
    {renderTableCellValue(value)}
    - - + +
    )} @@ -447,7 +495,7 @@ export default function TableBox(props: ITableProps) { const onClickTotalBtn = async () => { const res = await sqlService.getDMLCount({ - sql: queryResultData.sql, + sql: queryResultData.originalSql, ...(props.executeSqlParams || {}), }); setPaginationConfig({ ...paginationConfig, total: res }); @@ -608,8 +656,9 @@ export default function TableBox(props: ITableProps) { // 获取表格数据 接受一个参数params 包含IExecuteSqlParams中的一个或多个 const getTableData = (params?: Partial) => { + setTableLoading(true); const executeSQLParams: IExecuteSqlParams = { - sql: queryResultData.sql, + sql: queryResultData.originalSql, dataSourceId: props.executeSqlParams?.dataSourceId, databaseName: props.executeSqlParams?.databaseName, schemaName: props.executeSqlParams?.schemaName, @@ -619,6 +668,7 @@ export default function TableBox(props: ITableProps) { }; return sqlService.executeSql(executeSQLParams).then((res) => { + setTableLoading(false); setQueryResultData(res?.[0]); setUpdateData([]); }); @@ -708,7 +758,7 @@ export default function TableBox(props: ITableProps) {
    {queryResultData.canEdit && (
    - +
    - +
    - +
    - +
    - +
    {allDataReady && ( -
    +
    + {tableLoading && }

    {i18n('common.text.noData')}

    }} @@ -806,21 +861,27 @@ export default function TableBox(props: ITableProps) { onCancel={handleCancel} width="60vw" maskClosable={false} - footer={false} + footer={[ + , + ]} > -
    - -
    + <> +
    + +
    + ((props) => { } return ( - +
    { onDoubleClick(t); diff --git a/chat2db-client/src/styles/antd.less b/chat2db-client/src/styles/antd.less index 6f00b708d..018178ca1 100644 --- a/chat2db-client/src/styles/antd.less +++ b/chat2db-client/src/styles/antd.less @@ -32,6 +32,7 @@ .ant-modal-footer { border-top: 0px; padding: 8px; + padding-top: 0px; } // .ant-notification-notice { From c7cbe9e3237c559fca613bb622157a07255a2f95 Mon Sep 17 00:00:00 2001 From: robin <850379744@qq.com> Date: Sun, 22 Oct 2023 20:43:13 +0800 Subject: [PATCH 1034/1069] vector update --- .../web/api/controller/ai/ChatController.java | 40 ++++- .../controller/ai/EmbeddingController.java | 143 ++++++++++++++++++ .../ai/TextGenerationController.java | 2 +- .../api/controller/rdb/RdbDdlController.java | 2 +- .../api/controller/rdb/TableController.java | 2 +- .../rdb/converter/RdbWebConverter.java | 5 + .../web/api/http/GatewayClientService.java | 19 +++ .../web/api/http/model/EsTableSchema.java | 25 +++ .../http/request/EsTableSchemaRequest.java | 29 ++++ .../http/response/EsTableSchemaResponse.java | 18 +++ 10 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/EsTableSchema.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/EsTableSchemaRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/EsTableSchemaResponse.java diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java index ae3fecc38..bafd6e701 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java @@ -45,8 +45,11 @@ import ai.chat2db.server.web.api.controller.ai.request.ChatRequest; import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient; import ai.chat2db.server.web.api.http.GatewayClientService; +import ai.chat2db.server.web.api.http.model.EsTableSchema; import ai.chat2db.server.web.api.http.model.TableSchema; +import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; +import ai.chat2db.server.web.api.http.response.EsTableSchemaResponse; import ai.chat2db.server.web.api.http.response.TableSchemaResponse; import ai.chat2db.server.web.api.util.ApplicationContextUtil; import ai.chat2db.server.web.api.controller.ai.openai.client.OpenAIClient; @@ -498,7 +501,7 @@ private String buildPrompt(ChatQueryRequest queryRequest) { TableQueryParam queryParam = chatConverter.chat2tableQuery(queryRequest); properties = buildTableColumn(queryParam, queryRequest.getTableNames()); } else { - properties = queryDatabaseSchema(queryRequest); + properties = querySchemaByEs(queryRequest); } String prompt = queryRequest.getMessage(); String promptType = StringUtils.isBlank(queryRequest.getPromptType()) ? PromptType.NL_2_SQL.getCode() @@ -578,6 +581,41 @@ public String queryDatabaseSchema(ChatQueryRequest queryRequest) { } } + /** + * query database schema + * + * @param queryRequest + * @return + * @throws IOException + */ + public String querySchemaByEs(ChatQueryRequest queryRequest) { + // search embedding + EsTableSchemaRequest tableSchemaRequest = new EsTableSchemaRequest(); + tableSchemaRequest.setSearchKey(queryRequest.getMessage()); + tableSchemaRequest.setDataSourceId(queryRequest.getDataSourceId()); + tableSchemaRequest.setDatabaseName(queryRequest.getDatabaseName()); + tableSchemaRequest.setSchemaName(queryRequest.getSchemaName()); + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); + if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { + return ""; + } + tableSchemaRequest.setApiKey(keyConfig.getContent()); + try { + DataResult result = gatewayClientService.schemaEsSearch(tableSchemaRequest); + List schemas = Lists.newArrayList(); + if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) { + for(EsTableSchema data: result.getData().getTableSchemas()){ + schemas.add(data.getTableSchemaContent()); + } + } + return JSON.toJSONString(schemas); + } catch (Exception exception) { + log.error("query es table error, do nothing"); + return ""; + } + } + /** * distribute embedding with different AI * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java index a13489ad1..da5c99c55 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/EmbeddingController.java @@ -21,6 +21,7 @@ import ai.chat2db.server.web.api.controller.rdb.request.TableBriefQueryRequest; import ai.chat2db.server.web.api.controller.rdb.request.TableMilvusQueryRequest; import ai.chat2db.server.web.api.http.GatewayClientService; +import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; import ai.chat2db.server.web.api.http.request.WhiteListRequest; import ai.chat2db.server.web.api.util.ApplicationContextUtil; @@ -147,6 +148,81 @@ public ActionResult embeddings(@Valid TableMilvusQueryRequest request) return ActionResult.isSuccess(); } + /** + * save datasource schema + * + * @param request + * @return + * @throws IOException + */ + @PostMapping("/datasource/es") + @CrossOrigin + public ActionResult es(@Valid EsTableSchemaRequest request) + throws Exception { + + // query tables + TablePageQueryParam queryParam = rdbWebConverter.schemaReq2page(request); + TableSelector tableSelector = new TableSelector(); + tableSelector.setColumnList(false); + tableSelector.setIndexList(false); + queryParam.setPageNo(1); + queryParam.setPageSize(1000); + PageResult
    tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); + + List
    tables = tableDTOPageResult.getData(); + if (CollectionUtils.isEmpty(tables)) { + return ActionResult.isSuccess(); + } + String tableName = tables.get(0).getName(); + String tableSchema = queryTableDdlByEs(tableName, request); + request.setTableName(tableName); + request.setTableSchemaContent(tableSchema); + + if (StringUtils.isBlank(tableSchema)) { + throw new ParamBusinessException("tableSchema is empty"); + } + + // save first table embedding + request.setDeleteBeforeInsert(true); + saveTableEs(request); + + // save other table embedding + request.setDeleteBeforeInsert(false); + for (int i = 1; i < tables.size(); i++) { + tableName = tables.get(i).getName(); + tableSchema = queryTableDdlByEs(tableName, request); + if (StringUtils.isBlank(tableSchema)) { + continue; + } + request.setTableName(tableName); + request.setTableSchemaContent(tableSchema); + saveTableEs(request); + } + + // query all the tables + Long totalTableCount = tableDTOPageResult.getTotal(); + Integer pageSize = queryParam.getPageSize(); + if (pageSize < totalTableCount) { + for (int i = 2; i < totalTableCount/pageSize + 1; i++) { + queryParam.setPageNo(i); + tableDTOPageResult = tableService.pageQuery(queryParam, tableSelector); + tables = tableDTOPageResult.getData(); + for (Table table : tables) { + tableName = table.getName(); + tableSchema = queryTableDdlByEs(tableName, request); + if (StringUtils.isBlank(tableSchema)) { + continue; + } + request.setTableName(tableName); + request.setTableSchemaContent(tableSchema); + saveTableEs(request); + } + } + } + + return ActionResult.isSuccess(); + } + /** * sync table vector * @@ -223,6 +299,56 @@ private void saveTableEmbedding(String tableSchema, TableSchemaRequest tableSche gatewayClientService.schemaVectorSave(tableSchemaRequest); } + /** + * sync table vector + * + * @param param + */ + public void syncTableEs(TableBriefQueryRequest param) throws Exception { + EsTableSchemaRequest esParam = rdbWebConverter.req2req(param); + if (Objects.isNull(esParam.getDataSourceId())) { + return; + } + if (StringUtils.isBlank(esParam.getDatabaseName()) && StringUtils.isBlank(esParam.getSchemaName())) { + return; + } + + ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class); + Config config = configService.find(RestAIClient.AI_SQL_SOURCE).getData(); + String aiSqlSource = AiSqlSourceEnum.CHAT2DBAI.getCode(); + // only sync for chat2db ai + if (Objects.isNull(config) || !aiSqlSource.equals(config.getContent())) { + return; + } + Config keyConfig = configService.find(Chat2dbAIClient.CHAT2DB_OPENAI_KEY).getData(); + if (Objects.isNull(keyConfig) || StringUtils.isBlank(keyConfig.getContent())) { + return; + } + + String apiKey = keyConfig.getContent(); + TableVectorParam vectorParam = rdbWebConverter.param2param(param); + vectorParam.setApiKey(apiKey); + DataResult result = tableService.checkTableVector(vectorParam); + if (result.getData()) { + return; + } + + esParam.setApiKey(apiKey); + es(esParam); + tableService.saveTableVector(vectorParam); + } + + /** + * save table schema + * + * @param tableSchemaRequest + * @throws Exception + */ + private void saveTableEs(EsTableSchemaRequest tableSchemaRequest) throws Exception{ + // save table es + gatewayClientService.schemaEsSave(tableSchemaRequest); + } + /** * query table schema * @@ -240,4 +366,21 @@ private String queryTableDdl(String tableName, TableBriefQueryRequest request) { return tableSchema.getData(); } + /** + * query table schema + * + * @param tableName + * @param request + * @return + */ + private String queryTableDdlByEs(String tableName, EsTableSchemaRequest request) { + ShowCreateTableParam param = new ShowCreateTableParam(); + param.setTableName(tableName); + param.setDataSourceId(request.getDataSourceId()); + param.setDatabaseName(request.getDatabaseName()); + param.setSchemaName(request.getSchemaName()); + DataResult tableSchema = tableService.showCreateTable(param); + return tableSchema.getData(); + } + } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java index 0c6180667..6ba6ed99d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/TextGenerationController.java @@ -64,7 +64,7 @@ public SseEmitter prompt(ChatQueryRequest queryRequest, @RequestHeader Map list(@Valid TableBriefQueryRequest request) { singleThreadExecutor.submit(() -> { try { Chat2DBContext.putContext(connectInfo); - syncTableVector(request); + syncTableEs(request); } catch (Exception e) { log.error("sync table vector error", e); } finally { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java index 80ae1a022..f741f4edc 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/TableController.java @@ -68,7 +68,7 @@ public WebPageResult list(@Valid TableBriefQueryRequest request) { singleThreadExecutor.submit(() -> { try { Chat2DBContext.putContext(connectInfo); - syncTableVector(request); + syncTableEs(request); } catch (Exception e) { log.error("sync table vector error", e); } finally { diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java index 078fd8287..861f936fe 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/RdbWebConverter.java @@ -12,6 +12,7 @@ import ai.chat2db.server.web.api.controller.rdb.vo.SchemaVO; import ai.chat2db.server.web.api.controller.rdb.vo.SqlVO; import ai.chat2db.server.web.api.controller.rdb.vo.TableVO; +import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.ExecuteResult; import ai.chat2db.spi.model.MetaSchema; @@ -232,4 +233,8 @@ public abstract class RdbWebConverter { @Mapping(source = "schemaName", target = "schema"), }) public abstract TableVectorParam param2param(TableBriefQueryRequest request); + + public abstract EsTableSchemaRequest req2req(TableBriefQueryRequest request); + + public abstract TablePageQueryParam schemaReq2page(EsTableSchemaRequest request); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java index 8225840b7..d67ee4ef3 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/GatewayClientService.java @@ -2,6 +2,7 @@ import ai.chat2db.server.tools.base.wrapper.result.ActionResult; import ai.chat2db.server.tools.base.wrapper.result.DataResult; +import ai.chat2db.server.web.api.http.request.EsTableSchemaRequest; import ai.chat2db.server.web.api.http.request.KnowledgeRequest; import ai.chat2db.server.web.api.http.request.TableSchemaRequest; import ai.chat2db.server.web.api.http.request.WhiteListRequest; @@ -73,6 +74,15 @@ public interface GatewayClientService { @Post(url = "/api/client/milvus/schema/save", contentType = "application/json") ActionResult schemaVectorSave(@Body TableSchemaRequest request); + /** + * save table schema vector + * + * @param request + * @return + */ + @Post(url = "/api/client/es/schema/save", contentType = "application/json") + ActionResult schemaEsSave(@Body EsTableSchemaRequest request); + /** * save knowledge vector * @@ -91,6 +101,15 @@ public interface GatewayClientService { @Post(url = "/api/client/milvus/schema/search", contentType = "application/json") DataResult schemaVectorSearch(@Body TableSchemaRequest request); + /** + * save table schema vector + * + * @param request + * @return + */ + @Post(url = "/api/client/es/schema/search", contentType = "application/json") + DataResult schemaEsSearch(@Body EsTableSchemaRequest request); + /** * check in white list * diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/EsTableSchema.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/EsTableSchema.java new file mode 100644 index 000000000..a7e830d32 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/model/EsTableSchema.java @@ -0,0 +1,25 @@ +package ai.chat2db.server.web.api.http.model; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class EsTableSchema { + + private String dataSourceId; + + private String databaseName; + + private String apiKey; + + private String schemaName; + + private String tableName; + + private String tableSchemaContent; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/EsTableSchemaRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/EsTableSchemaRequest.java new file mode 100644 index 000000000..8ae50eeed --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/request/EsTableSchemaRequest.java @@ -0,0 +1,29 @@ +package ai.chat2db.server.web.api.http.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class EsTableSchemaRequest { + + private Long dataSourceId; + + private String databaseName; + + private String apiKey; + + private String schemaName; + + private String tableName; + + private String tableSchemaContent; + + private String searchKey; + + private Boolean deleteBeforeInsert; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/EsTableSchemaResponse.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/EsTableSchemaResponse.java new file mode 100644 index 000000000..3e78f765b --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/http/response/EsTableSchemaResponse.java @@ -0,0 +1,18 @@ +package ai.chat2db.server.web.api.http.response; + +import ai.chat2db.server.web.api.http.model.EsTableSchema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.util.List; + +@Data +@SuperBuilder +@NoArgsConstructor +@AllArgsConstructor +public class EsTableSchemaResponse { + + private List tableSchemas; +} From f8639a2205ee185b415eb85550c2b4ab348e9880 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Sun, 22 Oct 2023 21:25:24 +0800 Subject: [PATCH 1035/1069] feat: Add cascader component --- .../src/components/CascaderDB/index.less | 43 ++++ .../src/components/CascaderDB/index.tsx | 220 ++++++++++++++++++ .../src/components/Iconfont/index.tsx | 6 +- 3 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 chat2db-client/src/components/CascaderDB/index.less create mode 100644 chat2db-client/src/components/CascaderDB/index.tsx diff --git a/chat2db-client/src/components/CascaderDB/index.less b/chat2db-client/src/components/CascaderDB/index.less new file mode 100644 index 000000000..1652d04ac --- /dev/null +++ b/chat2db-client/src/components/CascaderDB/index.less @@ -0,0 +1,43 @@ +.cascaderDB { + display: inline-flex; + align-items: center; + border-radius: 16px; + padding: 4px 8px 4px 0; + background-color: var(--color-bg-subtle); + + &:hover { + background-color: var(--color-hover-bg); + } + +} + +.optionItem { + display: flex; + align-items: center; + height: 26px; + cursor: pointer; + padding: 0px 6px; + + + .optionItemIcon { + margin-right: 10px; + font-weight: 400; + color: var(--color-primary); + } + + .optionItemText { + font-weight: bold; + } + + +} + +.select { + :global { + .ant-select-selector { + caret-color: var(--color-text-placeholder); + // caret-width: 4px; + // width: 4px; + } + } +} \ No newline at end of file diff --git a/chat2db-client/src/components/CascaderDB/index.tsx b/chat2db-client/src/components/CascaderDB/index.tsx new file mode 100644 index 000000000..681e8b850 --- /dev/null +++ b/chat2db-client/src/components/CascaderDB/index.tsx @@ -0,0 +1,220 @@ +import React, { useEffect, useState } from 'react'; +import { Divider, Select, Typography } from 'antd'; +import connection from '@/service/connection'; +import cs from 'classnames'; +import styles from './index.less'; +import Iconfont from '../Iconfont'; +import { databaseMap } from '@/constants/database'; + +interface IProps { + className?: string; + curConnectionId?: number; + onChange?: (value: { dataSourceId: number; databaseName: string; schemaName: string }) => void; +} + +interface IOption { + label: string | React.ReactNode; + value: number | string; +} + +function CascaderDB(props: IProps) { + const [dataSourceOptions, setDataSourceOptions] = useState([]); + const [curDataSourceId, setCurDataSourceId] = useState(props.curConnectionId); + + const [databaseOptions, setDatabaseOptions] = useState([]); + const [curDatabaseName, setCurDatabaseName] = useState(''); + + const [schemaOptions, setSchemaOptions] = useState([]); + const [curSchemeName, setCurSchemeName] = useState(''); + + useEffect(() => { + loadDataSource(); + }, []); + useEffect(() => { + loadDatabase(); + }, [curDataSourceId]); + useEffect(() => { + loadSchema(); + }, [curDatabaseName]); + + const handleChangeDataSource = (value) => { + setCurDataSourceId(value); + setDatabaseOptions([]); + setSchemaOptions([]); + setCurDatabaseName(''); + setCurSchemeName(''); + + props.onChange && + props.onChange({ + dataSourceId: value, + databaseName: '', + schemaName: '', + }); + }; + const handleChangeDatabase = (value) => { + setCurDatabaseName(value); + setSchemaOptions([]); + setCurSchemeName(''); + + props.onChange && + props.onChange({ + dataSourceId: curDataSourceId!, + databaseName: value, + schemaName: '', + }); + }; + + const handleChangeSchema = (value) => { + setCurSchemeName(value); + + props.onChange && + props.onChange({ dataSourceId: curDataSourceId!, databaseName: curDatabaseName, schemaName: value }); + }; + + /** 加载DataSource数据 */ + const loadDataSource = async () => { + // 请求 dataSource 数据 + const dataSourceList = await connection.getList({ + pageNo: 1, + pageSize: 999, + refresh: true, + }); + const formattedData = (dataSourceList?.data || []).map((item) => ({ + ...item, + key: `dataSource-${item.id}`, + value: item.id, + label: ( +
    + +
    {item.alias}
    +
    + ), + })); + setDataSourceOptions(formattedData); + + if (curDataSourceId === undefined) { + setCurDataSourceId(formattedData[0]?.value); + } + }; + + /** 加载Database数据 */ + const loadDatabase = async () => { + if (curDataSourceId === undefined) { + return; + } + const databaseList = await connection.getDBList({ + dataSourceId: curDataSourceId, + }); + + const formattedData = (databaseList || []).map((item) => ({ + ...item, + key: `database-${item.name}`, + value: item.name, + label: ( +
    + +
    {item.name}
    +
    + ), + })); + + setDatabaseOptions(formattedData); + if (!curDatabaseName) { + setCurDatabaseName(formattedData[0]?.value); + } + }; + + const loadSchema = async () => { + if (curDataSourceId === undefined || !curDatabaseName) { + return; + } + const schemaList = await connection.getSchemaList({ + dataSourceId: curDataSourceId, + databaseName: curDatabaseName, + refresh: false, + }); + + const formattedData = (schemaList || []).map((item) => ({ + ...item, + key: `schema-${item.name}`, + value: item.name, + label: ( +
    + +
    {item.name}
    +
    + ), + })); + + setSchemaOptions(formattedData); + if (!curSchemeName) { + setCurSchemeName(formattedData[0]?.value); + } + }; + + return ( +
    + ( + <> +
    + 数据库 + +
    + {menu} + + )} + /> + {!!schemaOptions.length && ( +
    pageQuery(TablePageQueryParam param, TableSelector sele } total = versionDO.getTableCount(); } - LambdaQueryWrapper query = new LambdaQueryWrapper<>(); - query.eq(TableCacheDO::getVersion, version); - query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); - if (StringUtils.isNotBlank(param.getDatabaseName())) { - query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); - } - if (StringUtils.isNotBlank(param.getSchemaName())) { - query.eq(TableCacheDO::getSchemaName, param.getSchemaName()); - } - if (StringUtils.isNotBlank(param.getSearchKey())) { - query.like(TableCacheDO::getTableName, param.getSearchKey()); - } +// LambdaQueryWrapper query = new LambdaQueryWrapper<>(); +// query.eq(TableCacheDO::getVersion, version); +// query.eq(TableCacheDO::getDataSourceId, param.getDataSourceId()); +// if (StringUtils.isNotBlank(param.getDatabaseName())) { +// query.eq(TableCacheDO::getDatabaseName, param.getDatabaseName()); +// } +// if (StringUtils.isNotBlank(param.getSchemaName())) { +// query.eq(TableCacheDO::getSchemaName, param.getSchemaName()); +// } +// if (StringUtils.isNotBlank(param.getSearchKey())) { +// query.like(TableCacheDO::getTableName, param.getSearchKey()); +// } Page page = new Page<>(param.getPageNo(), param.getPageSize()); // page.setSearchCount(param.getEnableReturnCount()); - IPage iPage = tableCacheMapper.selectPage(page, query); + IPage iPage = tableCacheMapper.pageQuery(page, param.getDataSourceId(),param.getDatabaseName(),param.getSchemaName(),param.getSearchKey()); List
    tables = new ArrayList<>(); if (CollectionUtils.isNotEmpty(iPage.getRecords())) { for (TableCacheDO tableCacheDO : iPage.getRecords()) { From f9ae196ce9fb90c7a5f0658e121bad76b4e672af Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Sun, 22 Oct 2023 22:46:26 +0800 Subject: [PATCH 1040/1069] Supports fuzzy query of table names, not case sensitive. Automatically add pagination parameters when users query SQL without pagination parameters. Sort the database schema and prioritize the user-defined ones. --- .../ai/chat2db/plugin/db2/DB2MetaData.java | 20 ++++- .../plugin/db2/builder/DB2SqlBuilder.java | 23 ++++++ .../java/ai/chat2db/plugin/dm/DMMetaData.java | 15 +++- .../java/ai/chat2db/plugin/h2/H2Meta.java | 25 +++--- .../chat2db/plugin/mysql/MysqlMetaData.java | 13 ++++ .../plugin/mysql/builder/MysqlSqlBuilder.java | 18 +++++ .../chat2db/plugin/oracle/OracleMetaData.java | 10 +++ .../oracle/builder/OracleSqlBuilder.java | 40 ++++++++-- .../plugin/postgresql/PostgreSQLMetaData.java | 47 ++++++----- .../builder/PostgreSQLSqlBuilder.java | 15 ++++ .../plugin/sqlite/builder/SqliteBuilder.java | 13 ++-- .../plugin/sqlserver/SqlServerMetaData.java | 24 +++++- .../builder/SqlServerSqlBuilder.java | 27 ++++++- .../domain/core/impl/DatabaseServiceImpl.java | 28 +------ .../core/impl/DlTemplateServiceImpl.java | 22 ++++-- .../repository/mapper/TableCacheMapper.java | 5 ++ .../resources/mapper/TableCacheMapper.xml | 19 +++++ chat2db-server/chat2db-server-test/pom.xml | 11 +++ .../controller/system/AutomaticUpgrade.java | 63 ++++++++++----- .../main/java/ai/chat2db/spi/MetaData.java | 1 - .../main/java/ai/chat2db/spi/SqlBuilder.java | 12 +++ .../chat2db/spi/jdbc/DefaultSqlBuilder.java | 5 ++ .../ai/chat2db/spi/sql/Chat2DBContext.java | 26 ++++++- .../java/ai/chat2db/spi/sql/ConnectInfo.java | 13 +++- .../java/ai/chat2db/spi/sql/SQLExecutor.java | 42 ++++++++-- .../ai/chat2db/spi/util/JdbcJarUtils.java | 3 - .../java/ai/chat2db/spi/util/SortUtils.java | 77 +++++++++++++++++++ .../java/ai/chat2db/spi/util/SqlUtils.java | 49 ++++++++++-- 28 files changed, 538 insertions(+), 128 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java create mode 100644 chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SortUtils.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 07a24e999..a78a0d1f8 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 @@ -2,12 +2,26 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.Arrays; +import java.util.List; +import ai.chat2db.plugin.db2.builder.DB2SqlBuilder; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.SortUtils; public class DB2MetaData extends DefaultMetaService implements MetaData { + + private List systemSchemas = Arrays.asList("NULLID","SQLJ","SYSCAT","SYSFUN","SYSIBM","SYSIBMADM","SYSIBMINTERNAL","SYSIBMTS","SYSPROC","SYSPUBLIC","SYSSTAT","SYSTOOLS"); + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); + return SortUtils.sortSchema(schemas, systemSchemas); + } private String functionSQL = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " @@ -40,7 +54,6 @@ public class DB2MetaData extends DefaultMetaService implements MetaData { @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { - System.out.println(functionSQL); SQLExecutor.getInstance().executeSql(connection, functionSQL.replace("tableSchema", schemaName), resultSet -> null); } catch (Exception e) { //log.error("创建函数失败", e); @@ -59,6 +72,9 @@ public String tableDDL(Connection connection, String databaseName, String schema return null; }); } - + @Override + public SqlBuilder getSqlBuilder() { + return new DB2SqlBuilder(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java new file mode 100644 index 000000000..6a67220ea --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java @@ -0,0 +1,23 @@ +package ai.chat2db.plugin.db2.builder; + +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; + +public class DB2SqlBuilder extends DefaultSqlBuilder { + + + + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + int startRow = offset + 1; + int endRow = offset+ pageSize; + StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120); + sqlBuilder.append("SELECT * FROM (SELECT TMP_PAGE.*,ROWNUMBER() OVER() AS PAGEHELPER_ROW_ID FROM ( \n"); + sqlBuilder.append(sql); + sqlBuilder.append("\n ) AS TMP_PAGE) TMP_PAGE WHERE PAGEHELPER_ROW_ID BETWEEN "); + sqlBuilder.append(startRow); + sqlBuilder.append(" AND "); + sqlBuilder.append(endRow); + return sqlBuilder.toString(); + } +} 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 f6555f64d..6ac30a9a0 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 @@ -3,19 +3,26 @@ import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.SortUtils; import ai.chat2db.spi.util.SqlUtils; import jakarta.validation.constraints.NotEmpty; public class DMMetaData extends DefaultMetaService implements MetaData { + + private List systemSchemas = Arrays.asList("CTISYS", "SYS","SYSDBA","SYSSSO","SYSAUDITOR"); + + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); + return SortUtils.sortSchema(schemas, systemSchemas); + } public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String selectObjectDDLSQL = String.format( "select dbms_metadata.get_ddl(%s, %s, %s) AS \"sql\" from dual", diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java index e063d7920..181987b0e 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java @@ -3,21 +3,24 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import ai.chat2db.spi.MetaData; import ai.chat2db.spi.jdbc.DefaultMetaService; -import ai.chat2db.spi.model.Function; -import ai.chat2db.spi.model.Procedure; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.Trigger; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.SortUtils; import jakarta.validation.constraints.NotEmpty; public class H2Meta extends DefaultMetaService implements MetaData { + + + private List systemSchemas = Arrays.asList("INFORMATION_SCHEMA"); + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); + return SortUtils.sortSchema(schemas, systemSchemas); + } @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { @@ -69,19 +72,13 @@ private String getDDL(Connection connection, String databaseName, String schemaN createTableDDL.append(tableName).append(" (\n"); createTableDDL.append(String.join(",\n", columnDefinitions)); createTableDDL.append("\n);\n"); - - System.out.println("DDL建表语句:"); - System.out.println(createTableDDL.toString()); - // 输出索引信息 - System.out.println("\nDDL索引语句:"); for (Map.Entry> entry : indexMap.entrySet()) { String indexName = entry.getKey(); List columnList = entry.getValue(); String indexColumns = String.join(", ", columnList); String createIndexDDL = String.format("CREATE INDEX %s ON %s (%s);", indexName, tableName, indexColumns); - System.out.println(createIndexDDL); createTableDDL.append(createIndexDDL); } return createTableDDL.toString(); 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 38d8b034e..27c03f31b 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 @@ -5,6 +5,7 @@ import java.sql.SQLException; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; import ai.chat2db.plugin.mysql.builder.MysqlSqlBuilder; import ai.chat2db.plugin.mysql.type.MysqlCharsetEnum; @@ -17,9 +18,21 @@ import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import jakarta.validation.constraints.NotEmpty; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import static ai.chat2db.spi.util.SortUtils.sortDatabase; + public class MysqlMetaData extends DefaultMetaService implements MetaData { + + private List systemDatabases = Arrays.asList("information_schema", "performance_schema", "mysql", "sys"); + @Override + public List databases(Connection connection) { + List databases = SQLExecutor.getInstance().databases(connection); + return sortDatabase(databases,systemDatabases,connection); + } + + @Override public String tableDDL(Connection connection, @NotEmpty String databaseName, String schemaName, @NotEmpty String tableName) { 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 aa404f466..c8594bc74 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 @@ -104,4 +104,22 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { } + + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); + sqlBuilder.append(sql); + if (offset == 0) { + sqlBuilder.append("\n LIMIT "); + sqlBuilder.append(pageSize); + } else { + sqlBuilder.append("\n LIMIT "); + sqlBuilder.append(offset); + sqlBuilder.append(","); + sqlBuilder.append(pageSize); + } + return sqlBuilder.toString(); + } + + } 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 acf8552be..e2cca8ff1 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 @@ -14,6 +14,7 @@ import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.SortUtils; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; @@ -22,6 +23,15 @@ public class OracleMetaData extends DefaultMetaService implements MetaData { private static final String TABLE_DDL_SQL = "select dbms_metadata.get_ddl('TABLE','%s','%s') as sql from dual"; + private List systemSchemas = Arrays.asList("ANONYMOUS","APEX_030200","APEX_PUBLIC_USER","APPQOSSYS","BI","CTXSYS","DBSNMP","DIP","EXFSYS","FLOWS_FILES","HR","IX","MDDATA","MDSYS","MGMT_VIEW","OE","OLAPSYS","ORACLE_OCM","ORDDATA","ORDPLUGINS","ORDSYS","OUTLN","OWBSYS","OWBSYS_AUDIT","PM","SCOTT","SH","SI_INFORMTN_SCHEMA","SPATIAL_CSW_ADMIN_USR","SPATIAL_WFS_ADMIN_USR","SYS","SYSMAN","SYSTEM","WMSYS","XDB","XS$NULL"); + + + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); + return SortUtils.sortSchema(schemas, systemSchemas); + } + @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { String sql = String.format(TABLE_DDL_SQL, tableName, schemaName); diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java index 85096d600..c1dbbda75 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -6,6 +6,7 @@ import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; public class OracleSqlBuilder implements SqlBuilder { @@ -16,7 +17,7 @@ public String buildCreateTableSql(Table table) { script.append("CREATE TABLE ").append("\"").append(table.getSchemaName()).append("\".\"").append(table.getName()).append("\" (").append("\n"); for (TableColumn column : table.getColumnList()) { - if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType())){ + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(column.getColumnType()); @@ -27,7 +28,7 @@ public String buildCreateTableSql(Table table) { script.append("\n);"); for (TableIndex tableIndex : table.getIndexList()) { - if(StringUtils.isBlank(tableIndex.getName())|| StringUtils.isBlank(tableIndex.getType())){ + if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } OracleIndexTypeEnum oracleColumnTypeEnum = OracleIndexTypeEnum.getByType(tableIndex.getType()); @@ -35,7 +36,7 @@ public String buildCreateTableSql(Table table) { } for (TableColumn column : table.getColumnList()) { - if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())){ + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType()) || StringUtils.isBlank(column.getComment())) { continue; } script.append("\n").append(buildComment(column)).append(";"); @@ -66,7 +67,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { StringBuilder script = new StringBuilder(); if (!StringUtils.equalsIgnoreCase(oldTable.getName(), newTable.getName())) { - script.append("ALTER TABLE "). append("\"").append(oldTable.getSchemaName()).append("\".\"").append(oldTable.getName()).append("\""); + script.append("ALTER TABLE ").append("\"").append(oldTable.getSchemaName()).append("\".\"").append(oldTable.getName()).append("\""); script.append(" ").append("RENAME TO ").append("\"").append(newTable.getName()).append("\"").append(";\n"); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { @@ -79,7 +80,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { if (StringUtils.isNotBlank(tableColumn.getEditStatus())) { OracleColumnTypeEnum typeEnum = OracleColumnTypeEnum.getByType(tableColumn.getColumnType()); script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); - if(StringUtils.isNotBlank(tableColumn.getComment())){ + if (StringUtils.isNotBlank(tableColumn.getComment())) { script.append("\n").append(buildComment(tableColumn)).append(";\n"); } } @@ -92,11 +93,38 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { script.append("\t").append(mysqlIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } } - if(script.length()>2) { + if (script.length() > 2) { script = new StringBuilder(script.substring(0, script.length() - 2)); script.append(";"); } return script.toString(); } + + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + int startRow = offset; + int endRow = offset + pageSize; + StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120); + if (startRow > 0) { + sqlBuilder.append("SELECT * FROM ( "); + } + if (endRow > 0) { + sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM CAHT2DB_AUTO_ROW_ID FROM ( "); + } + sqlBuilder.append("\n"); + sqlBuilder.append(sql); + sqlBuilder.append("\n"); + if (endRow > 0) { + sqlBuilder.append(" ) TMP_PAGE WHERE ROWNUM <= "); + sqlBuilder.append(endRow); + } + if (startRow > 0) { + sqlBuilder.append(" ) WHERE CAHT2DB_AUTO_ROW_ID > "); + sqlBuilder.append(startRow); + } + return sqlBuilder.toString(); + + + } } 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 f9f9a14fb..78e986560 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 @@ -17,22 +17,23 @@ import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; -import com.alibaba.druid.sql.visitor.functions.If; -import com.alibaba.fastjson2.JSON; +import ai.chat2db.spi.util.SortUtils; import com.google.common.collect.Lists; import jakarta.validation.constraints.NotEmpty; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import static ai.chat2db.plugin.postgresql.consts.SQLConst.FUNCTION_SQL; +import static ai.chat2db.spi.util.SortUtils.sortDatabase; public class PostgreSQLMetaData extends DefaultMetaService implements MetaData { private static final String SELECT_KEY_INDEX = "SELECT ccu.table_schema AS Foreign_schema_name, ccu.table_name AS Foreign_table_name, ccu.column_name AS Foreign_column_name, constraint_type AS Constraint_type, tc.CONSTRAINT_NAME AS Key_name, tc.TABLE_NAME, kcu.Column_name, tc.is_deferrable, tc.initially_deferred FROM information_schema.table_constraints AS tc JOIN information_schema.key_column_usage AS kcu ON tc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME JOIN information_schema.constraint_column_usage AS ccu ON ccu.constraint_name = tc.constraint_name WHERE tc.TABLE_SCHEMA = '%s' AND tc.TABLE_NAME = '%s';"; + + private List systemDatabases = Arrays.asList("postgres"); @Override public List databases(Connection connection) { - return SQLExecutor.getInstance().executeSql(connection, "SELECT datname FROM pg_database;", resultSet -> { + List list = SQLExecutor.getInstance().executeSql(connection, "SELECT datname FROM pg_database;", resultSet -> { List databases = new ArrayList<>(); try { while (resultSet.next()) { @@ -49,8 +50,30 @@ public List databases(Connection connection) { } return databases; }); + return sortDatabase(list, systemDatabases,connection); + } + + private List systemSchemas = Arrays.asList("pg_toast","pg_temp_1","pg_toast_temp_1","pg_catalog","information_schema"); + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().execute(connection, + "SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> { + List databases = new ArrayList<>(); + while (resultSet.next()) { + Schema schema = new Schema(); + String name = resultSet.getString("schema_name"); + String catalogName = resultSet.getString("catalog_name"); + schema.setName(name); + schema.setDatabaseName(catalogName); + databases.add(schema); + } + return databases; + }); + return SortUtils.sortSchema(schemas, systemSchemas); } + + private static final String SELECT_TABLE_INDEX = "SELECT tmp.INDISPRIMARY AS Index_primary, tmp.TABLE_SCHEM, tmp.TABLE_NAME, tmp.NON_UNIQUE, tmp.INDEX_QUALIFIER, tmp.INDEX_NAME AS Key_name, tmp.indisclustered, tmp.ORDINAL_POSITION AS Seq_in_index, TRIM ( BOTH '\"' FROM pg_get_indexdef ( tmp.CI_OID, tmp.ORDINAL_POSITION, FALSE ) ) AS Column_name,CASE tmp.AM_NAME WHEN 'btree' THEN CASE tmp.I_INDOPTION [ tmp.ORDINAL_POSITION - 1 ] & 1 :: SMALLINT WHEN 1 THEN 'D' ELSE'A' END ELSE NULL END AS Collation, tmp.CARDINALITY, tmp.PAGES, tmp.FILTER_CONDITION , tmp.AM_NAME AS Index_method, tmp.DESCRIPTION AS Index_comment FROM ( SELECT n.nspname AS TABLE_SCHEM, ct.relname AS TABLE_NAME, NOT i.indisunique AS NON_UNIQUE, NULL AS INDEX_QUALIFIER, ci.relname AS INDEX_NAME,i.INDISPRIMARY , i.indisclustered , ( information_schema._pg_expandarray ( i.indkey ) ).n AS ORDINAL_POSITION, ci.reltuples AS CARDINALITY, ci.relpages AS PAGES, pg_get_expr ( i.indpred, i.indrelid ) AS FILTER_CONDITION, ci.OID AS CI_OID, i.indoption AS I_INDOPTION, am.amname AS AM_NAME , d.description FROM pg_class ct JOIN pg_namespace n ON ( ct.relnamespace = n.OID ) JOIN pg_index i ON ( ct.OID = i.indrelid ) JOIN pg_class ci ON ( ci.OID = i.indexrelid ) JOIN pg_am am ON ( ci.relam = am.OID ) left outer join pg_description d on i.indexrelid = d.objoid WHERE n.nspname = '%s' AND ct.relname = '%s' ) AS tmp ;"; private static String ROUTINES_SQL = " SELECT p.proname, p.prokind, pg_catalog.pg_get_functiondef(p.oid) as \"code\" FROM pg_catalog.pg_proc p " @@ -99,22 +122,6 @@ public String tableDDL(Connection connection, String databaseName, String schema }); } - @Override - public List schemas(Connection connection, String databaseName) { - return SQLExecutor.getInstance().execute(connection, - "SELECT catalog_name, schema_name FROM information_schema.schemata;", resultSet -> { - List databases = new ArrayList<>(); - while (resultSet.next()) { - Schema schema = new Schema(); - String name = resultSet.getString("schema_name"); - String catalogName = resultSet.getString("catalog_name"); - schema.setName(name); - schema.setDatabaseName(catalogName); - databases.add(schema); - } - return databases; - }); - } @Override public Function function(Connection connection, @NotEmpty String databaseName, String schemaName, diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java index 833c66d53..2a521d2e7 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java @@ -156,5 +156,20 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { return script.toString(); } + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + StringBuilder sqlStr = new StringBuilder(sql.length() + 17); + sqlStr.append(sql); + if (offset == 0) { + sqlStr.append(" LIMIT "); + sqlStr.append(pageSize); + } else { + sqlStr.append(" LIMIT "); + sqlStr.append(pageSize); + sqlStr.append(" OFFSET "); + sqlStr.append(offset); + } + return sqlStr.toString(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java index 379651fdf..b648834a9 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java @@ -18,7 +18,7 @@ public String buildCreateTableSql(Table table) { // append column for (TableColumn column : table.getColumnList()) { - if(StringUtils.isBlank(column.getName())|| StringUtils.isBlank(column.getColumnType())){ + if (StringUtils.isBlank(column.getName()) || StringUtils.isBlank(column.getColumnType())) { continue; } SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(column.getColumnType()); @@ -27,7 +27,7 @@ public String buildCreateTableSql(Table table) { // append primary key and index for (TableIndex tableIndex : table.getIndexList()) { - if(StringUtils.isBlank(tableIndex.getName())|| StringUtils.isBlank(tableIndex.getType())){ + if (StringUtils.isBlank(tableIndex.getName()) || StringUtils.isBlank(tableIndex.getType())) { continue; } SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); @@ -53,7 +53,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify column for (TableColumn tableColumn : newTable.getColumnList()) { - if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType())&& StringUtils.isNotBlank(tableColumn.getName())){ + if (StringUtils.isNotBlank(tableColumn.getEditStatus()) && StringUtils.isNotBlank(tableColumn.getColumnType()) && StringUtils.isNotBlank(tableColumn.getName())) { script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); SqliteColumnTypeEnum typeEnum = SqliteColumnTypeEnum.getByType(tableColumn.getColumnType()); script.append("\t").append(typeEnum.buildModifyColumn(tableColumn)).append(";\n"); @@ -63,7 +63,7 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { // append modify index for (TableIndex tableIndex : newTable.getIndexList()) { if (StringUtils.isNotBlank(tableIndex.getEditStatus()) && StringUtils.isNotBlank(tableIndex.getType())) { - // script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); + // script.append("ALTER TABLE ").append("\"").append(newTable.getDatabaseName()).append("\".\"").append(newTable.getName()).append("\"").append("\n"); SqliteIndexTypeEnum sqliteIndexTypeEnum = SqliteIndexTypeEnum.getByType(tableIndex.getType()); script.append("\t").append(sqliteIndexTypeEnum.buildModifyIndex(tableIndex)).append(";\n"); } @@ -75,5 +75,8 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { return script.toString(); } - + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + return "select * from(" + sql + ") t LIMIT " + pageNo + " OFFSET " + offset + ""; + } } 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 53b91e375..89e3284d4 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 @@ -14,10 +14,33 @@ import ai.chat2db.spi.jdbc.DefaultMetaService; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; +import ai.chat2db.spi.util.SortUtils; import jakarta.validation.constraints.NotEmpty; import org.apache.commons.lang3.StringUtils; +import static ai.chat2db.spi.util.SortUtils.sortDatabase; + public class SqlServerMetaData extends DefaultMetaService implements MetaData { + + + + private List systemDatabases = Arrays.asList("master", "model", "msdb", "tempdb"); + @Override + public List databases(Connection connection) { + List databases = SQLExecutor.getInstance().databases(connection); + return sortDatabase(databases,systemDatabases,connection); + } + + private List systemSchemas = Arrays.asList("guest", "INFORMATION_SCHEMA", "sys", "db_owner", + "db_accessadmin", "db_securityadmin", "db_ddladmin", "db_backupoperator", "db_datareader", "db_datawriter", + "db_denydatareader", "db_denydatawriter"); + + @Override + public List schemas(Connection connection, String databaseName) { + List schemas = SQLExecutor.getInstance().schemas(connection, databaseName, null); + return SortUtils.sortSchema(schemas, systemSchemas); + } + private String functionSQL = "CREATE FUNCTION tableSchema.ufn_GetCreateTableScript( @schema_name NVARCHAR(128), @table_name NVARCHAR" + "(128)) RETURNS NVARCHAR(MAX) AS BEGIN DECLARE @CreateTableScript NVARCHAR(MAX); DECLARE @IndexScripts " @@ -49,7 +72,6 @@ public class SqlServerMetaData extends DefaultMetaService implements MetaData { @Override public String tableDDL(Connection connection, String databaseName, String schemaName, String tableName) { try { - System.out.println(functionSQL); SQLExecutor.getInstance().executeSql(connection, functionSQL.replace("tableSchema", schemaName), resultSet -> null); } catch (Exception e) { diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java index 5101f8d08..22fbe67e6 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java @@ -6,6 +6,7 @@ import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; public class SqlServerSqlBuilder implements SqlBuilder { @@ -80,9 +81,9 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { script.append(buildRenameTable(oldTable, newTable)); } if (!StringUtils.equalsIgnoreCase(oldTable.getComment(), newTable.getComment())) { - if(oldTable.getComment() == null){ + if (oldTable.getComment() == null) { script.append("\n").append(buildTableComment(newTable)); - }else { + } else { script.append("\n").append(buildUpdateTableComment(newTable)); } } @@ -110,8 +111,8 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { return script.toString(); } - private static String UPDATE_TABLE_COMMENT_SCRIPT = "exec sp_updateextendedproperty 'MS_Description','%s','SCHEMA','%s','TABLE','%s' \ngo"; + private String buildUpdateTableComment(Table newTable) { return String.format(UPDATE_TABLE_COMMENT_SCRIPT, newTable.getComment(), newTable.getSchemaName(), newTable.getName()); } @@ -121,4 +122,24 @@ private String buildUpdateTableComment(Table newTable) { private String buildRenameTable(Table oldTable, Table newTable) { return String.format(RENAME_TABLE_SCRIPT, oldTable.getName(), newTable.getName()); } + + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + String version = Chat2DBContext.getDbVersion(); + if (StringUtils.isNotBlank(version)) { + String[] versions = version.split("\\."); + if (versions.length > 0 && Integer.parseInt(versions[0]) >= 11) { + StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); + sqlBuilder.append(sql); + sqlBuilder.append("\n OFFSET "); + sqlBuilder.append(offset); + sqlBuilder.append(" ROWS "); + sqlBuilder.append(" FETCH NEXT "); + sqlBuilder.append(pageSize); + sqlBuilder.append(" ROWS ONLY"); + return sqlBuilder.toString(); + } + } + return ""; + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 471b7125c..76f354ec0 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -51,33 +51,7 @@ public ListResult queryAll(DatabaseQueryAllParam param) { } private List getDatabases(String dbType, Connection connection) { - MetaData metaData = Chat2DBContext.getMetaData(dbType); - List databases = metaData.databases(connection); - sortDatabases(databases,connection); - return databases; - } - - private void sortDatabases(List databases,Connection connection) { - if (CollectionUtils.isEmpty(databases)) { - return; - } - String ulr = null; - try { - ulr = connection.getMetaData().getURL(); - } catch (SQLException e) { - log.error("get url error", e); - } - // If the database name contains the name of the current database, the current database is placed in the first place - int num = -1; - for (int i = 0; i < databases.size(); i++) { - if (StringUtils.isNotBlank(ulr) && StringUtils.isNotBlank(databases.get(i).getName())&& ulr.contains(databases.get(i).getName())) { - num = i; - break; - } - } - if (num != -1 && num != 0) { - Collections.swap(databases, num, 0); - } + return Chat2DBContext.getMetaData(dbType).databases(connection); } @Override diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java index 7352202d2..0892e1cd9 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DlTemplateServiceImpl.java @@ -146,14 +146,24 @@ private ExecuteResult executeSQL(String originalSql, DbType dbType, DlExecutePar } catch (ParserException e) { log.warn("解析sql失败:{}", originalSql, e); } + ExecuteResult executeResult = null; + if (SqlTypeEnum.SELECT.getCode().equals(sqlType) && !SqlUtils.hasPageLimit(originalSql,dbType)) { + String pageLimit = Chat2DBContext.getSqlBuilder().pageLimit(originalSql, offset, pageNo, pageSize); + if(StringUtils.isNotBlank(pageLimit)) { + executeResult = execute(pageLimit, 0, count); + } + } + if (executeResult == null || !executeResult.getSuccess()) { + executeResult = execute(originalSql, offset, count); + } + - ExecuteResult executeResult = execute(originalSql, offset, count); executeResult.setSqlType(sqlType); executeResult.setOriginalSql(originalSql); try { - SqlUtils.buildCanEditResult(originalSql, dbType,executeResult); + SqlUtils.buildCanEditResult(originalSql, dbType, executeResult); } catch (Exception e) { - + log.warn("buildCanEditResult error", e); } if (SqlTypeEnum.SELECT.getCode().equals(sqlType)) { executeResult.setPageNo(pageNo); @@ -258,7 +268,7 @@ public DataResult updateSelectResult(UpdateSelectResultParam param) { private String getDeleteSql(UpdateSelectResultParam param, List row, MetaData metaSchema) { StringBuilder script = new StringBuilder(); - script.append("DELETE FROM ").append( param.getTableName()).append(""); + script.append("DELETE FROM ").append(param.getTableName()).append(""); script.append(buildWhere(param.getHeaderList(), row, metaSchema)); return script.toString(); @@ -291,7 +301,7 @@ private String getInsertSql(UpdateSelectResultParam param, List row, Met return ""; } StringBuilder script = new StringBuilder(); - script.append("INSERT INTO ").append( param.getTableName()) + script.append("INSERT INTO ").append(param.getTableName()) .append(" ("); for (int i = 1; i < row.size(); i++) { Header header = param.getHeaderList().get(i); @@ -366,7 +376,7 @@ private void addOperationLog(ExecuteResult executeResult) { createParam.setSchemaName(connectInfo.getSchemaName()); createParam.setUseTime(executeResult.getDuration()); createParam.setType(connectInfo.getDbType()); - createParam.setOperationRows(executeResult.getUpdateCount() != null ? Long.valueOf(executeResult.getUpdateCount()):null); + createParam.setOperationRows(executeResult.getUpdateCount() != null ? Long.valueOf(executeResult.getUpdateCount()) : null); operationLogService.create(createParam); } catch (Exception e) { log.error("addOperationLog error:", e); diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java index 191c85fe8..7c26d0cd1 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/java/ai/chat2db/server/domain/repository/mapper/TableCacheMapper.java @@ -1,7 +1,10 @@ package ai.chat2db.server.domain.repository.mapper; import ai.chat2db.server.domain.repository.entity.TableCacheDO; +import ai.chat2db.server.domain.repository.entity.TeamUserDO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -16,4 +19,6 @@ public interface TableCacheMapper extends BaseMapper { void batchInsert(List list); + + IPage pageQuery(IPage page, @Param("dataSourceId") Long dataSourceId, @Param("databaseName") String databaseName, @Param("schemaName") String schemaName, @Param("searchKey") String searchKey); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml index 10d8441c7..c367e2605 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-repository/src/main/resources/mapper/TableCacheMapper.xml @@ -10,4 +10,23 @@ (#{item.dataSourceId},#{item.databaseName},#{item.schemaName},#{item.tableName},#{item.key},#{item.version},#{item.columns},#{item.extendInfo}) + + diff --git a/chat2db-server/chat2db-server-test/pom.xml b/chat2db-server/chat2db-server-test/pom.xml index 175517935..5874b1f56 100644 --- a/chat2db-server/chat2db-server-test/pom.xml +++ b/chat2db-server/chat2db-server-test/pom.xml @@ -35,6 +35,17 @@ 2.0.4 test + + + + + + + + + + + diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java index 40833045e..70238a00d 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java @@ -1,21 +1,42 @@ -package ai.chat2db.server.web.api.controller.system; - -import ai.chat2db.server.tools.common.util.ConfigUtils; -import ai.chat2db.server.web.api.controller.system.util.SystemUtils; -import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; -import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class AutomaticUpgrade { - - @Scheduled(fixedRate = 3600000) // 每小时运行一次 - public void checkVersionUpdates() { - AppVersionVO appVersion = SystemUtils.getLatestVersion(ConfigUtils.getLocalVersion(), "auto", ""); - if (appVersion != null) { - SystemUtils.upgrade(appVersion); - } - } -} +////package ai.chat2db.server.web.api.controller.system; +//// +////import ai.chat2db.server.tools.common.util.ConfigUtils; +////import ai.chat2db.server.web.api.controller.system.util.SystemUtils; +////import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; +////import lombok.extern.slf4j.Slf4j; +////import org.springframework.scheduling.annotation.Scheduled; +////import org.springframework.stereotype.Component; +//// +////@Slf4j +////@Component +////public class AutomaticUpgrade { +//// +//// @Scheduled(fixedRate = 3600000) // 每小时运行一次 +//// public void checkVersionUpdates() { +//// AppVersionVO appVersion = SystemUtils.getLatestVersion(ConfigUtils.getLocalVersion(), "auto", ""); +//// if (appVersion != null) { +//// SystemUtils.upgrade(appVersion); +//// } +//// } +////} +// +//const handleInsertText = (text, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { +// if ( +// [DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.ORACLE, DatabaseTypeCode.DB2, DatabaseTypeCode.SQLITE].includes( +// databaseCode, +// ) +// ) { +// return `\"${text}\"`; +// } else if ([DatabaseTypeCode.SQLSERVER].includes(databaseCode)) { +// return `[${text}]`; +// } else if ([DatabaseTypeCode.MYSQL].includes(databaseCode)) { +// return `\`${text}\``; +// } else { +// return `${text}`; +// } +// }; +// +//"" oracele sqlite postgrsql h2 dm +// ` MYSQL clickhouse MariaDB +// [ sqlserver +// diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java index 9aa1c09b7..21f1f8755 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/MetaData.java @@ -206,5 +206,4 @@ List indexes(Connection connection, @NotEmpty String databaseName, S */ String getMetaDataName(String ...names); - } \ No newline at end of file diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java index a219a40b1..bb31e1572 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java @@ -21,4 +21,16 @@ public interface SqlBuilder { * @return */ String buildModifyTaleSql(Table oldTable, Table newTable); + + + /** + * Generate page limit sql + * + * @param sql + * @param offset + * @param pageNo + * @param pageSize + * @return + */ + String pageLimit(String sql, int offset, int pageNo, int pageSize); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java index d32c5629e..aad145174 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -14,4 +14,9 @@ public String buildCreateTableSql(Table table) { public String buildModifyTaleSql(Table oldTable, Table newTable) { return null; } + + @Override + public String pageLimit(String sql, int offset, int pageNo, int pageSize) { + return null; + } } 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 de609d45c..abec6ee87 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 @@ -61,7 +61,7 @@ public static MetaData getMetaData() { } public static MetaData getMetaData(String dbType) { - if(StringUtils.isBlank(dbType)){ + if (StringUtils.isBlank(dbType)) { return getMetaData(); } return PLUGIN_MAP.get(dbType).getMetaData(); @@ -83,7 +83,7 @@ public static Connection getConnection() { connection = connectInfo.getConnection(); if (connection != null) { return connection; - }else { + } else { connection = getDBManage().getConnection(connectInfo); } } @@ -91,6 +91,26 @@ public static Connection getConnection() { return connection; } + public static String getDbVersion() { + ConnectInfo connectInfo = getConnectInfo(); + String dbVersion = connectInfo.getDbVersion(); + if (dbVersion == null) { + synchronized (connectInfo) { + if (connectInfo.getDbVersion() != null) { + return connectInfo.getDbVersion(); + } else { + dbVersion = SQLExecutor.getInstance().getDbVersion(getConnection()); + connectInfo.setDbVersion(dbVersion); + return connectInfo.getDbVersion(); + } + } + } else { + return dbVersion; + } + + } + + /** * 设置context * @@ -124,7 +144,7 @@ public static void removeContext() { Session session = connectInfo.getSession(); if (session != null && session.isConnected() && connectInfo.getSsh() != null - && connectInfo.getSsh().isUse()) { + && connectInfo.getSsh().isUse()) { try { session.delPortForwardingL(Integer.parseInt(connectInfo.getSsh().getLocalPort())); } catch (JSchException e) { diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java index 6cf380759..4b96e479a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/ConnectInfo.java @@ -129,12 +129,23 @@ public class ConnectInfo { public Connection connection; - + /** + * Database version used for different database + */ + private String dbVersion; private DriverConfig driverConfig; + public String getDbVersion() { + return dbVersion; + } + + public void setDbVersion(String dbVersion) { + this.dbVersion = dbVersion; + } + public DriverConfig getDriverConfig() { return driverConfig; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java index 9d20c5363..7b0628eb5 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/sql/SQLExecutor.java @@ -17,6 +17,7 @@ import cn.hutool.core.date.TimeInterval; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.jdbc.support.JdbcUtils; import org.springframework.util.Assert; @@ -158,7 +159,7 @@ public ExecuteResult executeUpdate(final String sql, Connection connection, int int affectedRows = stmt.executeUpdate(sql); if (affectedRows != n) { executeResult.setSuccess(false); - executeResult.setMessage("Update error "+ sql +" update affectedRows = " + affectedRows + ", Each SQL statement should update no more than one record. Please use a unique key for updates."); + executeResult.setMessage("Update error " + sql + " update affectedRows = " + affectedRows + ", Each SQL statement should update no more than one record. Please use a unique key for updates."); connection.rollback(); } } @@ -185,6 +186,11 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li ExecuteResult executeResult = ExecuteResult.builder().sql(sql).success(Boolean.TRUE).build(); try (Statement stmt = connection.createStatement()) { stmt.setFetchSize(EasyToolsConstant.MAX_PAGE_SIZE); + stmt.setQueryTimeout(30); + if (offset != null && count != null) { + stmt.setMaxRows(offset + count); + } + TimeInterval timeInterval = new TimeInterval(); boolean query = stmt.execute(sql); executeResult.setDescription(I18nUtils.getMessage("sqlResult.success")); @@ -200,11 +206,19 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li // 获取header信息 List
    headerList = Lists.newArrayListWithExpectedSize(col); executeResult.setHeaderList(headerList); + int chat2dbAutoRowIdIndex = -1;// chat2db自动生成的行分页ID + for (int i = 1; i <= col; i++) { + String name = ResultSetUtils.getColumnName(resultSetMetaData, i); + if ("CAHT2DB_AUTO_ROW_ID".equals(name)) { + chat2dbAutoRowIdIndex = i; + continue; + } + String dataType = ai.chat2db.spi.util.JdbcUtils.resolveDataType( + resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode(); headerList.add(Header.builder() - .dataType(ai.chat2db.spi.util.JdbcUtils.resolveDataType( - resultSetMetaData.getColumnTypeName(i), resultSetMetaData.getColumnType(i)).getCode()) - .name(ResultSetUtils.getColumnName(resultSetMetaData, i)) + .dataType(dataType) + .name(name) .build()); } @@ -224,6 +238,9 @@ public ExecuteResult execute(final String sql, Connection connection, boolean li List row = Lists.newArrayListWithExpectedSize(col); dataList.add(row); for (int i = 1; i <= col; i++) { + if (chat2dbAutoRowIdIndex == i) { + continue; + } row.add(ai.chat2db.spi.util.JdbcUtils.getResultSetValue(rs, i, limitRowSize)); } if (count != null && count > 0 && rowCount++ >= count) { @@ -263,7 +280,11 @@ public ExecuteResult execute(Connection connection, String sql) throws SQLExcept */ public List databases(Connection connection) { try (ResultSet resultSet = connection.getMetaData().getCatalogs();) { - return ResultSetUtils.toObjectList(resultSet, Database.class); + List databases = ResultSetUtils.toObjectList(resultSet, Database.class); + if (CollectionUtils.isEmpty(databases)) { + return databases; + } + return databases.stream().filter(database -> database.getName() != null).collect(Collectors.toList()); } catch (SQLException e) { throw new RuntimeException(e); } @@ -428,4 +449,15 @@ public List procedures(Connection connection, String databaseName, St } } + public String getDbVersion(Connection connection) { + try { + String dbVersion = connection.getMetaData().getDatabaseProductVersion(); + return dbVersion; + } catch (Exception e) { + log.error("get db version error", e); + //throw new RuntimeException(e); + } + return ""; + } + } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java index 0ca106a84..7f311d92e 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/JdbcJarUtils.java @@ -42,7 +42,6 @@ public static void asyncDownload(List urls) throws Exception { String outputPath = PATH + url.substring(url.lastIndexOf("/") + 1); File file = new File(outputPath); if (file.exists()) { - System.out.println("File already exists: " + outputPath); continue; } asyncDownload(url); @@ -77,7 +76,6 @@ public void onResponse(Call call, Response response) throws IOException { } fos.flush(); } - System.out.println("File downloaded: " + outputPath); } }); } @@ -105,7 +103,6 @@ public static void download(String url) throws IOException { } fos.flush(); } - System.out.println("File downloaded: " + outputPath); } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SortUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SortUtils.java new file mode 100644 index 000000000..95ee7974f --- /dev/null +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SortUtils.java @@ -0,0 +1,77 @@ +package ai.chat2db.spi.util; + +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Schema; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class SortUtils { + + public static List sortDatabase(List databases, List list, Connection connection) { + if (CollectionUtils.isEmpty(databases)) { + return databases; + } + List systemDatabases = databases.stream() + .filter(database -> list.contains(database.getName())).collect(Collectors.toList()); + List userDatabases = databases.stream() + .filter(database -> !list.contains(database.getName())).collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(userDatabases)) { + return databases; + } + if (CollectionUtils.isEmpty(systemDatabases)) { + return userDatabases; + } + List databaseList = Stream.concat(userDatabases.stream(), systemDatabases.stream()) + .collect(Collectors.toList()); + // If the database name contains the name of the current database, the current database is placed in the first place + + String ulr; + try { + ulr = connection.getMetaData().getURL(); + } catch (SQLException e) { + return databaseList; + } + // If the database name contains the name of the current database, the current database is placed in the first place + int no = -1; + for (int i = 0; i < databases.size(); i++) { + if (StringUtils.isNotBlank(ulr) + && StringUtils.isNotBlank(databases.get(i).getName()) + && ulr.contains(databases.get(i).getName()) + && !"mysql".equalsIgnoreCase(databases.get(i).getName())) { + no = i; + break; + } + } + if (no != -1 && no != 0) { + Collections.swap(databaseList, no, 0); + } + return databaseList; + } + + public static List sortSchema(List schemas, List systemSchemas) { + if (CollectionUtils.isEmpty(schemas)) { + return schemas; + } + List systemSchema = schemas.stream() + .filter(schema -> systemSchemas.contains(schema.getName()) || "APEX_".startsWith(schema.getName())).collect(Collectors.toList()); + List userSchema = schemas.stream() + .filter(schema -> !systemSchemas.contains(schema.getName()) && !"APEX_".startsWith(schema.getName())).collect(Collectors.toList()); + + if (CollectionUtils.isEmpty(userSchema)) { + return schemas; + } + if (CollectionUtils.isEmpty(systemSchema)) { + return userSchema; + } + return Stream.concat(userSchema.stream(), systemSchema.stream()) + .collect(Collectors.toList()); + } +} diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java index e6aef38de..cc3e578a3 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/util/SqlUtils.java @@ -12,13 +12,12 @@ import com.alibaba.druid.sql.ast.statement.SQLSelectStatement; import com.alibaba.druid.sql.ast.statement.SQLTableSource; import com.alibaba.druid.sql.parser.SQLParserUtils; +import net.sf.jsqlparser.expression.BinaryExpression; +import net.sf.jsqlparser.expression.Function; import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.statement.Statement; import net.sf.jsqlparser.statement.Statements; -import net.sf.jsqlparser.statement.select.PlainSelect; -import net.sf.jsqlparser.statement.select.Select; -import net.sf.jsqlparser.statement.select.SelectExpressionItem; -import net.sf.jsqlparser.statement.select.SelectItem; +import net.sf.jsqlparser.statement.select.*; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -47,7 +46,18 @@ public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult e if (expressionItem.getAlias() != null) { //canEdit = false; // 找到了一个别名 executeResult.setCanEdit(false); - return ; + return; + } + if (item instanceof SelectExpressionItem) { + SelectExpressionItem selectExpressionItem = (SelectExpressionItem) item; + // 如果表达式是一个函数 + if (selectExpressionItem.getExpression() instanceof Function) { + Function function = (Function) selectExpressionItem.getExpression(); + // 检查函数是否为 "COUNT" + if ("COUNT".equalsIgnoreCase(function.getName())) { + executeResult.setCanEdit(false); + return; } + } } } } @@ -58,6 +68,8 @@ public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult e sqlSelectStatement.getSelect().getFirstQueryBlock().getFrom()); executeResult.setTableName(getMetaDataTableName(sqlExprTableSource.getCatalog(), sqlExprTableSource.getSchema(), sqlExprTableSource.getTableName())); } + }else { + executeResult.setCanEdit(false); } } } catch (Exception e) { @@ -66,7 +78,7 @@ public static void buildCanEditResult(String sql, DbType dbType, ExecuteResult e } private static String getMetaDataTableName(String... names) { - return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> name ).collect(Collectors.joining(".")); + return Arrays.stream(names).filter(name -> StringUtils.isNotBlank(name)).map(name -> name).collect(Collectors.joining(".")); } public static String formatSQLString(Object para) { @@ -116,4 +128,29 @@ public static String getSqlValue(String value, String dataType) { DataTypeEnum dataTypeEnum = DataTypeEnum.getByCode(dataType); return dataTypeEnum.getSqlValue(value); } + + public static boolean hasPageLimit(String sql, DbType dbType) { + try { + Statement statement = CCJSqlParserUtil.parse(sql); + if (statement instanceof Select) { + Select selectStatement = (Select) statement; + SelectBody selectBody = selectStatement.getSelectBody(); + // 检查常见的分页方法 + if (selectBody instanceof PlainSelect) { + PlainSelect plainSelect = (PlainSelect) selectBody; + // 检查 LIMIT + if (plainSelect.getLimit() != null || plainSelect.getOffset() != null || plainSelect.getTop() != null || plainSelect.getFetch() != null) { + return true; + } + if (DbType.oracle.equals(dbType)) { + return sql.contains("ROWNUM") || sql.contains("rownum"); + } + } + } + } catch (Exception e) { + return false; + } + return false; + } + } \ No newline at end of file From 89373b6461bef0b474a3521fcac552df0bb38def Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 08:59:54 +0800 Subject: [PATCH 1041/1069] fix:Editing the tab name will cause some tabs to disappear --- .../SearchResult/TableBox/index.tsx | 4 +- .../src/components/SearchResult/index.tsx | 4 +- .../components/{TabsNew => Tabs}/index.less | 0 .../components/{TabsNew => Tabs}/index.tsx | 0 .../src/components/TestVersion/index.less | 19 ----- .../src/components/TestVersion/index.tsx | 84 ------------------- .../components/WorkspaceRight/index.tsx | 27 +++++- 7 files changed, 29 insertions(+), 109 deletions(-) rename chat2db-client/src/components/{TabsNew => Tabs}/index.less (100%) rename chat2db-client/src/components/{TabsNew => Tabs}/index.tsx (100%) delete mode 100644 chat2db-client/src/components/TestVersion/index.less delete mode 100644 chat2db-client/src/components/TestVersion/index.tsx diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index dec89d844..16c136e4d 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -21,6 +21,7 @@ import styles from './index.less'; import sqlService, { IExportParams, IExecuteSqlParams } from '@/service/sql'; import { downloadFile } from '@/utils/common'; import lodash from 'lodash'; +import { v4 as uuid } from 'uuid'; interface ITableProps { className?: string; @@ -864,6 +865,7 @@ export default function TableBox(props: ITableProps) { onCancel={handleCancel} width="60vw" maskClosable={false} + destroyOnClose={true} footer={[ - - - ); - notificationApi.open({ - message: i18n('common.text.updateReminder'), - description: `${i18n('common.text.detectionLatestVersion')} v${responseText.version}`, - btn, - key, - onClose: close, - }); - } - } - catch { - - } - }; - return <> - {notificationDom} - - -}) 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 c26412524..617aaa05f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -6,7 +6,7 @@ import classnames from 'classnames'; import { ConsoleOpenedStatus, ConsoleStatus, TreeNodeType, WorkspaceTabType, workspaceTabConfig } from '@/constants'; import historyService from '@/service/history'; import sqlService from '@/service/sql'; -import TabsNew, { ITabItem } from '@/components/TabsNew'; +import Tabs, { ITabItem } from '@/components/Tabs'; // import WorkspaceExtend from '../WorkspaceExtend'; import SearchResult from '@/components/SearchResult'; import Iconfont from '@/components/Iconfont'; @@ -28,6 +28,7 @@ import { import indexedDB from '@/indexedDB'; import { osNow } from '@/utils'; import { compatibleDataBaseName } from '@/utils/database'; +import lodash from 'lodash'; interface IProps { className?: string; @@ -50,6 +51,7 @@ const WorkspaceRight = memo((props: IProps) => { useEffect(() => { setActiveConsoleId(null); + setWorkspaceTabList([]); }, [curWorkspaceParams]); // 根据保存的console列表生成tab列表 @@ -62,7 +64,26 @@ const WorkspaceRight = memo((props: IProps) => { uniqueData: t, }; }); - setWorkspaceTabList(newTabList || []); + console.log(workspaceTabList, newTabList); + if (workspaceTabList.length) { + const newWorkspaceTabList = lodash.cloneDeep(workspaceTabList); + const newAddList: any = []; + newTabList.forEach((t) => { + let flag = false; + workspaceTabList.forEach((item, index) => { + if (item.id === t.id) { + flag = true; + newWorkspaceTabList[index] = t; + } + }); + if (!flag) { + newAddList.push(t); + } + }); + setWorkspaceTabList([...newWorkspaceTabList, ...newAddList]); + } else { + setWorkspaceTabList(newTabList || []); + } if (!activeConsoleId) { setActiveConsoleId(newTabList[0]?.id); } @@ -577,7 +598,7 @@ const WorkspaceRight = memo((props: IProps) => {
    - Date: Mon, 23 Oct 2023 15:33:26 +0800 Subject: [PATCH 1042/1069] Support for creating database and schema --- .../plugin/db2/builder/DB2SqlBuilder.java | 17 +++- .../java/ai/chat2db/plugin/h2/H2Meta.java | 8 ++ .../plugin/h2/builder/H2SqlBuilder.java | 22 ++++++ .../plugin/mysql/builder/MysqlSqlBuilder.java | 19 ++++- .../oracle/builder/OracleSqlBuilder.java | 19 ++++- .../builder/PostgreSQLSqlBuilder.java | 38 ++++++++- .../plugin/sqlite/builder/SqliteBuilder.java | 3 +- .../builder/SqlServerSqlBuilder.java | 36 ++++++++- ...ionParam.java => DatabaseCreateParam.java} | 13 ++- .../domain/api/service/DatabaseService.java | 16 ++-- .../domain/core/impl/DatabaseServiceImpl.java | 33 ++++---- .../controller/rdb/DatabaseController.java | 27 +++++-- .../api/controller/rdb/RdbDdlController.java | 79 +------------------ .../api/controller/rdb/SchemaController.java | 22 ++++-- .../rdb/converter/DatabaseConverter.java | 11 +++ .../rdb/request/DatabaseCreateRequest.java | 16 ++++ .../rdb/request/SchemaCreateRequest.java | 21 +++++ .../controller/system/AutomaticUpgrade.java | 42 ---------- .../main/java/ai/chat2db/spi/SqlBuilder.java | 36 +++++++++ .../ai/chat2db/spi/jdbc/DefaultDBManage.java | 2 + .../chat2db/spi/jdbc/DefaultSqlBuilder.java | 25 ++++++ .../java/ai/chat2db/spi/model/Database.java | 7 ++ .../java/ai/chat2db/spi/model/Schema.java | 6 ++ 23 files changed, 340 insertions(+), 178 deletions(-) create mode 100644 chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/H2SqlBuilder.java rename chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/{DatabaseOperationParam.java => DatabaseCreateParam.java} (62%) create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DatabaseConverter.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseCreateRequest.java create mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCreateRequest.java delete mode 100644 chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java diff --git a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java index 6a67220ea..ce7a84d22 100644 --- a/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-db2/src/main/java/ai/chat2db/plugin/db2/builder/DB2SqlBuilder.java @@ -2,15 +2,16 @@ import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Schema; +import org.apache.commons.lang3.StringUtils; public class DB2SqlBuilder extends DefaultSqlBuilder { - @Override public String pageLimit(String sql, int offset, int pageNo, int pageSize) { int startRow = offset + 1; - int endRow = offset+ pageSize; + int endRow = offset + pageSize; StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120); sqlBuilder.append("SELECT * FROM (SELECT TMP_PAGE.*,ROWNUMBER() OVER() AS PAGEHELPER_ROW_ID FROM ( \n"); sqlBuilder.append(sql); @@ -20,4 +21,16 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { sqlBuilder.append(endRow); return sqlBuilder.toString(); } + + @Override + public String buildCreateSchemaSql(Schema schema) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE SCHEMA \"" + schema.getName() + "\";"); + + if (StringUtils.isNotBlank(schema.getComment())) { + sqlBuilder.append("\nCOMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); + } + + return sqlBuilder.toString(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java index 181987b0e..7bd1082e8 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2Meta.java @@ -5,8 +5,11 @@ import java.sql.ResultSetMetaData; import java.util.*; +import ai.chat2db.plugin.h2.builder.H2SqlBuilder; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.jdbc.DefaultMetaService; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.SQLExecutor; import ai.chat2db.spi.util.SortUtils; @@ -190,4 +193,9 @@ public Table view(Connection connection, String databaseName, String schemaName, return table; }); } + @Override + public SqlBuilder getSqlBuilder() { + return new H2SqlBuilder(); + } + } diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/H2SqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/H2SqlBuilder.java new file mode 100644 index 000000000..ed41f653d --- /dev/null +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/builder/H2SqlBuilder.java @@ -0,0 +1,22 @@ +package ai.chat2db.plugin.h2.builder; + +import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Schema; +import org.apache.commons.lang3.StringUtils; + +public class H2SqlBuilder extends DefaultSqlBuilder implements SqlBuilder { + + @Override + public String buildCreateSchemaSql(Schema schema) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE SCHEMA \"" + schema.getName() + "\";"); + + if (StringUtils.isNotBlank(schema.getComment())) { + sqlBuilder.append("\nCOMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); + } + + return sqlBuilder.toString(); + } + +} 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 c8594bc74..bc1e2d510 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 @@ -3,13 +3,15 @@ import ai.chat2db.plugin.mysql.type.MysqlColumnTypeEnum; import ai.chat2db.plugin.mysql.type.MysqlIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; -public class MysqlSqlBuilder implements SqlBuilder { +public class MysqlSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); @@ -122,4 +124,19 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { } + + + @Override + public String buildCreateDatabaseSql(Database database) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE DATABASE "+database.getName()); + if (StringUtils.isNotBlank(database.getCharset())) { + sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); + } + if (StringUtils.isNotBlank(database.getCollation())) { + sqlBuilder.append(" COLLATE=").append(database.getCollation()); + } + return sqlBuilder.toString(); + } + } diff --git a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java index c1dbbda75..84d9be3c7 100644 --- a/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-oracle/src/main/java/ai/chat2db/plugin/oracle/builder/OracleSqlBuilder.java @@ -3,13 +3,15 @@ import ai.chat2db.plugin.oracle.type.OracleColumnTypeEnum; import ai.chat2db.plugin.oracle.type.OracleIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; -public class OracleSqlBuilder implements SqlBuilder { +public class OracleSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); @@ -124,7 +126,18 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { sqlBuilder.append(startRow); } return sqlBuilder.toString(); - - } + +// @Override +// public String buildCreateSchemaSql(Schema schema){ +// StringBuilder sqlBuilder = new StringBuilder(); +// sqlBuilder.append("CREATE SCHEMA \""+schema.getName()+"\""); +// if(StringUtils.isNotBlank(schema.getOwner())){ +// sqlBuilder.append(" AUTHORIZATION ").append(schema.getOwner()); +// } +// if(StringUtils.isNotBlank(schema.getComment())){ +// sqlBuilder.append("; COMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); +// } +// return sqlBuilder.toString(); +// } } diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java index 2a521d2e7..c92908633 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/builder/PostgreSQLSqlBuilder.java @@ -3,9 +3,8 @@ import ai.chat2db.plugin.postgresql.type.PostgreSQLColumnTypeEnum; import ai.chat2db.plugin.postgresql.type.PostgreSQLIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.*; import com.google.common.collect.Lists; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; @@ -16,7 +15,7 @@ import java.util.stream.Collectors; -public class PostgreSQLSqlBuilder implements SqlBuilder { +public class PostgreSQLSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); @@ -172,4 +171,35 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { return sqlStr.toString(); } + @Override + public String buildCreateDatabaseSql(Database database) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE DATABASE \""+database.getName()+"\""); + sqlBuilder.append("\nWITH "); + if(StringUtils.isNotBlank(database.getCharset())){ + sqlBuilder.append("\n LC_CTYPE = '").append(database.getCharset()).append("' "); + } + if(StringUtils.isNotBlank(database.getCollation())){ + sqlBuilder.append("\n LC_COLLATE = '").append(database.getCollation()).append("' "); + } + + if(StringUtils.isNotBlank(database.getComment())){ + sqlBuilder.append("; COMMENT ON DATABASE \"").append(database.getName()).append("\" IS '").append(database.getComment()).append("';"); + } + return sqlBuilder.toString(); + } + + + @Override + public String buildCreateSchemaSql(Schema schema){ + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE SCHEMA \""+schema.getName()+"\""); + if(StringUtils.isNotBlank(schema.getOwner())){ + sqlBuilder.append(" AUTHORIZATION ").append(schema.getOwner()); + } + if(StringUtils.isNotBlank(schema.getComment())){ + sqlBuilder.append("; COMMENT ON SCHEMA \"").append(schema.getName()).append("\" IS '").append(schema.getComment()).append("';"); + } + return sqlBuilder.toString(); + } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java index b648834a9..fb5460477 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlite/src/main/java/ai/chat2db/plugin/sqlite/builder/SqliteBuilder.java @@ -3,13 +3,14 @@ import ai.chat2db.plugin.sqlite.type.SqliteColumnTypeEnum; import ai.chat2db.plugin.sqlite.type.SqliteIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; import ai.chat2db.spi.model.Table; import ai.chat2db.spi.model.TableColumn; import ai.chat2db.spi.model.TableIndex; import org.apache.commons.lang3.StringUtils; -public class SqliteBuilder implements SqlBuilder { +public class SqliteBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java index 22fbe67e6..ccf671c4b 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java @@ -3,13 +3,12 @@ import ai.chat2db.plugin.sqlserver.type.SqlServerColumnTypeEnum; import ai.chat2db.plugin.sqlserver.type.SqlServerIndexTypeEnum; import ai.chat2db.spi.SqlBuilder; -import ai.chat2db.spi.model.Table; -import ai.chat2db.spi.model.TableColumn; -import ai.chat2db.spi.model.TableIndex; +import ai.chat2db.spi.jdbc.DefaultSqlBuilder; +import ai.chat2db.spi.model.*; import ai.chat2db.spi.sql.Chat2DBContext; import org.apache.commons.lang3.StringUtils; -public class SqlServerSqlBuilder implements SqlBuilder { +public class SqlServerSqlBuilder extends DefaultSqlBuilder implements SqlBuilder { @Override public String buildCreateTableSql(Table table) { StringBuilder script = new StringBuilder(); @@ -142,4 +141,33 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { } return ""; } + + + @Override + public String buildCreateDatabaseSql(Database database) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE DATABASE [" + database.getName() + "]"); + if (StringUtils.isNotBlank(database.getCollation())) { + sqlBuilder.append(" COLLATE ").append(database.getCollation()); + } + sqlBuilder.append("\ngo\n"); + if (StringUtils.isNotBlank(database.getComment())) { + sqlBuilder.append("exec [" + database.getName() + "].sys. sp_addextendedproperty 'MS_Description','") + .append(database.getComment()).append("'").append("'\ngo\n"); + } + return sqlBuilder.toString(); + } + + + @Override + public String buildCreateSchemaSql(Schema schema) { + StringBuilder sqlBuilder = new StringBuilder(); + sqlBuilder.append("CREATE SCHEMA [" + schema.getName() + "] \ngo\n"); + if (StringUtils.isNotBlank(schema.getComment())) { + sqlBuilder.append("exec sp_addextendedproperty 'MS_Description','") + .append(schema.getComment()).append("'").append(",'SCHEMA'") + .append(",'").append(schema.getName()).append("'").append("'\ngo\n"); + } + return sqlBuilder.toString(); + } } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseOperationParam.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseCreateParam.java similarity index 62% rename from chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseOperationParam.java rename to chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseCreateParam.java index bc3692814..920a01f31 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseOperationParam.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/param/datasource/DatabaseCreateParam.java @@ -14,9 +14,16 @@ @AllArgsConstructor @Builder @NoArgsConstructor -public class DatabaseOperationParam { +public class DatabaseCreateParam { - private String databaseName; + private Long dataSourceId; + + private String name; + + private String comment; + + private String charset; + + private String collation; - private String newDatabaseName; } \ No newline at end of file diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java index 5f216e96c..81957a0ac 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/service/DatabaseService.java @@ -1,7 +1,7 @@ package ai.chat2db.server.domain.api.service; import ai.chat2db.server.domain.api.param.*; -import ai.chat2db.server.domain.api.param.datasource.DatabaseOperationParam; +import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.spi.model.*; @@ -47,7 +47,7 @@ public interface DatabaseService { * @param param * @return */ - public ActionResult deleteDatabase(DatabaseOperationParam param); + ActionResult deleteDatabase(DatabaseCreateParam param); /** * 创建database @@ -55,14 +55,14 @@ public interface DatabaseService { * @param param * @return */ - public ActionResult createDatabase(DatabaseOperationParam param); + DataResult createDatabase(Database param); /** * 修改database * * @return */ - public ActionResult modifyDatabase( DatabaseOperationParam param) ; + ActionResult modifyDatabase( DatabaseCreateParam param) ; /** * 删除schema @@ -70,15 +70,15 @@ public interface DatabaseService { * @param param * @return */ - public ActionResult deleteSchema(SchemaOperationParam param) ; + ActionResult deleteSchema(SchemaOperationParam param) ; /** * 创建schema * - * @param param + * @param schema * @return */ - public ActionResult createSchema( SchemaOperationParam param); + DataResult createSchema(Schema schema); /** * 修改schema @@ -86,5 +86,5 @@ public interface DatabaseService { * @param request * @return */ - public ActionResult modifySchema( SchemaOperationParam request); + ActionResult modifySchema( SchemaOperationParam request); } diff --git a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java index 76f354ec0..3d9cca16e 100644 --- a/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java +++ b/chat2db-server/chat2db-server-domain/chat2db-server-domain-core/src/main/java/ai/chat2db/server/domain/core/impl/DatabaseServiceImpl.java @@ -6,7 +6,7 @@ import java.util.List; import java.util.concurrent.CountDownLatch; -import ai.chat2db.server.domain.api.param.datasource.DatabaseOperationParam; +import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.param.SchemaOperationParam; @@ -17,9 +17,11 @@ import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.spi.MetaData; +import ai.chat2db.spi.SqlBuilder; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.MetaSchema; import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Sql; import ai.chat2db.spi.sql.Chat2DBContext; import cn.hutool.core.thread.ThreadUtil; import lombok.extern.slf4j.Slf4j; @@ -69,12 +71,12 @@ public ListResult querySchema(SchemaQueryParam param) { private List getSchemaList(String databaseName, Connection connection) { MetaData metaData = Chat2DBContext.getMetaData(); - List schemas = metaData.schemas(connection,databaseName); - sortSchema(schemas,connection); + List schemas = metaData.schemas(connection, databaseName); + sortSchema(schemas, connection); return schemas; } - private void sortSchema(List schemas,Connection connection) { + private void sortSchema(List schemas, Connection connection) { if (CollectionUtils.isEmpty(schemas)) { return; } @@ -135,21 +137,21 @@ public DataResult queryDatabaseSchema(MetaDataQueryParam param) { } @Override - public ActionResult deleteDatabase(DatabaseOperationParam param) { - Chat2DBContext.getDBManage().dropDatabase(Chat2DBContext.getConnection(), param.getDatabaseName()); + public ActionResult deleteDatabase(DatabaseCreateParam param) { + Chat2DBContext.getDBManage().dropDatabase(Chat2DBContext.getConnection(), param.getName()); return ActionResult.isSuccess(); } @Override - public ActionResult createDatabase(DatabaseOperationParam param) { - Chat2DBContext.getDBManage().createDatabase(Chat2DBContext.getConnection(), param.getDatabaseName()); - return ActionResult.isSuccess(); + public DataResult createDatabase(Database database) { + String sql = Chat2DBContext.getSqlBuilder().buildCreateDatabaseSql(database); + return DataResult.of(Sql.builder().sql(sql).build()); } @Override - public ActionResult modifyDatabase(DatabaseOperationParam param) { - Chat2DBContext.getDBManage().modifyDatabase(Chat2DBContext.getConnection(), param.getDatabaseName(), - param.getNewDatabaseName()); + public ActionResult modifyDatabase(DatabaseCreateParam param) { + Chat2DBContext.getDBManage().modifyDatabase(Chat2DBContext.getConnection(), param.getName(), + param.getName()); return ActionResult.isSuccess(); } @@ -161,10 +163,9 @@ public ActionResult deleteSchema(SchemaOperationParam param) { } @Override - public ActionResult createSchema(SchemaOperationParam param) { - Chat2DBContext.getDBManage().createSchema(Chat2DBContext.getConnection(), param.getDatabaseName(), - param.getSchemaName()); - return ActionResult.isSuccess(); + public DataResult createSchema(Schema schema) { + String sql = Chat2DBContext.getSqlBuilder().buildCreateSchemaSql(schema); + return DataResult.of(Sql.builder().sql(sql).build()); } @Override diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java index 0b45b66f7..848ae1722 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/DatabaseController.java @@ -1,6 +1,6 @@ package ai.chat2db.server.web.api.controller.rdb; -import ai.chat2db.server.domain.api.param.datasource.DatabaseOperationParam; +import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.param.datasource.DatabaseQueryAllParam; import ai.chat2db.server.domain.api.param.MetaDataQueryParam; import ai.chat2db.server.domain.api.service.DatabaseService; @@ -10,12 +10,17 @@ import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.data.source.vo.DatabaseVO; +import ai.chat2db.server.web.api.controller.rdb.converter.DatabaseConverter; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.DatabaseCreateRequest; import ai.chat2db.server.web.api.controller.rdb.request.UpdateDatabaseRequest; import ai.chat2db.server.web.api.controller.rdb.vo.MetaSchemaVO; import ai.chat2db.spi.model.Database; import ai.chat2db.spi.model.MetaSchema; +import ai.chat2db.spi.model.Sql; import jakarta.validation.Valid; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.util.StringUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -36,6 +41,9 @@ public class DatabaseController { @Autowired private DatabaseService databaseService; + @Autowired + public DatabaseConverter databaseConverter; + /** * 查询数据库里包含的database_schema_list * @@ -69,7 +77,7 @@ public ListResult databaseList(@Valid DataSourceBaseRequest request) */ @PostMapping("/delete_database") public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest request) { - DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); + DatabaseCreateParam param = DatabaseCreateParam.builder().name(request.getDatabaseName()).build(); return databaseService.deleteDatabase(param); } @@ -79,10 +87,13 @@ public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest req * @param request * @return */ - @PostMapping("/create_database") - public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest request) { - DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); - return databaseService.createDatabase(param); + @PostMapping("/create_database_sql") + public DataResult createDatabase(@Valid @RequestBody DatabaseCreateRequest request) { + if(StringUtils.isBlank(request.getName())){ + request.setName(request.getDatabaseName()); + } + Database database = databaseConverter.request2param(request); + return databaseService.createDatabase(database); } /** @@ -93,8 +104,8 @@ public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest req */ @PostMapping("/modify_database") public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { - DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()) - .newDatabaseName(request.getNewDatabaseName()).build(); + DatabaseCreateParam param = DatabaseCreateParam.builder().name(request.getDatabaseName()) + .name(request.getNewDatabaseName()).build(); return databaseService.modifyDatabase(param); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java index 3953cdb35..b60705aa1 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDdlController.java @@ -1,7 +1,7 @@ package ai.chat2db.server.web.api.controller.rdb; import ai.chat2db.server.domain.api.param.*; -import ai.chat2db.server.domain.api.param.datasource.DatabaseOperationParam; +import ai.chat2db.server.domain.api.param.datasource.DatabaseCreateParam; import ai.chat2db.server.domain.api.service.DatabaseService; import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; @@ -118,83 +118,6 @@ public DataResult databaseSchemaList(@Valid DataSourceBaseRequest return DataResult.of(schemaDto2vo); } - /** - * 删除数据库 - * - * @param request - * @return - */ - @PostMapping("/delete_database") - public ActionResult deleteDatabase(@Valid @RequestBody DataSourceBaseRequest request) { - DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); - return databaseService.deleteDatabase(param); - } - - /** - * 创建database - * - * @param request - * @return - */ - @PostMapping("/create_database") - public ActionResult createDatabase(@Valid @RequestBody DataSourceBaseRequest request) { - DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()).build(); - return databaseService.createDatabase(param); - } - - /** - * 创建database - * - * @param request - * @return - */ - @PostMapping("/modify_database") - public ActionResult modifyDatabase(@Valid @RequestBody UpdateDatabaseRequest request) { - DatabaseOperationParam param = DatabaseOperationParam.builder().databaseName(request.getDatabaseName()) - .newDatabaseName(request.getNewDatabaseName()).build(); - return databaseService.modifyDatabase(param); - } - - /** - * 删除schema - * - * @param request - * @return - */ - @PostMapping("/delete_schema") - public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { - SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); - return databaseService.deleteSchema(param); - } - - /** - * 创建schema - * - * @param request - * @return - */ - @PostMapping("/create_schema") - public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest request) { - SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); - return databaseService.createSchema(param); - } - - /** - * 创建database - * - * @param request - * @return - */ - @PostMapping("/modify_schema") - public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { - SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); - return databaseService.modifySchema(param); - } - - /** * 查询当前DB下的表columns diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java index ab242deef..156fdb96a 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/SchemaController.java @@ -8,13 +8,16 @@ import ai.chat2db.server.domain.api.service.DlTemplateService; import ai.chat2db.server.domain.api.service.TableService; import ai.chat2db.server.tools.base.wrapper.result.ActionResult; +import ai.chat2db.server.tools.base.wrapper.result.DataResult; import ai.chat2db.server.tools.base.wrapper.result.ListResult; import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect; import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; import ai.chat2db.server.web.api.controller.rdb.converter.RdbWebConverter; +import ai.chat2db.server.web.api.controller.rdb.request.SchemaCreateRequest; import ai.chat2db.server.web.api.controller.rdb.request.UpdateSchemaRequest; import ai.chat2db.server.web.api.controller.rdb.vo.SchemaVO; import ai.chat2db.spi.model.Schema; +import ai.chat2db.spi.model.Sql; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -52,7 +55,7 @@ public class SchemaController { @GetMapping("/list") public ListResult list(@Valid DataSourceBaseRequest request) { SchemaQueryParam queryParam = SchemaQueryParam.builder().dataSourceId(request.getDataSourceId()).dataBaseName( - request.getDatabaseName()).refresh(request.isRefresh()).build(); + request.getDatabaseName()).refresh(request.isRefresh()).build(); ListResult tableColumns = databaseService.querySchema(queryParam); List tableVOS = rdbWebConverter.schemaDto2vo(tableColumns.getData()); return ListResult.of(tableVOS); @@ -67,7 +70,7 @@ public ListResult list(@Valid DataSourceBaseRequest request) { @PostMapping("/delete_schema") public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); + .schemaName(request.getSchemaName()).build(); return databaseService.deleteSchema(param); } @@ -77,11 +80,14 @@ public ActionResult deleteSchema(@Valid @RequestBody DataSourceBaseRequest reque * @param request * @return */ - @PostMapping("/create_schema") - public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest request) { - SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).build(); - return databaseService.createSchema(param); + @PostMapping("/create_schema_sql") + public DataResult createSchema(@Valid @RequestBody SchemaCreateRequest request) { + Schema schema = Schema.builder().databaseName(request.getDatabaseName()) + .name(request.getSchemaName()) + .owner(request.getOwner()) + .comment(request.getComment()) + .build(); + return databaseService.createSchema(schema); } /** @@ -93,7 +99,7 @@ public ActionResult createSchema(@Valid @RequestBody DataSourceBaseRequest reque @PostMapping("/modify_schema") public ActionResult modifySchema(@Valid @RequestBody UpdateSchemaRequest request) { SchemaOperationParam param = SchemaOperationParam.builder().databaseName(request.getDatabaseName()) - .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); + .schemaName(request.getSchemaName()).newSchemaName(request.getNewSchemaName()).build(); return databaseService.modifySchema(param); } } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DatabaseConverter.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DatabaseConverter.java new file mode 100644 index 000000000..b14e7c257 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/converter/DatabaseConverter.java @@ -0,0 +1,11 @@ +package ai.chat2db.server.web.api.controller.rdb.converter; + +import ai.chat2db.server.web.api.controller.rdb.request.DatabaseCreateRequest; +import ai.chat2db.spi.model.Database; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring") +public abstract class DatabaseConverter { + + public abstract Database request2param(DatabaseCreateRequest request); +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseCreateRequest.java new file mode 100644 index 000000000..95f4ebe73 --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/DatabaseCreateRequest.java @@ -0,0 +1,16 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import lombok.Data; + +@Data +public class DatabaseCreateRequest extends DataSourceBaseRequest { + + private String name; + + private String comment; + + private String charset; + + private String collation; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCreateRequest.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCreateRequest.java new file mode 100644 index 000000000..9ee25784d --- /dev/null +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/request/SchemaCreateRequest.java @@ -0,0 +1,21 @@ +package ai.chat2db.server.web.api.controller.rdb.request; + +import ai.chat2db.server.web.api.controller.data.source.request.DataSourceBaseRequest; +import com.fasterxml.jackson.annotation.JsonAlias; +import lombok.Data; + +@Data +public class SchemaCreateRequest extends DataSourceBaseRequest { + + /** + * 数据名字 + */ + @JsonAlias({"TABLE_SCHEM"}) + private String name; + + + private String comment; + + + private String owner; +} diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java deleted file mode 100644 index 70238a00d..000000000 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/system/AutomaticUpgrade.java +++ /dev/null @@ -1,42 +0,0 @@ -////package ai.chat2db.server.web.api.controller.system; -//// -////import ai.chat2db.server.tools.common.util.ConfigUtils; -////import ai.chat2db.server.web.api.controller.system.util.SystemUtils; -////import ai.chat2db.server.web.api.controller.system.vo.AppVersionVO; -////import lombok.extern.slf4j.Slf4j; -////import org.springframework.scheduling.annotation.Scheduled; -////import org.springframework.stereotype.Component; -//// -////@Slf4j -////@Component -////public class AutomaticUpgrade { -//// -//// @Scheduled(fixedRate = 3600000) // 每小时运行一次 -//// public void checkVersionUpdates() { -//// AppVersionVO appVersion = SystemUtils.getLatestVersion(ConfigUtils.getLocalVersion(), "auto", ""); -//// if (appVersion != null) { -//// SystemUtils.upgrade(appVersion); -//// } -//// } -////} -// -//const handleInsertText = (text, databaseCode: DatabaseTypeCode = DatabaseTypeCode.MYSQL) => { -// if ( -// [DatabaseTypeCode.POSTGRESQL, DatabaseTypeCode.ORACLE, DatabaseTypeCode.DB2, DatabaseTypeCode.SQLITE].includes( -// databaseCode, -// ) -// ) { -// return `\"${text}\"`; -// } else if ([DatabaseTypeCode.SQLSERVER].includes(databaseCode)) { -// return `[${text}]`; -// } else if ([DatabaseTypeCode.MYSQL].includes(databaseCode)) { -// return `\`${text}\``; -// } else { -// return `${text}`; -// } -// }; -// -//"" oracele sqlite postgrsql h2 dm -// ` MYSQL clickhouse MariaDB -// [ sqlserver -// diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java index bb31e1572..967ee086a 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/SqlBuilder.java @@ -1,5 +1,7 @@ package ai.chat2db.spi; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; public interface SqlBuilder { @@ -33,4 +35,38 @@ public interface SqlBuilder { * @return */ String pageLimit(String sql, int offset, int pageNo, int pageSize); + + + /** + * Generate create database sql + * @param database + * @return + */ + String buildCreateDatabaseSql(Database database); + + + /** + * + * @param oldDatabase + * @param newDatabase + * @return + */ + String buildModifyDatabaseSql(Database oldDatabase, Database newDatabase); + + + /** + * + * @param schemaName + * @return + */ + String buildCreateSchemaSql(Schema schemaName); + + + /** + * + * @param oldSchemaName + * @param newSchemaName + * @return + */ + String buildModifySchemaSql(String oldSchemaName, String newSchemaName); } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java index fee2c9246..e97f33102 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultDBManage.java @@ -21,6 +21,8 @@ */ public class DefaultDBManage implements DBManage { + + @Override public Connection getConnection(ConnectInfo connectInfo) { Connection connection = connectInfo.getConnection(); diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java index aad145174..dd957f729 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/jdbc/DefaultSqlBuilder.java @@ -1,10 +1,13 @@ package ai.chat2db.spi.jdbc; import ai.chat2db.spi.SqlBuilder; +import ai.chat2db.spi.model.Database; +import ai.chat2db.spi.model.Schema; import ai.chat2db.spi.model.Table; public class DefaultSqlBuilder implements SqlBuilder { + @Override public String buildCreateTableSql(Table table) { return null; @@ -19,4 +22,26 @@ public String buildModifyTaleSql(Table oldTable, Table newTable) { public String pageLimit(String sql, int offset, int pageNo, int pageSize) { return null; } + + public static String CREATE_DATABASE_SQL = "CREATE DATABASE IF NOT EXISTS `%s` DEFAULT CHARACTER SET %s COLLATE %s"; + + @Override + public String buildCreateDatabaseSql(Database database) { + return null; + } + + @Override + public String buildModifyDatabaseSql(Database oldDatabase, Database newDatabase) { + return null; + } + + @Override + public String buildCreateSchemaSql(Schema schema) { + return null; + } + + @Override + public String buildModifySchemaSql(String oldSchemaName, String newSchemaName) { + return null; + } } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java index 02e23ef1f..299b2cef9 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Database.java @@ -31,4 +31,11 @@ public class Database implements Serializable { * schema name */ private List schemas; + + + private String comment; + + private String charset; + + private String collation; } diff --git a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java index a54b927ac..b1b0d8711 100644 --- a/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java +++ b/chat2db-server/chat2db-spi/src/main/java/ai/chat2db/spi/model/Schema.java @@ -31,4 +31,10 @@ public class Schema implements Serializable { */ @JsonAlias({"TABLE_SCHEM"}) private String name; + + + private String comment; + + + private String owner; } \ No newline at end of file From 02dc35e6723304511e606a7324dc4b82495380d0 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 18:14:37 +0800 Subject: [PATCH 1043/1069] init: add database --- chat2db-client/.umirc.ts | 8 +- .../src/blocks/SQLExecute/index.tsx | 11 +- .../components/Console/MonacoEditor/index.tsx | 4 +- .../MonacoEditor/monacoEditorConfig.ts} | 0 .../src/components/CreateDatabase/index.less | 39 +++++ .../src/components/CreateDatabase/index.tsx | 158 ++++++++++++++++++ .../src/components/ExecuteSQL/index.less | 1 + .../Modal/TriggeredModal/index.less | 1 + .../components/Modal/TriggeredModal/index.tsx | 31 ++++ .../src/components/SearchResult/index.tsx | 21 ++- .../src/components/XXXX_FN/index.tsx | 10 +- chat2db-client/src/constants/index.ts | 1 - .../components/WorkspaceHeader/index.less | 15 ++ .../components/WorkspaceHeader/index.tsx | 82 ++++++++- .../components/WorkspaceRight/index.tsx | 2 +- chat2db-client/src/service/outside.ts | 7 - chat2db-client/src/service/sql.ts | 15 ++ chat2db-client/src/styles/var.less | 5 + 18 files changed, 380 insertions(+), 31 deletions(-) rename chat2db-client/src/{constants/monacoEditor.ts => components/Console/MonacoEditor/monacoEditorConfig.ts} (100%) create mode 100644 chat2db-client/src/components/CreateDatabase/index.less create mode 100644 chat2db-client/src/components/CreateDatabase/index.tsx create mode 100644 chat2db-client/src/components/Modal/TriggeredModal/index.less create mode 100644 chat2db-client/src/components/Modal/TriggeredModal/index.tsx diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 0ed67cc99..dc220436b 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -34,10 +34,10 @@ export default defineConfig({ plugins: ['@umijs/plugins/dist/dva'], chainWebpack, proxy: { - '/api': { - target: 'http://127.0.0.1:10821', - changeOrigin: true, - }, + // '/api': { + // target: 'http://127.0.0.1:10821', + // changeOrigin: true, + // }, '/client/remaininguses/': { target: 'http://127.0.0.1:1889', changeOrigin: true, diff --git a/chat2db-client/src/blocks/SQLExecute/index.tsx b/chat2db-client/src/blocks/SQLExecute/index.tsx index b23547c93..34581d2bf 100644 --- a/chat2db-client/src/blocks/SQLExecute/index.tsx +++ b/chat2db-client/src/blocks/SQLExecute/index.tsx @@ -4,7 +4,7 @@ import styles from './index.less'; import classnames from 'classnames'; import DraggableContainer from '@/components/DraggableContainer'; import Console, { IAppendValue } from '@/components/Console'; -import SearchResult from '@/components/SearchResult'; +import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; import { DatabaseTypeCode, ConsoleStatus } from '@/constants'; import { IWorkspaceModelState, IWorkspaceModelType } from '@/models/workspace'; import { IAIState } from '@/models/ai'; @@ -31,7 +31,8 @@ const SQLExecute = memo((props) => { const draggableRef = useRef(); const [appendValue, setAppendValue] = useState(); const { curTableList, curWorkspaceParams } = workspaceModel; - const [sql, setSql] = useState(''); + // const [sql, setSql] = useState(''); + const searchResultRef = useRef(null); // useEffect(() => { // if (!doubleClickTreeNodeData) { @@ -67,7 +68,9 @@ const SQLExecute = memo((props) => { executeParams={{ ...data }} hasAiChat={true} hasAi2Lang={true} - onExecuteSQL={setSql} + onExecuteSQL={(sql) => { + searchResultRef.current?.handleExecuteSQL(sql); + }} onConsoleSave={() => { dispatch({ type: 'workspace/fetchGetSavedConsole', @@ -89,7 +92,7 @@ const SQLExecute = memo((props) => { />
    - +
    diff --git a/chat2db-client/src/components/Console/MonacoEditor/index.tsx b/chat2db-client/src/components/Console/MonacoEditor/index.tsx index 296fc3c67..74cac02d3 100644 --- a/chat2db-client/src/components/Console/MonacoEditor/index.tsx +++ b/chat2db-client/src/components/Console/MonacoEditor/index.tsx @@ -2,7 +2,8 @@ import React, { ForwardedRef, forwardRef, useEffect, useImperativeHandle, useRef import cs from 'classnames'; import { useTheme } from '@/hooks'; import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import { DatabaseTypeCode, editorDefaultOptions, EditorThemeType, ThemeType } from '@/constants'; +import { DatabaseTypeCode, EditorThemeType, ThemeType } from '@/constants'; +import { editorDefaultOptions } from './monacoEditorConfig'; import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'; import styles from './index.less'; @@ -88,6 +89,7 @@ function MonacoEditor(props: IProps, ref: ForwardedRef) { // 'editorLineNumber.foreground': colorPrimary, // 行号颜色 'editorLineNumber.activeForeground': colorPrimary, // 当前行号颜色 // 'editorCursor.foreground': colorPrimary, // 光标颜色 + 'editorRuler.foreground': colorPrimary + '15', }; monaco.editor.defineTheme(ThemeType.Light, { base: 'vs', diff --git a/chat2db-client/src/constants/monacoEditor.ts b/chat2db-client/src/components/Console/MonacoEditor/monacoEditorConfig.ts similarity index 100% rename from chat2db-client/src/constants/monacoEditor.ts rename to chat2db-client/src/components/Console/MonacoEditor/monacoEditorConfig.ts diff --git a/chat2db-client/src/components/CreateDatabase/index.less b/chat2db-client/src/components/CreateDatabase/index.less new file mode 100644 index 000000000..09e2e250e --- /dev/null +++ b/chat2db-client/src/components/CreateDatabase/index.less @@ -0,0 +1,39 @@ +@import '../../styles/var.less'; + +.monacoEditorBox { + height: 200px; + border: 1px solid var(--color-border); + border-radius: 4px; + overflow: hidden; +} + +.errorBox { + margin-top: 10px; +} + +.previewBox { + // 伪元素画一条剧中的线条 + position: relative; + display: flex; + margin-bottom: 4px; + .previewText { + background: var(--color-bg-base); + flex-shrink: 0; + margin-right: 10px; + } + .previewLine { + flex: 1; + position: relative; + &::after { + position: absolute; + left: 0; + right: 0px; + top: 50%; + content: ''; + width: 100%; + height: 1px; + background: var(--color-border); + transform: translateY(-50%); + } + } +} diff --git a/chat2db-client/src/components/CreateDatabase/index.tsx b/chat2db-client/src/components/CreateDatabase/index.tsx new file mode 100644 index 000000000..dafbc6079 --- /dev/null +++ b/chat2db-client/src/components/CreateDatabase/index.tsx @@ -0,0 +1,158 @@ +import React, { forwardRef, ForwardedRef, useImperativeHandle, useMemo, useState, useEffect } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { Form, Input, Modal } from 'antd'; +import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; +import { v4 as uuid } from 'uuid'; +import sqlService from '@/service/sql'; + +interface IProps { + className?: string; + curWorkspaceParams: any; + executedCallback?: () => void; +} + +type CreateType = 'database' | 'datasource'; + +export interface ICreateDatabaseRef { + setOpen: (open: boolean, type?: CreateType) => void; +} + +export interface ICreateDatabase { + databaseName: string; + comment?: string; +} + +export default forwardRef((props: IProps, ref: ForwardedRef) => { + const { className, curWorkspaceParams, executedCallback } = props; + const [form] = Form.useForm(); + const monacoEditorUuid = useMemo(() => uuid(), []); + const monacoEditorRef = React.useRef(null); + const [open, setOpen] = useState(false); + const [errorMessage, setErrorMessage] = useState<{ success: boolean; message: string; originalSql: string } | null>( + null, + ); + const [confirmLoading, setConfirmLoading] = useState(false); + const [createType, setCreateType] = useState('database'); + + useEffect(() => { + if (!open) { + setErrorMessage(null); + form.resetFields(); + monacoEditorRef.current?.setValue('', 'cover'); + } + }, [open]); + + const config = useMemo(() => { + return createType === 'database' + ? { + title: 'Create Database', + api: sqlService.getCreateDatabaseSql, + formName: 'databaseName', + } + : { + title: 'Create Schema', + api: sqlService.getCreateSchemaSql, + formName: 'schemaName', + }; + }, [createType]); + + const exposedSetOpen = (_open: boolean, type?: CreateType) => { + setOpen(_open); + setCreateType(type || 'database'); + }; + + useImperativeHandle(ref, () => ({ + setOpen: exposedSetOpen, + })); + + const labelCol = { flex: '70px' }; + + const handleFieldsChange = () => { + const params = { + databaseType: curWorkspaceParams.databaseType, + dataSourceId: curWorkspaceParams.dataSourceId, + databaseName: curWorkspaceParams.databaseName, + ...form.getFieldsValue(), + }; + config.api(params).then((res) => { + const { sql } = res; + monacoEditorRef.current?.setValue(sql, 'cover'); + }); + }; + + const executeUpdateDataSql = (sql: string) => { + const params: any = { + dataSourceId: curWorkspaceParams.dataSourceId, + databaseType: curWorkspaceParams.databaseType, + databaseName: curWorkspaceParams.databaseName, + sql, + }; + setConfirmLoading(true); + setErrorMessage(null); + return sqlService + .executeDDL(params) + .then((res) => { + if (res.success) { + setOpen(false); + executedCallback(); + } else { + setErrorMessage(res); + } + }) + .finally(() => { + setConfirmLoading(false); + }); + }; + + const onOk = () => { + const sql = monacoEditorRef.current?.getAllContent() || ''; + executeUpdateDataSql(sql); + }; + + return ( + { + setOpen(false); + }} + title={config.title} + destroyOnClose + confirmLoading={confirmLoading} + open={open} + onOk={onOk} + > +
    + + + + + + + + +
    +
    Preview
    +
    +
    +
    + +
    + {errorMessage && ( + <> +
    +
    Error
    +
    +
    +
    {errorMessage.message}
    + + )} +
    + + ); +}); diff --git a/chat2db-client/src/components/ExecuteSQL/index.less b/chat2db-client/src/components/ExecuteSQL/index.less index dbb6c1703..8a513bba2 100644 --- a/chat2db-client/src/components/ExecuteSQL/index.less +++ b/chat2db-client/src/components/ExecuteSQL/index.less @@ -85,6 +85,7 @@ padding: 4px 10px; background-color: var(--color-bg-subtle); border-radius: 8px; + .f-doc-en-break(); } } } diff --git a/chat2db-client/src/components/Modal/TriggeredModal/index.less b/chat2db-client/src/components/Modal/TriggeredModal/index.less new file mode 100644 index 000000000..b514cfb03 --- /dev/null +++ b/chat2db-client/src/components/Modal/TriggeredModal/index.less @@ -0,0 +1 @@ +@import '../../styles/var.less'; diff --git a/chat2db-client/src/components/Modal/TriggeredModal/index.tsx b/chat2db-client/src/components/Modal/TriggeredModal/index.tsx new file mode 100644 index 000000000..e41774c38 --- /dev/null +++ b/chat2db-client/src/components/Modal/TriggeredModal/index.tsx @@ -0,0 +1,31 @@ +import React, { memo, Fragment, ReactElement, cloneElement } from 'react'; +import { Modal } from 'antd'; + +interface ITriggeredModal extends React.ComponentProps { + children: ReactElement; + modalContent: React.ReactNode; + onOk?: any; +} + +const TriggeredModal = memo((props) => { + const { children, modalContent, ...orgs } = props; + const [open, setOpen] = React.useState(false); + const onClose = () => { + setOpen(false); + }; + + const onOk = () => { + orgs?.onOk?.(setOpen); + }; + + return ( + + {cloneElement(children, { onClick: () => setOpen(true) })} + + {modalContent} + + + ); +}); + +export default TriggeredModal; diff --git a/chat2db-client/src/components/SearchResult/index.tsx b/chat2db-client/src/components/SearchResult/index.tsx index 692429bac..36fb8246b 100644 --- a/chat2db-client/src/components/SearchResult/index.tsx +++ b/chat2db-client/src/components/SearchResult/index.tsx @@ -1,4 +1,13 @@ -import React, { memo, useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import React, { + useCallback, + useEffect, + useMemo, + useState, + useRef, + forwardRef, + ForwardedRef, + useImperativeHandle, +} from 'react'; import classnames from 'classnames'; import Tabs from '@/components/Tabs'; import Iconfont from '@/components/Iconfont'; @@ -27,7 +36,11 @@ const defaultResultConfig: IResultConfig = { hasNextPage: true, }; -export default memo((props) => { +export interface ISearchResultRef { + handleExecuteSQL: (sql: string) => void; +} + +export default forwardRef((props: IProps, ref: ForwardedRef) => { const { className, sql, executeSqlParams } = props; // const [currentTab, setCurrentTab] = useState(); const [resultDataList, setResultDataList] = useState(); @@ -40,6 +53,10 @@ export default memo((props) => { } }, [sql]); + useImperativeHandle(ref, () => ({ + handleExecuteSQL, + })); + /** * 执行SQL * @param sql diff --git a/chat2db-client/src/components/XXXX_FN/index.tsx b/chat2db-client/src/components/XXXX_FN/index.tsx index ff47777ba..42d905700 100644 --- a/chat2db-client/src/components/XXXX_FN/index.tsx +++ b/chat2db-client/src/components/XXXX_FN/index.tsx @@ -6,9 +6,7 @@ interface IProps { className?: string; } -export default memo(function XXXX_FN(props) { - const { className } = props - return
    - -
    -}) +export default memo((props) => { + const { className } = props; + return
    demo
    ; +}); diff --git a/chat2db-client/src/constants/index.ts b/chat2db-client/src/constants/index.ts index 2cae293d0..9c4c0b0ec 100644 --- a/chat2db-client/src/constants/index.ts +++ b/chat2db-client/src/constants/index.ts @@ -2,7 +2,6 @@ export * from './appConfig'; export * from './common'; export * from './database'; export * from './environment'; -export * from './monacoEditor'; export * from './table'; export * from './theme'; export * from './tree'; diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less index 56d9b6fbc..17071ec86 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -119,3 +119,18 @@ } } } + +.dropdownFooter { + display: flex; + align-items: center; + margin: 4px; + padding: 4px 8px; + line-height: 20px; + i { + margin-right: 4px; + } + &:hover { + background-color: var(--color-hover-bg); + cursor: pointer; + } +} 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 93e016b68..0e9c558ba 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -6,12 +6,13 @@ import Iconfont from '@/components/Iconfont'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; -import { Cascader, Spin, Modal, Tag } from 'antd'; +import { Cascader, Spin, Modal, Tag, Divider } from 'antd'; import { databaseMap, TreeNodeType } from '@/constants'; import { treeConfig } from '../Tree/treeConfig'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import styles from './index.less'; import i18n from '@/i18n'; +import CreateDatabase, { ICreateDatabaseRef } from '@/components/CreateDatabase'; interface IProps { className?: string; @@ -37,6 +38,21 @@ const WorkspaceHeader = memo((props) => { const [curDBOptions, setCurDBOptions] = useState([]); const [curSchemaOptions, setCurSchemaOptions] = useState([]); const [isRefresh, setIsRefresh] = useState(false); + const databaseNameRef = React.useRef(null); + const [openDBCascaderDropdown, setOpenDBCascaderDropdown] = useState(false); + const [openSchemaCascaderDropdown, setOpenSchemaCascaderDropdown] = useState(false); + const createDatabaseRef = React.useRef(null); + + useEffect(() => { + const handleClick = () => { + setOpenDBCascaderDropdown(false); + setOpenSchemaCascaderDropdown(false); + }; + document.addEventListener('click', handleClick); + return () => { + document.removeEventListener('click', handleClick); + }; + }, [databaseNameRef.current]); useEffect(() => { if (curPage !== 'workspace') { @@ -134,7 +150,7 @@ const WorkspaceHeader = memo((props) => { : curWorkspaceParams.databaseName || dbList[0]?.label; getSchemaList(databaseName, refresh); }) - .catch((error) => { + .catch(() => { setCascaderLoading(false); }); } @@ -265,16 +281,41 @@ const WorkspaceHeader = memo((props) => { {!!curDBOptions?.length && } - {!!curDBOptions?.length && ( { + return ( +
    + {menu} + +
    { + createDatabaseRef.current?.setOpen(true); + }} + > + + 添加数据库 +
    +
    + ); + }} onChange={databaseChange} bordered={false} value={[curWorkspaceParams?.databaseName || '']} > -
    +
    { + // 这里我拿不到这个div的ref,无法在document的的点击事件里过滤掉 ,所以用了setTimeout + setTimeout(() => { + setOpenDBCascaderDropdown(!openDBCascaderDropdown); + }, 0); + }} + >
    {curWorkspaceParams.databaseName}
    @@ -286,9 +327,35 @@ const WorkspaceHeader = memo((props) => { options={curSchemaOptions} onChange={schemaChange} bordered={false} + open={openSchemaCascaderDropdown} value={[curWorkspaceParams?.schemaName || '']} + dropdownRender={(menu) => { + return ( +
    + {menu} + +
    { + createDatabaseRef.current?.setOpen(true, 'datasource'); + }} + > + + 添加Schema +
    +
    + ); + }} > -
    +
    { + // 这里我拿不到这个div的ref,无法在document的的点击事件里过滤掉 ,所以用了setTimeout + setTimeout(() => { + setOpenSchemaCascaderDropdown(!openDBCascaderDropdown); + }, 0); + }} + >
    {curWorkspaceParams.schemaName}
    @@ -330,6 +397,11 @@ const WorkspaceHeader = memo((props) => {
    + ); }); 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 617aaa05f..1d9e3f337 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -8,7 +8,7 @@ import historyService from '@/service/history'; import sqlService from '@/service/sql'; import Tabs, { ITabItem } from '@/components/Tabs'; // import WorkspaceExtend from '../WorkspaceExtend'; -import SearchResult from '@/components/SearchResult'; +import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; diff --git a/chat2db-client/src/service/outside.ts b/chat2db-client/src/service/outside.ts index b22973f49..b5d414656 100644 --- a/chat2db-client/src/service/outside.ts +++ b/chat2db-client/src/service/outside.ts @@ -1,10 +1,4 @@ import createRequest from './base'; -import { IVersionResponse } from '@/typings'; - -const checkVersion = createRequest('/api/client/version/check/v2', { - errorLevel: false, - outside: true, -}); const dynamicUrl = createRequest('', { dynamicUrl: true, @@ -12,5 +6,4 @@ const dynamicUrl = createRequest('', { export default { dynamicUrl, - checkVersion, }; diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index 7a930ef0e..acbe113fa 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -288,7 +288,22 @@ const executeUpdateDataSql = createRequest('/api/rdb/dml/get_update_sql', { method: 'post' }); +/** 创建数据库 */ +const getCreateDatabaseSql = createRequest<{ + dataSourceId: number; + databaseName: string; +}, { sql: string }>('/api/rdb/database/create_database_sql', { method: 'post' }); + +/** 创建schema */ +const getCreateSchemaSql = createRequest<{ + dataSourceId: number; + databaseName?: string; + schemaName: string; +}, {sql:string}>('/api/rdb/schema/create_schema_sql', { method: 'post' }); + export default { + getCreateSchemaSql, + getCreateDatabaseSql, executeUpdateDataSql, executeDDL, getExecuteUpdateSql, diff --git a/chat2db-client/src/styles/var.less b/chat2db-client/src/styles/var.less index 3efe4d690..db46dd54d 100644 --- a/chat2db-client/src/styles/var.less +++ b/chat2db-client/src/styles/var.less @@ -51,6 +51,11 @@ } } +// 文档英文强制换行 +.f-doc-en-break { + word-break: break-all; +} + @keyframes loading-animation { 0% { transform: rotate(0deg); From 696c9cdce7bb43ade9d0908ae5ebd6f1a470b8ba Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 19:54:51 +0800 Subject: [PATCH 1044/1069] recove .umirc --- chat2db-client/.umirc.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index dc220436b..0ed67cc99 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -34,10 +34,10 @@ export default defineConfig({ plugins: ['@umijs/plugins/dist/dva'], chainWebpack, proxy: { - // '/api': { - // target: 'http://127.0.0.1:10821', - // changeOrigin: true, - // }, + '/api': { + target: 'http://127.0.0.1:10821', + changeOrigin: true, + }, '/client/remaininguses/': { target: 'http://127.0.0.1:1889', changeOrigin: true, From 311fc50dab9009b60fabb4e0930ee0645a952de7 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 23 Oct 2023 20:17:47 +0800 Subject: [PATCH 1045/1069] Support for creating database and schema --- .../java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bc1e2d510..c2cc32c0f 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 @@ -129,7 +129,7 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("CREATE DATABASE "+database.getName()); + sqlBuilder.append("CREATE DATABASE `"+database.getName()+"`"); if (StringUtils.isNotBlank(database.getCharset())) { sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); } From 6c2e82fd64c9c2ada824eb19799a89e1a3c05dcc Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 20:24:50 +0800 Subject: [PATCH 1046/1069] i18n: create database --- .../blocks/Setting/UpdateDetection/index.tsx | 3 +- .../src/components/CreateDatabase/index.tsx | 13 ++--- chat2db-client/src/i18n/en-us/common.ts | 8 ++- chat2db-client/src/i18n/zh-cn/common.ts | 6 +++ .../components/WorkspaceHeader/index.tsx | 54 ++++++++++++------- 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx index eb31c04b1..b65836a2c 100644 --- a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx +++ b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx @@ -40,10 +40,9 @@ const UpdateDetection = memo( const { openSettingModal, updateDetectionData, setUpdateDetectionData } = props; const [notificationApi, notificationDom] = notification.useNotification(); const timesRef = React.useRef(0); - console.log(updateDetectionData); useEffect(() => { - checkUpdate(); + // checkUpdate(); }, []); const close = () => {}; diff --git a/chat2db-client/src/components/CreateDatabase/index.tsx b/chat2db-client/src/components/CreateDatabase/index.tsx index dafbc6079..af1e9a378 100644 --- a/chat2db-client/src/components/CreateDatabase/index.tsx +++ b/chat2db-client/src/components/CreateDatabase/index.tsx @@ -5,6 +5,7 @@ import { Form, Input, Modal } from 'antd'; import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEditor'; import { v4 as uuid } from 'uuid'; import sqlService from '@/service/sql'; +import i18n from '@/i18n'; interface IProps { className?: string; @@ -12,7 +13,7 @@ interface IProps { executedCallback?: () => void; } -type CreateType = 'database' | 'datasource'; +export type CreateType = 'database' | 'schema'; export interface ICreateDatabaseRef { setOpen: (open: boolean, type?: CreateType) => void; @@ -95,7 +96,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) .then((res) => { if (res.success) { setOpen(false); - executedCallback(); + executedCallback?.(); } else { setErrorMessage(res); } @@ -123,15 +124,15 @@ export default forwardRef((props: IProps, ref: ForwardedRef) >
    - + - +
    -
    Preview
    +
    {i18n('common.title.preview')}
    @@ -146,7 +147,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) {errorMessage && ( <>
    -
    Error
    +
    {i18n('common.title.errorMessage')}
    {errorMessage.message}
    diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index fd1387383..b4ede058d 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -93,5 +93,11 @@ export default { 'common.text.affectedRows': 'Affected rows: {1}', 'common.text.selectFile' : 'Select File', 'common.text.noTableFoundUp' : 'No tables in this database', - 'common.text.noTableFoundDown' : 'Switch databases at the top' + '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', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index fb519096e..20baec39a 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -93,4 +93,10 @@ export default { 'common.text.noTableFoundUp' : '当前库没有查询到表', 'common.text.noTableFoundDown' : '你可以在顶部切换数据库', 'common.text.updateNow' : '立即更新', + 'common.title.preview' : '预览', + 'common.title.errorMessage': '错误信息', + 'common.Button.addDatabase': '添加数据库', + 'common.Button.addSchema': '添加Schema', + 'common.label.comment': '备注', + 'common.label.name': '名称', }; 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 0e9c558ba..6b7e2ff27 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -7,7 +7,7 @@ import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; import { Cascader, Spin, Modal, Tag, Divider } from 'antd'; -import { databaseMap, TreeNodeType } from '@/constants'; +import { databaseMap, TreeNodeType, DatabaseTypeCode } from '@/constants'; import { treeConfig } from '../Tree/treeConfig'; import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import styles from './index.less'; @@ -27,6 +27,12 @@ interface IOption { value: number | string; } +// 不支持创建数据库的数据库类型 +const notSupportCreateDatabaseType = [DatabaseTypeCode.H2]; + +// 不支持创建schema的数据库类型 +const notSupportCreateSchemaType = [DatabaseTypeCode.ORACLE]; + const WorkspaceHeader = memo((props) => { const { connectionModel, workspaceModel, mainPageModel, dispatch } = props; const { connectionList, curConnection } = connectionModel; @@ -291,15 +297,20 @@ const WorkspaceHeader = memo((props) => {
    {menu} -
    { - createDatabaseRef.current?.setOpen(true); - }} - > - - 添加数据库 -
    + { + // 不支持创建数据库的数据库类型 + !notSupportCreateDatabaseType.includes(curWorkspaceParams?.databaseType) && ( +
    { + createDatabaseRef.current?.setOpen(true, 'database'); + }} + > + + {i18n('common.Button.addDatabase')} +
    + ) + }
    ); }} @@ -334,15 +345,20 @@ const WorkspaceHeader = memo((props) => {
    {menu} -
    { - createDatabaseRef.current?.setOpen(true, 'datasource'); - }} - > - - 添加Schema -
    + { + // 不支持创建schema的数据库类型 + !notSupportCreateSchemaType.includes(curWorkspaceParams?.databaseType) && ( +
    { + createDatabaseRef.current?.setOpen(true, 'schema'); + }} + > + + {i18n('common.Button.addSchema')} +
    + ) + }
    ); }} From 7c15b83fe03a49da390506129ba1f1b0205aad8e Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 21:34:31 +0800 Subject: [PATCH 1047/1069] feat: create database debounce --- .../blocks/Setting/UpdateDetection/index.tsx | 2 +- .../src/components/CreateDatabase/index.tsx | 46 ++++++++++------- chat2db-client/src/i18n/en-us/common.ts | 1 + chat2db-client/src/i18n/zh-cn/common.ts | 1 + .../components/WorkspaceHeader/index.tsx | 51 +++++++------------ chat2db-client/src/service/sql.ts | 2 +- 6 files changed, 50 insertions(+), 53 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx index b65836a2c..945ce48df 100644 --- a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx +++ b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx @@ -42,7 +42,7 @@ const UpdateDetection = memo( const timesRef = React.useRef(0); useEffect(() => { - // checkUpdate(); + checkUpdate(); }, []); const close = () => {}; diff --git a/chat2db-client/src/components/CreateDatabase/index.tsx b/chat2db-client/src/components/CreateDatabase/index.tsx index af1e9a378..4774cc76d 100644 --- a/chat2db-client/src/components/CreateDatabase/index.tsx +++ b/chat2db-client/src/components/CreateDatabase/index.tsx @@ -1,4 +1,4 @@ -import React, { forwardRef, ForwardedRef, useImperativeHandle, useMemo, useState, useEffect } from 'react'; +import React, { useCallback, forwardRef, ForwardedRef, useImperativeHandle, useMemo, useState, useEffect } from 'react'; import styles from './index.less'; import classnames from 'classnames'; import { Form, Input, Modal } from 'antd'; @@ -6,6 +6,7 @@ import MonacoEditor, { IExportRefFunction } from '@/components/Console/MonacoEdi import { v4 as uuid } from 'uuid'; import sqlService from '@/service/sql'; import i18n from '@/i18n'; +import { debounce } from 'lodash'; interface IProps { className?: string; @@ -20,13 +21,14 @@ export interface ICreateDatabaseRef { } export interface ICreateDatabase { - databaseName: string; + databaseName?: string; + schemaName?: string; comment?: string; } export default forwardRef((props: IProps, ref: ForwardedRef) => { const { className, curWorkspaceParams, executedCallback } = props; - const [form] = Form.useForm(); + const [form] = Form.useForm(); const monacoEditorUuid = useMemo(() => uuid(), []); const monacoEditorRef = React.useRef(null); const [open, setOpen] = useState(false); @@ -47,12 +49,12 @@ export default forwardRef((props: IProps, ref: ForwardedRef) const config = useMemo(() => { return createType === 'database' ? { - title: 'Create Database', + title: `${i18n('common.title.create')} Database`, api: sqlService.getCreateDatabaseSql, formName: 'databaseName', } : { - title: 'Create Schema', + title: `${i18n('common.title.create')} Schema`, api: sqlService.getCreateSchemaSql, formName: 'schemaName', }; @@ -69,18 +71,28 @@ export default forwardRef((props: IProps, ref: ForwardedRef) const labelCol = { flex: '70px' }; - const handleFieldsChange = () => { - const params = { - databaseType: curWorkspaceParams.databaseType, - dataSourceId: curWorkspaceParams.dataSourceId, - databaseName: curWorkspaceParams.databaseName, - ...form.getFieldsValue(), - }; - config.api(params).then((res) => { - const { sql } = res; - monacoEditorRef.current?.setValue(sql, 'cover'); - }); - }; + const handleFieldsChange = useCallback( + debounce(() => { + const formData: ICreateDatabase = form.getFieldsValue(); + if (!formData.databaseName && createType === 'database') { + return; + } + if (!formData.schemaName && createType === 'schema') { + return; + } + const params = { + databaseType: curWorkspaceParams.databaseType, + dataSourceId: curWorkspaceParams.dataSourceId, + databaseName: curWorkspaceParams.databaseName, + ...formData, + }; + config.api(params).then((res) => { + const { sql } = res; + monacoEditorRef.current?.setValue(sql, 'cover'); + }); + }, 500), + [], + ); const executeUpdateDataSql = (sql: string) => { const params: any = { diff --git a/chat2db-client/src/i18n/en-us/common.ts b/chat2db-client/src/i18n/en-us/common.ts index b4ede058d..b7570ab76 100644 --- a/chat2db-client/src/i18n/en-us/common.ts +++ b/chat2db-client/src/i18n/en-us/common.ts @@ -100,4 +100,5 @@ export default { 'common.Button.addSchema': 'Add schema', 'common.label.comment': 'Comment', 'common.label.name': 'Name', + 'common.title.create': 'Create', }; diff --git a/chat2db-client/src/i18n/zh-cn/common.ts b/chat2db-client/src/i18n/zh-cn/common.ts index 20baec39a..4c55168ac 100644 --- a/chat2db-client/src/i18n/zh-cn/common.ts +++ b/chat2db-client/src/i18n/zh-cn/common.ts @@ -99,4 +99,5 @@ export default { 'common.Button.addSchema': '添加Schema', 'common.label.comment': '备注', 'common.label.name': '名称', + 'common.title.create': '创建', }; 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 6b7e2ff27..9f659d504 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -44,21 +44,18 @@ const WorkspaceHeader = memo((props) => { const [curDBOptions, setCurDBOptions] = useState([]); const [curSchemaOptions, setCurSchemaOptions] = useState([]); const [isRefresh, setIsRefresh] = useState(false); - const databaseNameRef = React.useRef(null); - const [openDBCascaderDropdown, setOpenDBCascaderDropdown] = useState(false); - const [openSchemaCascaderDropdown, setOpenSchemaCascaderDropdown] = useState(false); + const [openDBCascaderDropdown, setOpenDBCascaderDropdown] = useState(undefined); + const [openSchemaCascaderDropdown, setOpenSchemaCascaderDropdown] = useState(undefined); const createDatabaseRef = React.useRef(null); useEffect(() => { - const handleClick = () => { - setOpenDBCascaderDropdown(false); - setOpenSchemaCascaderDropdown(false); - }; - document.addEventListener('click', handleClick); - return () => { - document.removeEventListener('click', handleClick); - }; - }, [databaseNameRef.current]); + if (openDBCascaderDropdown === false) { + setOpenDBCascaderDropdown(undefined); + } + if (openSchemaCascaderDropdown === false) { + setOpenSchemaCascaderDropdown(undefined); + } + }, [openDBCascaderDropdown, openSchemaCascaderDropdown]); useEffect(() => { if (curPage !== 'workspace') { @@ -66,7 +63,7 @@ const WorkspaceHeader = memo((props) => { } // 如果没有curConnection默认选第一个 if (!curConnection?.id && connectionList.length) { - connectionChange([connectionList[0].id], [connectionList[0]]); + connectionChange([connectionList[0].id]); return; } // 如果都有的话 @@ -74,7 +71,7 @@ const WorkspaceHeader = memo((props) => { // 如果curConnection不再connectionList里,也是默认选第一个 const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id); if (flag === -1) { - connectionChange([connectionList[0].id], [connectionList[0]]); + connectionChange([connectionList[0].id]); return; } @@ -140,7 +137,7 @@ const WorkspaceHeader = memo((props) => { dataSourceName: curConnection.name, }, }) - .then((res) => { + .then((res: any) => { const dbList = res?.map((t) => { return { @@ -177,7 +174,7 @@ const WorkspaceHeader = memo((props) => { dataSourceName: curConnection.name, }, }) - .then((res) => { + .then((res: any) => { const schemaList = res?.map((t) => { return { @@ -303,6 +300,7 @@ const WorkspaceHeader = memo((props) => {
    { + setOpenDBCascaderDropdown(false); createDatabaseRef.current?.setOpen(true, 'database'); }} > @@ -318,15 +316,7 @@ const WorkspaceHeader = memo((props) => { bordered={false} value={[curWorkspaceParams?.databaseName || '']} > -
    { - // 这里我拿不到这个div的ref,无法在document的的点击事件里过滤掉 ,所以用了setTimeout - setTimeout(() => { - setOpenDBCascaderDropdown(!openDBCascaderDropdown); - }, 0); - }} - > +
    {curWorkspaceParams.databaseName}
    @@ -351,6 +341,7 @@ const WorkspaceHeader = memo((props) => {
    { + setOpenDBCascaderDropdown(false); createDatabaseRef.current?.setOpen(true, 'schema'); }} > @@ -363,15 +354,7 @@ const WorkspaceHeader = memo((props) => { ); }} > -
    { - // 这里我拿不到这个div的ref,无法在document的的点击事件里过滤掉 ,所以用了setTimeout - setTimeout(() => { - setOpenSchemaCascaderDropdown(!openDBCascaderDropdown); - }, 0); - }} - > +
    {curWorkspaceParams.schemaName}
    diff --git a/chat2db-client/src/service/sql.ts b/chat2db-client/src/service/sql.ts index acbe113fa..8754246fa 100644 --- a/chat2db-client/src/service/sql.ts +++ b/chat2db-client/src/service/sql.ts @@ -298,7 +298,7 @@ const getCreateDatabaseSql = createRequest<{ const getCreateSchemaSql = createRequest<{ dataSourceId: number; databaseName?: string; - schemaName: string; + schemaName?: string; }, {sql:string}>('/api/rdb/schema/create_schema_sql', { method: 'post' }); export default { From 1303b7814eda55fecadbab86cde36a72a4d4f4f8 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 21:38:34 +0800 Subject: [PATCH 1048/1069] merge --- .../java/ai/chat2db/plugin/mysql/builder/MysqlSqlBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 bc1e2d510..c2cc32c0f 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 @@ -129,7 +129,7 @@ public String pageLimit(String sql, int offset, int pageNo, int pageSize) { @Override public String buildCreateDatabaseSql(Database database) { StringBuilder sqlBuilder = new StringBuilder(); - sqlBuilder.append("CREATE DATABASE "+database.getName()); + sqlBuilder.append("CREATE DATABASE `"+database.getName()+"`"); if (StringUtils.isNotBlank(database.getCharset())) { sqlBuilder.append(" DEFAULT CHARACTER SET=").append(database.getCharset()); } From 441826c89ff516d866d2296e62690bea92fb441e Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Mon, 23 Oct 2023 22:23:46 +0800 Subject: [PATCH 1049/1069] Support for creating database and schema --- .../chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java index ccf671c4b..54978d456 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/builder/SqlServerSqlBuilder.java @@ -153,7 +153,7 @@ public String buildCreateDatabaseSql(Database database) { sqlBuilder.append("\ngo\n"); if (StringUtils.isNotBlank(database.getComment())) { sqlBuilder.append("exec [" + database.getName() + "].sys. sp_addextendedproperty 'MS_Description','") - .append(database.getComment()).append("'").append("'\ngo\n"); + .append(database.getComment()).append("'").append("\ngo\n"); } return sqlBuilder.toString(); } @@ -166,7 +166,7 @@ public String buildCreateSchemaSql(Schema schema) { if (StringUtils.isNotBlank(schema.getComment())) { sqlBuilder.append("exec sp_addextendedproperty 'MS_Description','") .append(schema.getComment()).append("'").append(",'SCHEMA'") - .append(",'").append(schema.getName()).append("'").append("'\ngo\n"); + .append(",'").append(schema.getName()).append("'").append("\ngo\n"); } return sqlBuilder.toString(); } From f60ad28e7e7f77bebf1248d8365bf3fb4cf0b015 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Mon, 23 Oct 2023 22:25:48 +0800 Subject: [PATCH 1050/1069] fix: fix repeat redirects --- .../server/start/config/config/Chat2dbWebMvcConfigurer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java index 2f3a68a42..08ad4a1ac 100644 --- a/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java +++ b/chat2db-server/chat2db-server-start/src/main/java/ai/chat2db/server/start/config/config/Chat2dbWebMvcConfigurer.java @@ -56,7 +56,7 @@ public class Chat2dbWebMvcConfigurer implements WebMvcConfigurer { * 全局放行的url */ private static final String[] FRONT_PERMIT_ALL = new String[] {"/favicon.ico", "/error", "/static/**", - "/api/system", "/login"}; + "/api/system", "/login", "/api/system/get_latest_version"}; @Resource private UserService userService; From 6b9859d3823a25d466e1e8e21ec69ed468a168e2 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 23:11:51 +0800 Subject: [PATCH 1051/1069] fix:bug --- .../src/components/CreateDatabase/index.tsx | 14 ++++++++++---- chat2db-client/src/main/index.js | 11 ++++++----- chat2db-client/src/main/preload.js | 7 +++---- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/chat2db-client/src/components/CreateDatabase/index.tsx b/chat2db-client/src/components/CreateDatabase/index.tsx index 4774cc76d..665d5b57f 100644 --- a/chat2db-client/src/components/CreateDatabase/index.tsx +++ b/chat2db-client/src/components/CreateDatabase/index.tsx @@ -7,6 +7,7 @@ import { v4 as uuid } from 'uuid'; import sqlService from '@/service/sql'; import i18n from '@/i18n'; import { debounce } from 'lodash'; +import { DatabaseTypeCode } from '@/constants'; interface IProps { className?: string; @@ -26,6 +27,9 @@ export interface ICreateDatabase { comment?: string; } +// 创建database不支持注释的数据库 +const noCommentDatabase = [DatabaseTypeCode.MYSQL]; + export default forwardRef((props: IProps, ref: ForwardedRef) => { const { className, curWorkspaceParams, executedCallback } = props; const [form] = Form.useForm(); @@ -91,7 +95,7 @@ export default forwardRef((props: IProps, ref: ForwardedRef) monacoEditorRef.current?.setValue(sql, 'cover'); }); }, 500), - [], + [curWorkspaceParams, createType, monacoEditorRef, config], ); const executeUpdateDataSql = (sql: string) => { @@ -139,9 +143,11 @@ export default forwardRef((props: IProps, ref: ForwardedRef) - - - + {noCommentDatabase.includes(curWorkspaceParams.databaseType) ? null : ( + + + + )}
    {i18n('common.title.preview')}
    diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index 91fa470ce..e6e8ac7b4 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -31,7 +31,7 @@ function createWindow() { // 关闭window时触发下列事件. mainWindow.on('closed', function (event) { event.preventDefault(); - mainWindow = null; + // mainWindow = null; }); // 监听打开新窗口事件 用默认浏览器打开 @@ -56,9 +56,9 @@ app.on('ready', () => { registerAnalysis(); app.on('activate', function () { - if (mainWindow === null) { - createWindow(); - } + // if (mainWindow === null) { + // } + createWindow(); }); }); @@ -69,7 +69,8 @@ app.on('window-all-closed', () => { }); app.on('before-quit', () => { - mainWindow.webContents.send('before-quit-app'); + // 退出应用前触发before-quit-app + // mainWindow.webContents.send('before-quit-app'); }); ipcMain.handle('get-product-name', () => { diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index 094133596..1f801d342 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -43,13 +43,12 @@ contextBridge.exposeInMainWorld('electronApi', { quitApp: () => { ipcRenderer.send('quit-app'); }, - beforeQuitApp: (callback)=>{ + beforeQuitApp: (callback) => { ipcRenderer.on('before-quit-app', () => { callback(); }); }, - registerAppMenu: (menuProps)=>{ + registerAppMenu: (menuProps) => { ipcRenderer.send('register-app-menu', menuProps); - } + }, }); - From d83d2845b35a87f2faf502784f25e98e3e3737bb Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 23:18:34 +0800 Subject: [PATCH 1052/1069] fix:table_list report an error --- .../pages/main/workspace/components/WorkspaceRight/index.tsx | 3 +++ 1 file changed, 3 insertions(+) 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 1d9e3f337..4ccd1e2c7 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -355,6 +355,9 @@ const WorkspaceRight = memo((props: IProps) => { // 更新表名提示 useUpdateEffect(() => { const { dataSourceId, databaseName, schemaName, databaseType } = curWorkspaceParams; + if (dataSourceId === null || dataSourceId === undefined) { + return; + } sqlService .getAllTableList({ dataSourceId, From 2eeef436dae59a0fdaf124b1302525667d2cdea9 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 23:31:14 +0800 Subject: [PATCH 1053/1069] Updata:Changelog --- CHANGELOG.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b92bd1047..8b2425855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# 3.0.5 +`2023-10-23` +**Changelog** +- ⭐【New Features】Supports visual database creation +- ⭐【New Features】Support hot update +- ⭐【New Features】Double-click the table to open it directly +- ⚡️【Optimize】The search table supports size fuzzy matching +- ⚡️【Optimize】Sort Database and Schema at the top +- ⚡️【Optimize】The queried data supports editing and modification in the large popup window of the view +- ⚡️【Optimize】Example Query the page loading effect of data +- ⚡️【Optimize】Keep the top focused tab always in the viewable area +- ⚡️【Optimize】Query data cell does not have the problem of rolling nail bar + +**更新日志** +- ⭐【新功能】支持可视化创建数据库 +- ⭐【新功能】支持热更新 +- ⭐【新功能】双击表直接打开表 +- ⚡️【优化】搜索表支持大小模糊匹配 +- ⚡️【优化】Database 和 Schema 排序 +- ⚡️【优化】查询的数据支持在查看的大的弹窗中编辑修改 +- ⚡️【优化】查询数据翻页loading效果 +- ⚡️【优化】保持顶部聚焦的tab永远在可视区域内 +- ⚡️【优化】查询数据单元格没有滚钉条问题 + + # 3.0.4 `2023-10-20` From 540e9ad85a2d4d2b94ce7ad4c8df34697e0850d8 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 23:43:34 +0800 Subject: [PATCH 1054/1069] Update: Changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2425855..9135151d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - ⚡️【Optimize】The queried data supports editing and modification in the large popup window of the view - ⚡️【Optimize】Example Query the page loading effect of data - ⚡️【Optimize】Keep the top focused tab always in the viewable area -- ⚡️【Optimize】Query data cell does not have the problem of rolling nail bar +- ⚡️【Optimize】Query data cell does not have scroll bar problem **更新日志** - ⭐【新功能】支持可视化创建数据库 @@ -20,7 +20,7 @@ - ⚡️【优化】查询的数据支持在查看的大的弹窗中编辑修改 - ⚡️【优化】查询数据翻页loading效果 - ⚡️【优化】保持顶部聚焦的tab永远在可视区域内 -- ⚡️【优化】查询数据单元格没有滚钉条问题 +- ⚡️【优化】查询数据单元格没有滚动条问题 # 3.0.4 From c3abf49988aa601fbca1d27023abaf0552776729 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Mon, 23 Oct 2023 23:50:06 +0800 Subject: [PATCH 1055/1069] fix: create schemaName drop-down box is not folded --- .../pages/main/workspace/components/WorkspaceHeader/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 9f659d504..fce180874 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -341,7 +341,7 @@ const WorkspaceHeader = memo((props) => {
    { - setOpenDBCascaderDropdown(false); + setOpenSchemaCascaderDropdown(false); createDatabaseRef.current?.setOpen(true, 'schema'); }} > From a29275d23cbc6ff38d269337abc4e0808a553f83 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 11:10:32 +0800 Subject: [PATCH 1056/1069] fix:Close the app --- chat2db-client/src/layouts/index.tsx | 10 ++++------ chat2db-client/src/main/index.js | 30 ++++++++++++++++++++++------ chat2db-client/src/main/preload.js | 6 ++---- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 98ebb67e5..2c0c5295c 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -6,7 +6,6 @@ import { v4 as uuidv4 } from 'uuid'; import { getAntdThemeConfig, injectThemeVar } from '@/theme'; import { IVersionResponse } from '@/typings'; import miscService from '@/service/misc'; -import configService from '@/service/config'; import antdEnUS from 'antd/locale/en_US'; import antdZhCN from 'antd/locale/zh_CN'; import { useTheme } from '@/hooks'; @@ -34,7 +33,7 @@ declare global { electronApi?: { startServerForSpawn: () => void; quitApp: () => void; - beforeQuitApp: (fn: () => void) => void; + setBaseURL: (baseUrl:string) => void; registerAppMenu: (data:any) => void; }; } @@ -111,10 +110,9 @@ function AppContainer() { window.electronApi?.registerAppMenu({ version: __APP_VERSION__, }) - - window.electronApi?.beforeQuitApp(()=>{ - configService.stopJavaService() - }) + // 把关闭java服务的的方法传给electron + window.electronApi?.setBaseURL?.(window._BaseURL) + // console.log(window.electronApi) } // 初始化indexedDB diff --git a/chat2db-client/src/main/index.js b/chat2db-client/src/main/index.js index e6e8ac7b4..cebfbd40e 100644 --- a/chat2db-client/src/main/index.js +++ b/chat2db-client/src/main/index.js @@ -10,6 +10,8 @@ const { loadMainResource } = require('./utils'); let mainWindow = null; +let baseUrl = null; + function createWindow() { mainWindow = new BrowserWindow({ minWidth: 1080, @@ -31,7 +33,7 @@ function createWindow() { // 关闭window时触发下列事件. mainWindow.on('closed', function (event) { event.preventDefault(); - // mainWindow = null; + mainWindow = null; }); // 监听打开新窗口事件 用默认浏览器打开 @@ -56,9 +58,9 @@ app.on('ready', () => { registerAnalysis(); app.on('activate', function () { - // if (mainWindow === null) { - // } - createWindow(); + if (mainWindow === null) { + createWindow(); + } }); }); @@ -69,8 +71,18 @@ app.on('window-all-closed', () => { }); app.on('before-quit', () => { - // 退出应用前触发before-quit-app - // mainWindow.webContents.send('before-quit-app'); + if(baseUrl){ + try { + const request = net.request({ + headers: { + 'Content-Type': 'application/json', + }, + method: 'POST', + url: `${baseUrl}/api/system/stop`, + }); + request.end(); + } catch (error) {} + } }); ipcMain.handle('get-product-name', () => { @@ -87,3 +99,9 @@ ipcMain.on('quit-app', () => { ipcMain.on('register-app-menu', (event, orgs) => { registerAppMenu(mainWindow, orgs); }); + +ipcMain.on('set-base-url',(event,_baseUrl)=>{ + baseUrl = _baseUrl; +}) + + diff --git a/chat2db-client/src/main/preload.js b/chat2db-client/src/main/preload.js index 1f801d342..5d2a04213 100644 --- a/chat2db-client/src/main/preload.js +++ b/chat2db-client/src/main/preload.js @@ -43,10 +43,8 @@ contextBridge.exposeInMainWorld('electronApi', { quitApp: () => { ipcRenderer.send('quit-app'); }, - beforeQuitApp: (callback) => { - ipcRenderer.on('before-quit-app', () => { - callback(); - }); + setBaseURL: (baseUrl) => { + ipcRenderer.send('set-base-url', baseUrl); }, registerAppMenu: (menuProps) => { ipcRenderer.send('register-app-menu', menuProps); From d4734cdb669451cdc35a1687ee1556da17348fdc Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 13:36:23 +0800 Subject: [PATCH 1057/1069] fix:autosave --- .../src/components/Console/index.tsx | 55 +++++++++++-------- chat2db-client/src/pages/demo/index.tsx | 24 +------- .../components/WorkspaceRight/index.tsx | 17 +++++- 3 files changed, 48 insertions(+), 48 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 4f17a1480..b1f3b4637 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -65,6 +65,7 @@ interface IProps { consoleId?: number; schemaName?: string; consoleName?: string; + status?: ConsoleStatus; }; tableList?: ITreeNode[]; editorOptions?: IEditorOptions; @@ -108,7 +109,6 @@ function Console(props: IProps, ref: ForwardedRef) { const [isStream, setIsStream] = useState(false); const timerRef = useRef(); const aiFetchIntervalRef = useRef(); - const initializeSuccessful = useRef(false); const closeEventSource = useRef(); /** @@ -140,31 +140,32 @@ function Console(props: IProps, ref: ForwardedRef) { return; } // 离开时保存 - if (!isActive) { + if (!isActive && timerRef.current) { // 离开时清除定时器 indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { consoleId: executeParams.consoleId!, ddl: editorRef?.current?.getAllContent(), userId: getCookie('CHAT2DB.USER_ID'), }); - if (timerRef.current) { - clearInterval(timerRef.current); - } + clearInterval(timerRef.current); } else { // 活跃时自动保存 indexedDB - .getDataByCursor('chat2db', 'workspaceConsoleDDL', { - consoleId: executeParams.consoleId!, - userId: getCookie('CHAT2DB.USER_ID'), - }) - .then((res: any) => { - const value = res?.[0]?.ddl || ''; - if (value) { - editorRef?.current?.setValue(value, 'reset'); - initializeSuccessful.current = true; - timingAutoSave(); - } - }); + .getDataByCursor('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + userId: getCookie('CHAT2DB.USER_ID'), + }) + .then((res: any) => { + const value = defaultValue || res?.[0]?.ddl || ''; + const oldValue = editorRef?.current?.getAllContent(); + console.log(value !== oldValue) + if(value !== oldValue){ + editorRef?.current?.setValue(value, 'reset'); + } + setTimeout(() => { + timingAutoSave(); + }, 0); + }); } return () => { if (timerRef.current) { @@ -175,12 +176,20 @@ function Console(props: IProps, ref: ForwardedRef) { function timingAutoSave() { timerRef.current = setInterval(() => { - indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { - consoleId: executeParams.consoleId!, - ddl: editorRef?.current?.getAllContent(), - userId: getCookie('CHAT2DB.USER_ID'), - }); - }, 3000); + if(executeParams.status === ConsoleStatus.RELEASE){ + const p: any = { + id: executeParams.consoleId, + ddl: editorRef?.current?.getAllContent(), + }; + historyServer.updateSavedConsole(p) + }else{ + indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + ddl: editorRef?.current?.getAllContent(), + userId: getCookie('CHAT2DB.USER_ID'), + }); + } + }, 2000); } const tableListName = useMemo(() => { diff --git a/chat2db-client/src/pages/demo/index.tsx b/chat2db-client/src/pages/demo/index.tsx index b033dc5e4..cb3bfce8c 100644 --- a/chat2db-client/src/pages/demo/index.tsx +++ b/chat2db-client/src/pages/demo/index.tsx @@ -1,5 +1,4 @@ import React, { useEffect, useState } from 'react'; -import indexedDB from '@/indexedDB'; export enum CustomerTypeEnum { visitor = 'visitor', @@ -14,31 +13,10 @@ export enum CustomerTypeEnum2 { export type CustomerType1 = CustomerTypeEnum | CustomerTypeEnum2; const App: React.FC = () => { - const [db, setDb] = useState(); - useEffect(() => { - indexedDB.createDB('chat2db', 2).then((db) => { - setDb(db); - }); - }, []); - - const add = () => { - indexedDB.addData(db, 'workspaceConsoleDDL', { - userId: '1', - consoleId: '1', - ddl: 'select * from user', - }); - }; - - const deleteFn = () => { - indexedDB.deleteData(db, 'workspaceConsoleDDL', '1'); - }; return ( -
    - - -
    +
    demo
    ); }; 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 4ccd1e2c7..f633db76f 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -8,7 +8,7 @@ import historyService from '@/service/history'; import sqlService from '@/service/sql'; import Tabs, { ITabItem } from '@/components/Tabs'; // import WorkspaceExtend from '../WorkspaceExtend'; -import SearchResult, { ISearchResultRef } from '@/components/SearchResult'; +import SearchResult from '@/components/SearchResult'; import Iconfont from '@/components/Iconfont'; import LoadingContent from '@/components/Loading/LoadingContent'; import ShortcutKey from '@/components/ShortcutKey'; @@ -64,7 +64,6 @@ const WorkspaceRight = memo((props: IProps) => { uniqueData: t, }; }); - console.log(workspaceTabList, newTabList); if (workspaceTabList.length) { const newWorkspaceTabList = lodash.cloneDeep(workspaceTabList); const newAddList: any = []; @@ -312,11 +311,24 @@ const WorkspaceRight = memo((props: IProps) => { } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { + const { extraParams } = doubleClickTreeNodeData; const { tableName } = extraParams || {}; const sql = `SELECT * FROM ${compatibleDataBaseName(tableName!, curWorkspaceParams.databaseType)};\n`; const title = tableName!; const id = uuidV4(); + let flag = false; + workspaceTabList.forEach((t) => { + console.log(t.uniqueData?.sql === sql) + if (t.uniqueData?.sql === sql) { + setActiveConsoleId(t.id); + flag = true + return; + } + }) + if(flag){ + return + } setWorkspaceTabList([ ...(workspaceTabList || []), { @@ -574,6 +586,7 @@ const WorkspaceRight = memo((props: IProps) => { schemaName: curWorkspaceParams?.schemaName, consoleId: t.id as number, consoleName: uniqueData.name, + status: uniqueData.status, }} /> )} From 5055be29095b17edd8a07bd70a2f99567b34f6f3 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 15:11:03 +0800 Subject: [PATCH 1058/1069] fix: Getting the cached CurrentWorkspaceDatabase results in an error problem --- .../src/components/Console/index.tsx | 18 ++--- chat2db-client/src/models/workspace.ts | 2 +- .../components/WorkspaceHeader/index.tsx | 65 +++++++++++++------ 3 files changed, 55 insertions(+), 30 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index b1f3b4637..e5c2d79ad 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -30,13 +30,13 @@ enum IPromptType { ChatRobot = 'ChatRobot', } -enum IPromptTypeText { - NL_2_SQL = '自然语言转换', - SQL_EXPLAIN = '解释SQL', - SQL_OPTIMIZER = 'SQL优化', - SQL_2_SQL = 'SQL转换', - ChatRobot = 'Chat机器人', -} +// enum IPromptTypeText { +// NL_2_SQL = '自然语言转换', +// SQL_EXPLAIN = '解释SQL', +// SQL_OPTIMIZER = 'SQL优化', +// SQL_2_SQL = 'SQL转换', +// ChatRobot = 'Chat机器人', +// } export type IAppendValue = { text: any; @@ -70,7 +70,7 @@ interface IProps { tableList?: ITreeNode[]; editorOptions?: IEditorOptions; aiModel: IAIState; - dispatch: Function; + dispatch: any; remainingBtnLoading: boolean; // remainingUse: IAIState['remainingUse']; // onSQLContentChange: (v: string) => void; @@ -379,7 +379,7 @@ function Console(props: IProps, ref: ForwardedRef) { const saveConsole = (value?: string) => { // const a = editorRef.current?.getAllContent(); - let p: any = { + const p: any = { id: executeParams.consoleId, status: ConsoleStatus.RELEASE, ddl: value, diff --git a/chat2db-client/src/models/workspace.ts b/chat2db-client/src/models/workspace.ts index 9e151f6fb..47d194365 100644 --- a/chat2db-client/src/models/workspace.ts +++ b/chat2db-client/src/models/workspace.ts @@ -58,7 +58,7 @@ const WorkspaceModel: IWorkspaceModelType = { state: { databaseAndSchema: undefined, - curWorkspaceParams: getCurrentWorkspaceDatabase(), + curWorkspaceParams: {} as any, doubleClickTreeNodeData: undefined, consoleList: [], openConsoleList: [], 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 fce180874..9751eaf6b 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -13,6 +13,7 @@ import { useUpdateEffect } from '@/hooks/useUpdateEffect'; import styles from './index.less'; import i18n from '@/i18n'; import CreateDatabase, { ICreateDatabaseRef } from '@/components/CreateDatabase'; +import { getCurrentWorkspaceDatabase } from '@/utils/localStorage'; interface IProps { className?: string; @@ -33,9 +34,12 @@ const notSupportCreateDatabaseType = [DatabaseTypeCode.H2]; // 不支持创建schema的数据库类型 const notSupportCreateSchemaType = [DatabaseTypeCode.ORACLE]; +const localStorageWorkspaceDatabase = getCurrentWorkspaceDatabase(); + const WorkspaceHeader = memo((props) => { const { connectionModel, workspaceModel, mainPageModel, dispatch } = props; const { connectionList, curConnection } = connectionModel; + console.log(connectionList); const { curWorkspaceParams } = workspaceModel; const { curPage } = mainPageModel; const [cascaderLoading, setCascaderLoading] = useState(false); @@ -63,6 +67,13 @@ const WorkspaceHeader = memo((props) => { } // 如果没有curConnection默认选第一个 if (!curConnection?.id && connectionList.length) { + if ( + localStorageWorkspaceDatabase.dataSourceId && + connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId)) + ) { + connectionChange([localStorageWorkspaceDatabase.dataSourceId]); + return; + } connectionChange([connectionList[0].id]); return; } @@ -71,6 +82,11 @@ const WorkspaceHeader = memo((props) => { // 如果curConnection不再connectionList里,也是默认选第一个 const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id); if (flag === -1) { + if (localStorageWorkspaceDatabase.dataSourceId && + connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId))) { + connectionChange([localStorageWorkspaceDatabase.dataSourceId]); + return; + } connectionChange([connectionList[0].id]); return; } @@ -90,10 +106,6 @@ const WorkspaceHeader = memo((props) => { getDatabaseList(isRefresh); setIsRefresh(false); } - // connectionList转换成可用的ConnectionOptions - // if (!connectionList.length) { - // setNoConnectionModal(true); - // } setConnectionOptions( connectionList?.map((t) => { return { @@ -146,12 +158,18 @@ const WorkspaceHeader = memo((props) => { }; }) || []; setCurDBOptions(dbList); - // 如果是切换那么就默认取列表的第一个database, 如果不是切换那么就取缓存的,如果缓存没有还是取列表第一个(这里是兜底,如果原先他并没有database,后来他加了database,如果还是取缓存的空就不对了) - const databaseName = - curWorkspaceParams.dataSourceId !== curConnection?.id - ? dbList[0]?.label - : curWorkspaceParams.databaseName || dbList[0]?.label; - getSchemaList(databaseName, refresh); + let databaseName = ''; + if(dbList.find((t: any) => t.value === localStorageWorkspaceDatabase.databaseName)){ + databaseName = localStorageWorkspaceDatabase.databaseName!; + }else{ + // 如果是切换那么就默认取列表的第一个database, 如果不是切换那么就取缓存的,如果缓存没有还是取列表第一个(这里是兜底,如果原先他并没有database,后来他加了database,如果还是取缓存的空就不对了) + databaseName = + curWorkspaceParams.dataSourceId !== curConnection?.id + ? dbList[0]?.label + : curWorkspaceParams.databaseName || dbList[0]?.label; + } + databaseChange([databaseName], [{ label: databaseName }],refresh); + // getSchemaList(databaseName, refresh); }) .catch(() => { setCascaderLoading(false); @@ -183,10 +201,17 @@ const WorkspaceHeader = memo((props) => { }; }) || []; setCurSchemaOptions(schemaList); - const schemaName = - curWorkspaceParams.dataSourceId !== curConnection?.id - ? schemaList[0]?.label - : curWorkspaceParams.schemaName || schemaList[0]?.label; + + let schemaName = ''; + if(schemaList.find((t: any) => t.value === localStorageWorkspaceDatabase.schemaName)){ + schemaName = localStorageWorkspaceDatabase.schemaName!; + }else{ + schemaName = + curWorkspaceParams.dataSourceId !== curConnection?.id + ? schemaList[0]?.label + : curWorkspaceParams.schemaName || schemaList[0]?.label; + } + // schemaChange([schemaName], [{ label: schemaName }]); const data: any = { dataSourceId: curConnection.id, dataSourceName: curConnection.alias, @@ -245,17 +270,17 @@ const WorkspaceHeader = memo((props) => { } // 数据库切换 - function databaseChange(valueArr: any, selectedOptions: any) { - if (selectedOptions[0].label !== curWorkspaceParams.databaseName) { - getSchemaList(selectedOptions[0].label); - } + function databaseChange(valueArr: any, selectedOptions: any,refresh) { + // if (selectedOptions[0].label !== curWorkspaceParams.databaseName) { + getSchemaList(selectedOptions[0].label,refresh); + // } } // schema切换 function schemaChange(valueArr: any, selectedOptions: any) { - if (selectedOptions[0].label !== curWorkspaceParams.schemaName) { + // if (selectedOptions[0].label !== curWorkspaceParams.schemaName) { setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value }); - } + // } } function handleRefresh() { From 8961a76c807cb881696b72ab5b3d8960d52d36b7 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 15:12:59 +0800 Subject: [PATCH 1059/1069] Remove console --- chat2db-client/src/blocks/Setting/About/index.tsx | 1 - chat2db-client/src/components/Console/index.tsx | 1 - chat2db-client/src/components/SearchResult/TableBox/index.tsx | 1 - .../pages/main/workspace/components/WorkspaceHeader/index.tsx | 1 - .../src/pages/main/workspace/components/WorkspaceRight/index.tsx | 1 - 5 files changed, 5 deletions(-) diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index 780356bbd..9b5c9b89c 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -35,7 +35,6 @@ export default function AboutUs(props: IProps) { }; const restartApp = () => { - console.log(window.electronApi) window.electronApi?.quitApp(); } diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index e5c2d79ad..397cf461b 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -158,7 +158,6 @@ function Console(props: IProps, ref: ForwardedRef) { .then((res: any) => { const value = defaultValue || res?.[0]?.ddl || ''; const oldValue = editorRef?.current?.getAllContent(); - console.log(value !== oldValue) if(value !== oldValue){ editorRef?.current?.setValue(value, 'reset'); } diff --git a/chat2db-client/src/components/SearchResult/TableBox/index.tsx b/chat2db-client/src/components/SearchResult/TableBox/index.tsx index 16c136e4d..ba7194bc5 100644 --- a/chat2db-client/src/components/SearchResult/TableBox/index.tsx +++ b/chat2db-client/src/components/SearchResult/TableBox/index.tsx @@ -234,7 +234,6 @@ export default function TableBox(props: ITableProps) { } }); setTableData(newTableData); - console.log(newTableData); // 添加更新记录 setUpdateData([ 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 9751eaf6b..1ad3d3229 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -39,7 +39,6 @@ const localStorageWorkspaceDatabase = getCurrentWorkspaceDatabase(); const WorkspaceHeader = memo((props) => { const { connectionModel, workspaceModel, mainPageModel, dispatch } = props; const { connectionList, curConnection } = connectionModel; - console.log(connectionList); const { curWorkspaceParams } = workspaceModel; const { curPage } = mainPageModel; const [cascaderLoading, setCascaderLoading] = useState(false); 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 f633db76f..8f4fdceb2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -319,7 +319,6 @@ const WorkspaceRight = memo((props: IProps) => { const id = uuidV4(); let flag = false; workspaceTabList.forEach((t) => { - console.log(t.uniqueData?.sql === sql) if (t.uniqueData?.sql === sql) { setActiveConsoleId(t.id); flag = true From 9b2a54668a5e923ad5ac9f2181f6762c74502df6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 17:14:13 +0800 Subject: [PATCH 1060/1069] Log in to the main page without saving history --- chat2db-client/src/utils/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chat2db-client/src/utils/index.ts b/chat2db-client/src/utils/index.ts index e9d3ab9ba..9ae085c20 100644 --- a/chat2db-client/src/utils/index.ts +++ b/chat2db-client/src/utils/index.ts @@ -244,9 +244,9 @@ export function formatSql(sql: string, dbType: DatabaseTypeCode) { // 桌面端用hash模式,web端用history模式,路由跳转 export function navigate(path: string) { if (__ENV__ === 'desktop') { - window.location.href = `#${path}`; + window.location.replace(`#${path}`) } else { - window.location.href = path; + window.location.replace(path) } } From c56d3af509eae9ab87a66df4a6f920abb3286052 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 17:32:58 +0800 Subject: [PATCH 1061/1069] Click Save and save to the server automatically --- chat2db-client/src/components/Console/index.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 397cf461b..f5ab5836e 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -173,9 +173,12 @@ function Console(props: IProps, ref: ForwardedRef) { }; }, [isActive]); - function timingAutoSave() { + function timingAutoSave(status?: ConsoleStatus) { + if (timerRef.current) { + clearInterval(timerRef.current); + } timerRef.current = setInterval(() => { - if(executeParams.status === ConsoleStatus.RELEASE){ + if(executeParams.status === ConsoleStatus.RELEASE || status === ConsoleStatus.RELEASE){ const p: any = { id: executeParams.consoleId, ddl: editorRef?.current?.getAllContent(), @@ -188,7 +191,7 @@ function Console(props: IProps, ref: ForwardedRef) { userId: getCookie('CHAT2DB.USER_ID'), }); } - }, 2000); + }, 5000); } const tableListName = useMemo(() => { @@ -388,6 +391,7 @@ function Console(props: IProps, ref: ForwardedRef) { indexedDB.deleteData('chat2db', 'workspaceConsoleDDL', executeParams.consoleId!); message.success(i18n('common.tips.saveSuccessfully')); props.onConsoleSave && props.onConsoleSave(); + timingAutoSave(ConsoleStatus.RELEASE); }); }; From 7ea002985c052277c8b685f5f84b66bf936127df Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 24 Oct 2023 19:23:38 +0800 Subject: [PATCH 1062/1069] Support for creating database and schema --- .../web/api/controller/rdb/doc/DatabaseExportService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java index a33a4d650..4e5bc4094 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/doc/DatabaseExportService.java @@ -108,7 +108,7 @@ public void generate(String databaseName, OutputStream outputStream, ExportOptio try { export(outputStream, exportOptions); } catch (Exception e) { - throw new RuntimeException("导出失败!请联系开发者,邮箱:963565242@qq.com" + e); + throw new RuntimeException("导出失败!请联系开发者" + e); } init(); } From b439b65e7dd122b993d2ab795fa1c05af3c2ecd1 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 20 Oct 2023 17:47:17 +0800 Subject: [PATCH 1063/1069] fix:edit table data paging --- chat2db-client/src/components/Console/index.tsx | 11 +++++++++-- .../main/workspace/components/SaveList/index.tsx | 6 +++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index f5ab5836e..203f679a0 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -110,6 +110,8 @@ function Console(props: IProps, ref: ForwardedRef) { const timerRef = useRef(); const aiFetchIntervalRef = useRef(); const closeEventSource = useRef(); + // 上一次同步的console数据 + const lastSyncConsole = useRef(defaultValue); /** * 当前选择的AI类型是Chat2DBAI @@ -178,16 +180,21 @@ function Console(props: IProps, ref: ForwardedRef) { clearInterval(timerRef.current); } timerRef.current = setInterval(() => { + const ddl = editorRef?.current?.getAllContent() + if(ddl === lastSyncConsole.current){ + return + } + lastSyncConsole.current = ddl; if(executeParams.status === ConsoleStatus.RELEASE || status === ConsoleStatus.RELEASE){ const p: any = { id: executeParams.consoleId, - ddl: editorRef?.current?.getAllContent(), + ddl, }; historyServer.updateSavedConsole(p) }else{ indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { consoleId: executeParams.consoleId!, - ddl: editorRef?.current?.getAllContent(), + ddl, userId: getCookie('CHAT2DB.USER_ID'), }); } 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 f220af4bc..834bc0bfe 100644 --- a/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/SaveList/index.tsx @@ -107,7 +107,7 @@ const SaveList = dvaModel((props: any) => { id: data.id, status: ConsoleStatus.DRAFT, }; - historyServer.updateSavedConsole(params).then((res) => { + historyServer.updateSavedConsole(params).then(() => { dispatch({ type: 'workspace/fetchGetSavedConsole', payload: { @@ -115,10 +115,10 @@ const SaveList = dvaModel((props: any) => { status: ConsoleStatus.RELEASE, ...curWorkspaceParams, }, - callback: (res: any) => { + callback: (_res: any) => { dispatch({ type: 'workspace/setConsoleList', - payload: res.data, + payload: _res.data, }); }, }); From aabcffb390ffb385afc5f0abf9e398f456e10470 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Sat, 21 Oct 2023 22:30:38 +0800 Subject: [PATCH 1064/1069] init: UpdateDetection --- chat2db-client/.umirc.ts | 2 +- chat2db-client/src/blocks/Setting/About/index.tsx | 1 - .../src/blocks/Setting/UpdateDetection/index.tsx | 2 +- chat2db-client/src/service/config.ts | 8 +------- 4 files changed, 3 insertions(+), 10 deletions(-) diff --git a/chat2db-client/.umirc.ts b/chat2db-client/.umirc.ts index 0ed67cc99..fe3b19cee 100644 --- a/chat2db-client/.umirc.ts +++ b/chat2db-client/.umirc.ts @@ -97,7 +97,7 @@ export default defineConfig({ define: { __ENV__: process.env.UMI_ENV, __BUILD_TIME__: transitionTimezoneTimestamp(new Date().getTime()), - __APP_VERSION__: yarn_config.app_version || '9.9.9', + __APP_VERSION__: yarn_config.app_version || '0.0.0', __APP_PORT__: yarn_config.app_port, }, esbuildMinifyIIFE: true diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index 9b5c9b89c..3f0c7d5e9 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -9,7 +9,6 @@ import { DownloadOutlined } from '@ant-design/icons'; import { IUpdateDetectionData } from '../index'; import { IUpdateDetectionRef, UpdatedStatusEnum } from '../UpdateDetection'; import Iconfont from '@/components/Iconfont'; - interface IProps { updateDetectionData: IUpdateDetectionData | null; updateDetectionRef: React.MutableRefObject | null; diff --git a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx index 945ce48df..9d4be8039 100644 --- a/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx +++ b/chat2db-client/src/blocks/Setting/UpdateDetection/index.tsx @@ -37,7 +37,7 @@ const MAX_TIMES = 200; const UpdateDetection = memo( forwardRef((props: IProps, ref: ForwardedRef) => { - const { openSettingModal, updateDetectionData, setUpdateDetectionData } = props; + const { openSettingModal, setUpdateDetectionData } = props; const [notificationApi, notificationDom] = notification.useNotification(); const timesRef = React.useRef(0); diff --git a/chat2db-client/src/service/config.ts b/chat2db-client/src/service/config.ts index 2ddc6b682..d78c99d29 100644 --- a/chat2db-client/src/service/config.ts +++ b/chat2db-client/src/service/config.ts @@ -78,11 +78,6 @@ const setAppUpdateType = createRequest('/api/sy method: 'post', }); -// 退出electron时关闭后端服务 -const stopJavaService = createRequest('/api/system/stop', { - method: 'post', -}); - export default { getSystemConfig, setSystemConfig, @@ -92,6 +87,5 @@ export default { getLatestVersion, isUpdateSuccess, updateDesktopVersion, - setAppUpdateType, - stopJavaService + setAppUpdateType }; From 82dbff1f817d7a3e6f6f196b00b080da443055ce Mon Sep 17 00:00:00 2001 From: shanhexi Date: Tue, 24 Oct 2023 20:53:29 +0800 Subject: [PATCH 1065/1069] merge --- chat2db-client/src/blocks/Setting/About/index.tsx | 6 ++++++ chat2db-client/src/i18n/en-us/setting.ts | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/chat2db-client/src/blocks/Setting/About/index.tsx b/chat2db-client/src/blocks/Setting/About/index.tsx index 3f0c7d5e9..11fea931e 100644 --- a/chat2db-client/src/blocks/Setting/About/index.tsx +++ b/chat2db-client/src/blocks/Setting/About/index.tsx @@ -78,6 +78,12 @@ export default function AboutUs(props: IProps) { {i18n('setting.button.restart')} ); + // case UpdatedStatusEnum.UPDATED: + // return ( + // + // ); default: return false; } diff --git a/chat2db-client/src/i18n/en-us/setting.ts b/chat2db-client/src/i18n/en-us/setting.ts index ac62d72f1..c5497c7d6 100644 --- a/chat2db-client/src/i18n/en-us/setting.ts +++ b/chat2db-client/src/i18n/en-us/setting.ts @@ -40,7 +40,7 @@ export default { 'setting.button.restart': 'Restart', 'setting.text.discoverNewVersion': 'Discover new version {1}', 'setting.text.isLatestVersion': 'This is the latest version', - 'setting.button.changeLog': 'ChangeLog', + 'setting.button.changeLog': 'Changelog', 'setting.title.updateRule': 'Update rule', 'setting.text.autoUpdate': 'The new version automatically downloads and installs updates', 'setting.text.manualUpdate': 'Only alert me when a new version is released', From c751703e330240eeba125f0c610e77c3602e1d34 Mon Sep 17 00:00:00 2001 From: Jerry Fan Date: Tue, 24 Oct 2023 23:08:49 +0800 Subject: [PATCH 1066/1069] fix: fix auto select table model --- .../src/components/Console/index.tsx | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/chat2db-client/src/components/Console/index.tsx b/chat2db-client/src/components/Console/index.tsx index 397cf461b..755e772dc 100644 --- a/chat2db-client/src/components/Console/index.tsx +++ b/chat2db-client/src/components/Console/index.tsx @@ -133,8 +133,6 @@ function Console(props: IProps, ref: ForwardedRef) { editorRef: editorRef?.current, })); - useEffect(() => {}, []); - useEffect(() => { if (source !== 'workspace') { return; @@ -151,20 +149,20 @@ function Console(props: IProps, ref: ForwardedRef) { } else { // 活跃时自动保存 indexedDB - .getDataByCursor('chat2db', 'workspaceConsoleDDL', { - consoleId: executeParams.consoleId!, - userId: getCookie('CHAT2DB.USER_ID'), - }) - .then((res: any) => { - const value = defaultValue || res?.[0]?.ddl || ''; - const oldValue = editorRef?.current?.getAllContent(); - if(value !== oldValue){ - editorRef?.current?.setValue(value, 'reset'); - } - setTimeout(() => { - timingAutoSave(); - }, 0); - }); + .getDataByCursor('chat2db', 'workspaceConsoleDDL', { + consoleId: executeParams.consoleId!, + userId: getCookie('CHAT2DB.USER_ID'), + }) + .then((res: any) => { + const value = defaultValue || res?.[0]?.ddl || ''; + const oldValue = editorRef?.current?.getAllContent(); + if (value !== oldValue) { + editorRef?.current?.setValue(value, 'reset'); + } + setTimeout(() => { + timingAutoSave(); + }, 0); + }); } return () => { if (timerRef.current) { @@ -175,13 +173,13 @@ function Console(props: IProps, ref: ForwardedRef) { function timingAutoSave() { timerRef.current = setInterval(() => { - if(executeParams.status === ConsoleStatus.RELEASE){ + if (executeParams.status === ConsoleStatus.RELEASE) { const p: any = { id: executeParams.consoleId, ddl: editorRef?.current?.getAllContent(), }; - historyServer.updateSavedConsole(p) - }else{ + historyServer.updateSavedConsole(p); + } else { indexedDB.updateData('chat2db', 'workspaceConsoleDDL', { consoleId: executeParams.consoleId!, ddl: editorRef?.current?.getAllContent(), @@ -455,10 +453,10 @@ function Console(props: IProps, ref: ForwardedRef) { }; const handleSelectTableSyncModel = () => { - const syncModel: SyncModelType | null = Number(localStorage.getItem('syncTableModel')) ?? null; + const syncModel = localStorage.getItem('syncTableModel'); const hasAiAccess = aiModel.hasWhite; if (syncModel !== null) { - setSyncTableModel(syncModel); + setSyncTableModel(Number(syncModel)); return; } From cd1ede11f572bb5f38485c368836bf044f5292a0 Mon Sep 17 00:00:00 2001 From: SwallowGG <1558143046@qq.com> Date: Tue, 24 Oct 2023 23:13:18 +0800 Subject: [PATCH 1067/1069] Support for creating database and schema --- .github/workflows/release.yml | 4 +++- .../java/ai/chat2db/plugin/h2/H2DBManage.java | 19 +++++++++++++++++ .../plugin/postgresql/PostgreSQLDBManage.java | 21 ++++++++++++------- .../plugin/sqlserver/SqlServerDBManage.java | 2 +- .../api/controller/rdb/RdbDmlController.java | 4 ++-- 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 552cd732d..04a6cb17b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -255,7 +255,9 @@ jobs: cp chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/chat2db-server-start.jar ./oss_temp_file cp -r chat2db-client/release/*.dmg ./oss_temp_file cp -r chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/dist ./oss_temp_file/dist - cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/static/ && zip -r chat2db-server-start.zip ./ + cd chat2db-client/versions/${{ steps.chat2db_version.outputs.substring }}/ && zip -r ${{ steps.chat2db_version.outputs.substring }}.zip ./ + cp -r ${{ steps.chat2db_version.outputs.substring }}.zip ../../../oss_temp_file + cd static/ && zip -r chat2db-server-start.zip ./ cp -r chat2db-server-start.zip ../../../../oss_temp_file # 准备要需要的数据 MacOS arm64 diff --git a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java index 0e02f8ddf..c22b795a9 100644 --- a/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-h2/src/main/java/ai/chat2db/plugin/h2/H2DBManage.java @@ -1,13 +1,32 @@ package ai.chat2db.plugin.h2; import java.sql.Connection; +import java.sql.SQLException; import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; +import ai.chat2db.spi.sql.Chat2DBContext; +import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; +import org.apache.commons.lang3.ObjectUtils; +import org.apache.commons.lang3.StringUtils; public class H2DBManage extends DefaultDBManage implements DBManage { + @Override + public void connectDatabase(Connection connection, String database) { + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + if (ObjectUtils.anyNull(connectInfo) || StringUtils.isEmpty(connectInfo.getSchemaName())) { + return; + } + String schemaName = connectInfo.getSchemaName(); + try { + SQLExecutor.getInstance().execute(connection, "SET SCHEMA \"" + schemaName + "\""); + } catch (SQLException e) { + + } + } + @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { diff --git a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java index ff3ffdcb1..c2f4b91b5 100644 --- a/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-postgresql/src/main/java/ai/chat2db/plugin/postgresql/PostgreSQLDBManage.java @@ -4,17 +4,22 @@ import ai.chat2db.spi.DBManage; import ai.chat2db.spi.jdbc.DefaultDBManage; +import ai.chat2db.spi.sql.Chat2DBContext; import ai.chat2db.spi.sql.ConnectInfo; import ai.chat2db.spi.sql.SQLExecutor; +import org.apache.commons.lang3.StringUtils; public class PostgreSQLDBManage extends DefaultDBManage implements DBManage { @Override public void connectDatabase(Connection connection, String database) { - //try { - // SQLExecutor.getInstance().execute(connection,"SELECT pg_database_size('"+database+"');"); - //} catch (SQLException e) { - // throw new RuntimeException(e); - //} + try { + ConnectInfo connectInfo = Chat2DBContext.getConnectInfo(); + if (!StringUtils.isEmpty(connectInfo.getSchemaName())) { + SQLExecutor.getInstance().execute(connection, "SET search_path TO \"" + connectInfo.getSchemaName() + "\""); + } + } catch (Exception e) { + + } } @Override @@ -26,7 +31,7 @@ public Connection getConnection(ConnectInfo connectInfo) { } connectInfo.setUrl(url); - return super.getConnection(connectInfo); + return super.getConnection(connectInfo); } @@ -53,8 +58,8 @@ public String replaceDatabaseInJdbcUrl(String url, String newDatabase) { @Override public void dropTable(Connection connection, String databaseName, String schemaName, String tableName) { - String sql = "DROP TABLE "+ tableName; - SQLExecutor.getInstance().executeSql(connection,sql, resultSet -> null); + String sql = "DROP TABLE " + tableName; + SQLExecutor.getInstance().executeSql(connection, sql, resultSet -> null); } } diff --git a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java index 2b1d73256..ded0abf11 100644 --- a/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java +++ b/chat2db-server/chat2db-plugins/chat2db-sqlserver/src/main/java/ai/chat2db/plugin/sqlserver/SqlServerDBManage.java @@ -11,7 +11,7 @@ public class SqlServerDBManage extends DefaultDBManage implements DBManage { @Override public void connectDatabase(Connection connection, String database) { try { - SQLExecutor.getInstance().execute(connection,"use [" + database + "];"); + SQLExecutor.getInstance().execute(connection, "use [" + database + "];"); } catch (SQLException e) { throw new RuntimeException(e); } diff --git a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java index 626663fec..1df806b22 100644 --- a/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java +++ b/chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/rdb/RdbDmlController.java @@ -93,7 +93,7 @@ public DataResult executeDDL(@RequestBody DmlRequest request) { try { boolean flag = true; ExecuteResultVO executeResult = null; - connection.setAutoCommit(false); + //connection.setAutoCommit(false); ListResult resultDTOListResult = dlTemplateService.execute(param); List resultVOS = rdbWebConverter.dto2vo(resultDTOListResult.getData()); if (!CollectionUtils.isEmpty(resultVOS)) { @@ -107,7 +107,7 @@ public DataResult executeDDL(@RequestBody DmlRequest request) { } } if (flag) { - connection.commit(); + //connection.commit(); return DataResult.of(resultVOS.get(0)); }else { connection.rollback(); From e973f27fd4f071642d767fd78c5152ea74a0cb70 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Thu, 26 Oct 2023 23:33:13 +0800 Subject: [PATCH 1068/1069] feat:Close the left sidebar And fix:Switching table paging bug --- chat2db-client/.vscode/settings.json | 4 +- chat2db-client/package.json | 5 +- .../src/components/CustomLayout/index.less | 52 +++ .../src/components/CustomLayout/index.tsx | 28 ++ .../components/DraggableContainer/index.tsx | 58 +-- chat2db-client/src/layouts/index.tsx | 12 +- .../workspace/components/TableList/index.tsx | 36 +- .../workspace/components/Tree/treeConfig.tsx | 9 +- .../components/WorkspaceHeader/index.less | 2 + .../components/WorkspaceHeader/index.tsx | 29 +- .../components/WorkspaceRight/index.tsx | 32 +- .../src/pages/main/workspace/index.less | 6 +- .../src/pages/main/workspace/index.tsx | 19 +- chat2db-client/src/store/index.ts | 0 chat2db-client/src/store/workspace/config.ts | 32 ++ chat2db-client/src/store/workspace/demo.ts | 21 + chat2db-client/src/store/workspace/index.ts | 24 ++ chat2db-client/yarn.lock | 369 +++++++++++++++++- 18 files changed, 629 insertions(+), 109 deletions(-) create mode 100644 chat2db-client/src/components/CustomLayout/index.less create mode 100644 chat2db-client/src/components/CustomLayout/index.tsx create mode 100644 chat2db-client/src/store/index.ts create mode 100644 chat2db-client/src/store/workspace/config.ts create mode 100644 chat2db-client/src/store/workspace/demo.ts create mode 100644 chat2db-client/src/store/workspace/index.ts diff --git a/chat2db-client/.vscode/settings.json b/chat2db-client/.vscode/settings.json index 7ae4734e2..0aeb499ec 100644 --- a/chat2db-client/.vscode/settings.json +++ b/chat2db-client/.vscode/settings.json @@ -62,6 +62,7 @@ "OPENAI", "packagejson", "Parens", + "partialize", "pgsql", "plusplus", "pnpm", @@ -87,7 +88,8 @@ "webp", "wireframe", "Wppk", - "yapi" + "yapi", + "zustand" ], "typescript.tsdk": "/Users/wangjiaqi/Desktop/Chat2DB/chat2db-client/node_modules/typescript/lib" } diff --git a/chat2db-client/package.json b/chat2db-client/package.json index 0fb1844da..35a8e52e1 100644 --- a/chat2db-client/package.json +++ b/chat2db-client/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@dnd-kit/modifiers": "^6.0.1", - "ahooks": "^3.7.7", + "ahooks": "^3.7.8", "ali-react-table": "^2.6.1", "antd": "^5.6.0", "copy-to-clipboard": "^3.3.3", @@ -35,6 +35,7 @@ "echarts-for-react": "^3.0.2", "electron-log": "^4.4.8", "event-source-polyfill": "^1.0.31", + "highlight.js": "^11.9.0", "lodash": "^4.17.21", "markdown-it-link-attributes": "^4.0.1", "monaco-editor": "^0.34.0", @@ -48,7 +49,7 @@ "umi": "^4.0.70", "umi-request": "^1.4.0", "uuid": "^9.0.0", - "highlight.js": "^11.9.0" + "zustand": "^4.4.4" }, "devDependencies": { "@types/event-source-polyfill": "^1.0.1", diff --git a/chat2db-client/src/components/CustomLayout/index.less b/chat2db-client/src/components/CustomLayout/index.less new file mode 100644 index 000000000..298e11aaf --- /dev/null +++ b/chat2db-client/src/components/CustomLayout/index.less @@ -0,0 +1,52 @@ +@import '../../styles/var.less'; + +.customLayout { + display: flex; + align-items: center; + .iconPanel { + margin: 0px 5px; + border: 1px solid var(--color-text-tertiary); + border-radius: 3px; + height: 14px; + width: 14px; + position: relative; + overflow: hidden; + cursor: pointer; + &::after { + position: absolute; + content: ''; + width: 6px; + background-color: var(--color-text-tertiary); + } + } + + .iconPanelLeft { + &::after { + top: 0; + bottom: 0; + left: 0; + } + } + + .iconPanelLeftHidden { + &::after { + left: 5px; + width: 1px; + } + } + + .iconPanelRight { + &::after { + top: 0; + bottom: 0; + right: 5px; + width: 1px; + } + } + + .iconPanelRightHidden { + &::after { + width: 6px; + } + } +} diff --git a/chat2db-client/src/components/CustomLayout/index.tsx b/chat2db-client/src/components/CustomLayout/index.tsx new file mode 100644 index 000000000..c626a79ba --- /dev/null +++ b/chat2db-client/src/components/CustomLayout/index.tsx @@ -0,0 +1,28 @@ +import React, { memo } from 'react'; +import styles from './index.less'; +import classnames from 'classnames'; +import { useWorkspaceStore } from '@/store/workspace'; + +interface IProps { + className?: string; +} + +export default memo((props) => { + const { className } = props; + const { panelLeft, togglePanelLeft } = useWorkspaceStore((state) => { + return { + togglePanelLeft: state.togglePanelLeft, + panelLeft: state.layout.panelLeft, + }; + }); + + return ( +
    +
    + {/*
    */} +
    + ); +}); diff --git a/chat2db-client/src/components/DraggableContainer/index.tsx b/chat2db-client/src/components/DraggableContainer/index.tsx index 403a8b84a..b8889ecee 100644 --- a/chat2db-client/src/components/DraggableContainer/index.tsx +++ b/chat2db-client/src/components/DraggableContainer/index.tsx @@ -1,4 +1,4 @@ -import React, { memo, useRef, useEffect, useState, useMemo } from 'react'; +import React, { memo, useRef, useEffect, useState } from 'react'; import styles from './index.less'; import classnames from 'classnames'; @@ -7,18 +7,12 @@ interface IProps { children: any; //TODO: TS,约定接受一个数组,第一项child需要携带ref min?: number; layout?: 'row' | 'column'; - callback?: Function; + callback?: (data: any) => void; showLine?: boolean; } -export default memo(function DraggableContainer({ - children, - showLine = true, - callback, - min, - className, - layout = 'row', -}) { +export default memo((props: IProps) => { + const { children, showLine = true, callback, min, className, layout = 'row' } = props; const volatileRef = children[0]?.ref; const DividerRef = useRef(null); @@ -31,31 +25,17 @@ export default memo(function DraggableContainer({ if (!DividerRef.current) { return; } - // DividerRef.current.onmouseover = (e) => { - // setDragging(true); - // }; - // DividerRef.current.onmouseout = (e) => { - // setDragging(false); - // }; DividerRef.current.onmousedown = (e) => { if (!volatileRef?.current) return; - e.preventDefault(); setDragging(true); const clientStart = isRow ? e.clientX : e.clientY; - const volatileBoxXY = isRow - ? volatileRef.current.offsetWidth - : volatileRef.current.offsetHeight; - document.onmousemove = (e) => { - moveHandle( - isRow ? e.clientX : e.clientY, - volatileRef.current, - clientStart, - volatileBoxXY, - ); + const volatileBoxXY = isRow ? volatileRef.current.offsetWidth : volatileRef.current.offsetHeight; + document.onmousemove = (_e) => { + moveHandle(isRow ? _e.clientX : _e.clientY, volatileRef.current, clientStart, volatileBoxXY); }; - document.onmouseup = (e) => { + document.onmouseup = () => { setDragging(false); document.onmouseup = null; document.onmousemove = null; @@ -63,13 +43,8 @@ export default memo(function DraggableContainer({ }; }, []); - const moveHandle = ( - nowClientXY: any, - leftDom: any, - clientStart: any, - volatileBoxXY: any, - ) => { - let computedXY = nowClientXY - clientStart; + const moveHandle = (nowClientXY: any, leftDom: any, clientStart: any, volatileBoxXY: any) => { + const computedXY = nowClientXY - clientStart; let finalXY = 0; finalXY = volatileBoxXY + computedXY; @@ -92,18 +67,9 @@ export default memo(function DraggableContainer({
    -
    +
    } {children[1]} diff --git a/chat2db-client/src/layouts/index.tsx b/chat2db-client/src/layouts/index.tsx index 2c0c5295c..1668c93c7 100644 --- a/chat2db-client/src/layouts/index.tsx +++ b/chat2db-client/src/layouts/index.tsx @@ -33,8 +33,8 @@ declare global { electronApi?: { startServerForSpawn: () => void; quitApp: () => void; - setBaseURL: (baseUrl:string) => void; - registerAppMenu: (data:any) => void; + setBaseURL: (baseUrl: string) => void; + registerAppMenu: (data: any) => void; }; } const __APP_VERSION__: string; @@ -109,9 +109,9 @@ function AppContainer() { function registerElectronApi() { window.electronApi?.registerAppMenu({ version: __APP_VERSION__, - }) + }); // 把关闭java服务的的方法传给electron - window.electronApi?.setBaseURL?.(window._BaseURL) + window.electronApi?.setBaseURL?.(window._BaseURL); // console.log(window.electronApi) } @@ -186,7 +186,7 @@ function AppContainer() {
    {/* 状态等于1时,说明没服务起来需要轮训接口,这时可能服务配置又问题,需要设置来修改 */} - {startSchedule === 1 && ( + {/* {startSchedule === 1 && ( @@ -195,7 +195,7 @@ function AppContainer() { } noLogin /> - )} + )} */} {serviceFail && ( <>
    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 b3e9ed182..8066fb99b 100644 --- a/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/TableList/index.tsx @@ -20,6 +20,7 @@ import { debounce } from 'lodash'; import { dataSourceFormConfigs } from '@/components/ConnectionEdit/config/dataSource'; import styles from './index.less'; import ImportBlock from '@/components/ImportBlock'; +import { useRequest } from 'ahooks'; interface IOption { value: TreeNodeType; @@ -68,6 +69,9 @@ const TableList = dvaModel((props: any) => { const [searchKey, setSearchKey] = useState(''); const leftModuleTitleRef = useRef(null); const treeBoxRef = useRef(null); + const controllerRef = useRef(); + + const useRequestRes = useRequest(); // 导出表结构 const handleExport = (exportType: ExportTypeEnum) => { @@ -219,7 +223,6 @@ const TableList = dvaModel((props: any) => { }, [curWorkspaceParams]); useUpdateEffect(() => { - setCurList([]); getList(); }, [curType]); @@ -299,8 +302,19 @@ const TableList = dvaModel((props: any) => { }; function getList(params?: { refresh?: boolean }) { + // 在每一getList之前,都要把上一次的请求abort掉 + controllerRef.current && controllerRef.current.abort('abortRequest'); + + // 为每一次请求创建一个新的AbortController + controllerRef.current = new AbortController(); + + // abort会触发上一次请求的setTableLoading(false); 所以这里要延迟触发 + setTimeout(() => { + setTableLoading(true); + }, 0); + + setCurList([]); const { refresh = false } = params || {}; - setTableLoading(true); const p = { refresh, ...curWorkspaceParams, @@ -311,7 +325,11 @@ const TableList = dvaModel((props: any) => { p.pageNo = pagingData.pageNo; p.pageSize = pagingData.pageSize; } - treeConfig[curType.value].getChildren!(p) + + // 发送请求 + treeConfig[curType.value].getChildren!(p, { + signal: controllerRef.current.signal, + }) .then((res: any) => { // 表的处理 if (curType.value === TreeNodeType.TABLES) { @@ -346,8 +364,7 @@ const TableList = dvaModel((props: any) => { } function refreshTableList() { - if (isReady) { - setCurList([]); + if (isReady && !tableLoading) { getList({ refresh: true, }); @@ -441,15 +458,14 @@ const TableList = dvaModel((props: any) => {
    - { - (curType.value === TreeNodeType.TABLES && !curList.length) ? + {curType.value === TreeNodeType.TABLES && !curList.length ? (
    {i18n('common.text.noTableFoundUp')}
    {i18n('common.text.noTableFoundDown')}
    -
    - : +
    + ) : ( - } + )}
    diff --git a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx index 32ab31f7a..d25deb62e 100644 --- a/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx +++ b/chat2db-client/src/pages/main/workspace/components/Tree/treeConfig.tsx @@ -68,7 +68,10 @@ export const switchIcon: Partial<{ [key in TreeNodeType]: { icon: string; unfold export interface ITreeConfigItem { icon?: string; - getChildren?: (params: any) => Promise< + getChildren?: ( + params: any, + options?: any, + ) => Promise< | ITreeNode[] | ({ data: ITreeNode[]; @@ -191,10 +194,10 @@ export const treeConfig: { [key in TreeNodeType]: ITreeConfigItem } = { [TreeNodeType.TABLES]: { icon: '\ueac5', - getChildren: (params) => { + getChildren: (params, options) => { return new Promise((r, j) => { mysqlServer - .getTableList(params) + .getTableList(params, options) .then((res) => { const tableList: ITreeNode[] = res.data?.map((t: any) => { return { diff --git a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less index 17071ec86..4b29f40d2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.less @@ -19,6 +19,8 @@ flex: 1; flex-shrink: 0; overflow: hidden; + display: flex; + justify-content: end; } .workspaceHeaderLeft { 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 1ad3d3229..c4f8bcca2 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceHeader/index.tsx @@ -3,6 +3,7 @@ import classnames from 'classnames'; import { connect } from 'umi'; import lodash from 'lodash'; import Iconfont from '@/components/Iconfont'; +import CustomLayout from '@/components/CustomLayout'; import { IConnectionModelType } from '@/models/connection'; import { IWorkspaceModelType } from '@/models/workspace'; import { IMainPageType } from '@/models/mainPage'; @@ -68,7 +69,7 @@ const WorkspaceHeader = memo((props) => { if (!curConnection?.id && connectionList.length) { if ( localStorageWorkspaceDatabase.dataSourceId && - connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId)) + connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId)) ) { connectionChange([localStorageWorkspaceDatabase.dataSourceId]); return; @@ -81,8 +82,10 @@ const WorkspaceHeader = memo((props) => { // 如果curConnection不再connectionList里,也是默认选第一个 const flag = connectionList.findIndex((t: any) => t.id === curConnection?.id); if (flag === -1) { - if (localStorageWorkspaceDatabase.dataSourceId && - connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId))) { + if ( + localStorageWorkspaceDatabase.dataSourceId && + connectionList.find((t: any) => Number(t.id) === Number(localStorageWorkspaceDatabase.dataSourceId)) + ) { connectionChange([localStorageWorkspaceDatabase.dataSourceId]); return; } @@ -158,16 +161,16 @@ const WorkspaceHeader = memo((props) => { }) || []; setCurDBOptions(dbList); let databaseName = ''; - if(dbList.find((t: any) => t.value === localStorageWorkspaceDatabase.databaseName)){ + if (dbList.find((t: any) => t.value === localStorageWorkspaceDatabase.databaseName)) { databaseName = localStorageWorkspaceDatabase.databaseName!; - }else{ + } else { // 如果是切换那么就默认取列表的第一个database, 如果不是切换那么就取缓存的,如果缓存没有还是取列表第一个(这里是兜底,如果原先他并没有database,后来他加了database,如果还是取缓存的空就不对了) databaseName = curWorkspaceParams.dataSourceId !== curConnection?.id ? dbList[0]?.label : curWorkspaceParams.databaseName || dbList[0]?.label; } - databaseChange([databaseName], [{ label: databaseName }],refresh); + databaseChange([databaseName], [{ label: databaseName }], refresh); // getSchemaList(databaseName, refresh); }) .catch(() => { @@ -202,9 +205,9 @@ const WorkspaceHeader = memo((props) => { setCurSchemaOptions(schemaList); let schemaName = ''; - if(schemaList.find((t: any) => t.value === localStorageWorkspaceDatabase.schemaName)){ + if (schemaList.find((t: any) => t.value === localStorageWorkspaceDatabase.schemaName)) { schemaName = localStorageWorkspaceDatabase.schemaName!; - }else{ + } else { schemaName = curWorkspaceParams.dataSourceId !== curConnection?.id ? schemaList[0]?.label @@ -269,16 +272,16 @@ const WorkspaceHeader = memo((props) => { } // 数据库切换 - function databaseChange(valueArr: any, selectedOptions: any,refresh) { + function databaseChange(valueArr: any, selectedOptions: any, refresh) { // if (selectedOptions[0].label !== curWorkspaceParams.databaseName) { - getSchemaList(selectedOptions[0].label,refresh); + getSchemaList(selectedOptions[0].label, refresh); // } } // schema切换 function schemaChange(valueArr: any, selectedOptions: any) { // if (selectedOptions[0].label !== curWorkspaceParams.schemaName) { - setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value }); + setCurWorkspaceParams({ ...curWorkspaceParams, schemaName: selectedOptions[0].value }); // } } @@ -398,7 +401,9 @@ const WorkspaceHeader = memo((props) => { )}
    -
    +
    + +
    )} 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 8f4fdceb2..83f58c64e 100644 --- a/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx +++ b/chat2db-client/src/pages/main/workspace/components/WorkspaceRight/index.tsx @@ -311,7 +311,6 @@ const WorkspaceRight = memo((props: IProps) => { } if (doubleClickTreeNodeData.treeNodeType === TreeNodeType.TABLE) { - const { extraParams } = doubleClickTreeNodeData; const { tableName } = extraParams || {}; const sql = `SELECT * FROM ${compatibleDataBaseName(tableName!, curWorkspaceParams.databaseType)};\n`; @@ -321,25 +320,24 @@ const WorkspaceRight = memo((props: IProps) => { workspaceTabList.forEach((t) => { if (t.uniqueData?.sql === sql) { setActiveConsoleId(t.id); - flag = true + flag = true; return; } - }) - if(flag){ - return - } - setWorkspaceTabList([ - ...(workspaceTabList || []), - { - id, - title, - type: WorkspaceTabType.EditTableData, - uniqueData: { - sql, + }); + if (!flag) { + setWorkspaceTabList([ + ...(workspaceTabList || []), + { + id, + title, + type: WorkspaceTabType.EditTableData, + uniqueData: { + sql, + }, }, - }, - ]); - setActiveConsoleId(id); + ]); + setActiveConsoleId(id); + } } dispatch({ diff --git a/chat2db-client/src/pages/main/workspace/index.less b/chat2db-client/src/pages/main/workspace/index.less index 745afe676..eb0104395 100644 --- a/chat2db-client/src/pages/main/workspace/index.less +++ b/chat2db-client/src/pages/main/workspace/index.less @@ -17,7 +17,7 @@ } .boxLeft { - width: 220px; + width: var(--panel-left-width); height: 100%; overflow: hidden; border-right: 1px solid var(--color-border-secondary); @@ -87,3 +87,7 @@ } } } + +.hiddenPanelLeft { + display: none; +} diff --git a/chat2db-client/src/pages/main/workspace/index.tsx b/chat2db-client/src/pages/main/workspace/index.tsx index e583957da..76afb98c7 100644 --- a/chat2db-client/src/pages/main/workspace/index.tsx +++ b/chat2db-client/src/pages/main/workspace/index.tsx @@ -1,15 +1,18 @@ import React, { memo, useRef, useEffect } from 'react'; import { connect } from 'umi'; +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 { IConnectionModelType } from '@/models/connection'; -import { IWorkspaceModelType } from '@/models/workspace'; -import { ConsoleOpenedStatus } from '@/constants'; - import styles from './index.less'; interface IProps { @@ -32,6 +35,8 @@ const workspacePage = memo((props) => { const { workspaceModel, connectionModel, dispatch } = props; const { curConnection } = connectionModel; const { curWorkspaceParams } = workspaceModel; + const { panelLeft, panelLeftWidth } = useWorkspaceStore((state) => state.layout); + const isReady = curWorkspaceParams?.dataSourceId && (curWorkspaceParams?.databaseName || @@ -92,7 +97,11 @@ const workspacePage = memo((props) => { -
    +
    diff --git a/chat2db-client/src/store/index.ts b/chat2db-client/src/store/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/chat2db-client/src/store/workspace/config.ts b/chat2db-client/src/store/workspace/config.ts new file mode 100644 index 000000000..b2837c083 --- /dev/null +++ b/chat2db-client/src/store/workspace/config.ts @@ -0,0 +1,32 @@ +export interface IConfigStore { + layout: { + panelLeft: boolean; + panelLeftWidth: number; + panelRight: boolean; + }; + togglePanelLeft: () => void; + togglePanelRight: () => void; +} + + +export const configStore = (set) => ({ + 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, + }, + })), +}) diff --git a/chat2db-client/src/store/workspace/demo.ts b/chat2db-client/src/store/workspace/demo.ts new file mode 100644 index 000000000..1b181f5dc --- /dev/null +++ b/chat2db-client/src/store/workspace/demo.ts @@ -0,0 +1,21 @@ +export interface IDemoStore { + student: { + name: 'James', + age: 18, + } + setDemoData: (student: Partial) => void +} + + +export const demoStore = (set): IDemoStore => ({ + student: { + name: 'James', + age: 18, + }, + setDemoData: (student) => set((state) => ({ + student: { + ...state.student, + ...student, + }, + })), +}); diff --git a/chat2db-client/src/store/workspace/index.ts b/chat2db-client/src/store/workspace/index.ts new file mode 100644 index 000000000..1c9cf6ba7 --- /dev/null +++ b/chat2db-client/src/store/workspace/index.ts @@ -0,0 +1,24 @@ +import { create, UseBoundStore, StoreApi } from 'zustand'; +import { devtools, persist } from 'zustand/middleware'; + +import { configStore, IConfigStore } from './config'; +import { demoStore, IDemoStore } from './demo'; + +export type IStore = IConfigStore & IDemoStore; + +export const useWorkspaceStore: UseBoundStore> = create( + devtools( + persist( + (set) => ({ + ...configStore(set), + ...demoStore(set), + }), + { + name: 'workspace-store', + getStorage: () => localStorage, + // 工作区的状态只保存 layout + partialize: (state: IStore) => ({ layout: state.layout }), + }, + ), + ), +); diff --git a/chat2db-client/yarn.lock b/chat2db-client/yarn.lock index 32cf86e54..1daec9dfe 100644 --- a/chat2db-client/yarn.lock +++ b/chat2db-client/yarn.lock @@ -1560,6 +1560,21 @@ dependencies: tslib "^2.0.0" +"@electron/get@^2.0.0": + version "2.0.3" + resolved "https://registry.npmmirror.com/@electron/get/-/get-2.0.3.tgz#fba552683d387aebd9f3fcadbcafc8e12ee4f960" + integrity sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== + dependencies: + debug "^4.1.1" + env-paths "^2.2.0" + fs-extra "^8.1.0" + got "^11.8.5" + progress "^2.0.3" + semver "^6.2.0" + sumchecker "^3.0.1" + optionalDependencies: + global-agent "^3.0.0" + "@electron/universal@1.2.1": version "1.2.1" resolved "https://registry.npmmirror.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339" @@ -2150,6 +2165,11 @@ resolved "https://registry.npmmirror.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.npmmirror.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@stylelint/postcss-css-in-js@^0.38.0": version "0.38.0" resolved "https://registry.npmmirror.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.38.0.tgz#eabb061df932744db766f11a153ae1c465b6263c" @@ -2249,6 +2269,13 @@ deepmerge "^4.2.2" svgo "^2.8.0" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.npmmirror.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@tanstack/match-sorter-utils@^8.7.0": version "8.8.4" resolved "https://registry.npmmirror.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz#0b2864d8b7bac06a9f84cb903d405852cc40a457" @@ -2321,6 +2348,16 @@ dependencies: "@babel/types" "^7.20.7" +"@types/cacheable-request@^6.0.1": + version "6.0.3" + resolved "https://registry.npmmirror.com/@types/cacheable-request/-/cacheable-request-6.0.3.tgz#a430b3260466ca7b5ca5bfd735693b36e7a9d183" + integrity sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "^3.1.4" + "@types/node" "*" + "@types/responselike" "^1.0.0" + "@types/classnames@^2.2.9": version "2.3.1" resolved "https://registry.npmmirror.com/@types/classnames/-/classnames-2.3.1.tgz#3c2467aa0f1a93f1f021e3b9bcf938bd5dfdc0dd" @@ -2380,6 +2417,11 @@ resolved "https://registry.npmmirror.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== +"@types/http-cache-semantics@*": + version "4.0.3" + resolved "https://registry.npmmirror.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz#a3ff232bf7d5c55f38e4e45693eda2ebb545794d" + integrity sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA== + "@types/invariant@^2.2.31": version "2.2.35" resolved "https://registry.npmmirror.com/@types/invariant/-/invariant-2.2.35.tgz#cd3ebf581a6557452735688d8daba6cf0bd5a3be" @@ -2424,6 +2466,13 @@ resolved "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/keyv@^3.1.4": + version "3.1.4" + resolved "https://registry.npmmirror.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/lodash@^4.14.195": version "4.14.195" resolved "https://registry.npmmirror.com/@types/lodash/-/lodash-4.14.195.tgz#bafc975b252eb6cea78882ce8a7b6bf22a6de632" @@ -2444,6 +2493,11 @@ resolved "https://registry.npmmirror.com/@types/node/-/node-20.4.2.tgz#129cc9ae69f93824f92fac653eebfb4812ab4af9" integrity sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw== +"@types/node@^16.11.26": + version "16.18.59" + resolved "https://registry.npmmirror.com/@types/node/-/node-16.18.59.tgz#4cdbd631be6d9be266a96fb17b5d0d7ad6bbe26c" + integrity sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.npmmirror.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2478,6 +2532,13 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/responselike@^1.0.0": + version "1.0.2" + resolved "https://registry.npmmirror.com/@types/responselike/-/responselike-1.0.2.tgz#8de1b0477fd7c12df77e50832fa51701a8414bd6" + integrity sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA== + dependencies: + "@types/node" "*" + "@types/scheduler@*": version "0.16.3" resolved "https://registry.npmmirror.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" @@ -2532,6 +2593,13 @@ dependencies: "@types/yargs-parser" "*" +"@types/yauzl@^2.9.1": + version "2.10.2" + resolved "https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.2.tgz#dab926ef9b41a898bc943f11bca6b0bad6d4b729" + integrity sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@5.48.1": version "5.48.1" resolved "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.1.tgz#deee67e399f2cb6b4608c935777110e509d8018c" @@ -3142,7 +3210,7 @@ ahooks-v3-count@^1.0.0: resolved "https://registry.npmmirror.com/ahooks-v3-count/-/ahooks-v3-count-1.0.0.tgz#ddeb392e009ad6e748905b3cbf63a9fd8262ca80" integrity sha512-V7uUvAwnimu6eh/PED4mCDjE7tokeZQLKlxg9lCTMPhN+NjsSbtdacByVlR1oluXQzD3MOw55wylDmQo4+S9ZQ== -ahooks@^3.7.7: +ahooks@^3.7.8: version "3.7.8" resolved "https://registry.npmmirror.com/ahooks/-/ahooks-3.7.8.tgz#3fa3c491cd153e884a32b0c4192fc72cf84c4332" integrity sha512-e/NMlQWoCjaUtncNFIZk3FG1ImSkV/JhScQSkTqnftakRwdfZWSw6zzoWSG9OMYqPNs2MguDYBUFFC6THelWXA== @@ -3727,6 +3795,11 @@ boolbase@^1.0.0: resolved "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== +boolean@^3.0.1: + version "3.2.0" + resolved "https://registry.npmmirror.com/boolean/-/boolean-3.2.0.tgz#9e5294af4e98314494cbb17979fa54ca159f116b" + integrity sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== + bplist-parser@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" @@ -3852,6 +3925,11 @@ buffer-alloc@^1.2.0: buffer-alloc-unsafe "^1.1.0" buffer-fill "^1.0.0" +buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.npmmirror.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal@1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe" @@ -3932,6 +4010,24 @@ bundle-name@^3.0.0: dependencies: run-applescript "^5.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.npmmirror.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.4" + resolved "https://registry.npmmirror.com/cacheable-request/-/cacheable-request-7.0.4.tgz#7a33ebf08613178b403635be7b899d3e69bbe817" + integrity sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.npmmirror.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -4076,6 +4172,13 @@ cliui@^8.0.1: strip-ansi "^6.0.1" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.npmmirror.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -4491,6 +4594,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -4519,6 +4629,11 @@ default-browser@^4.0.0: execa "^7.1.1" titleize "^3.0.0" +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-data-property@^1.0.1: version "1.1.0" resolved "https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.0.tgz#0db13540704e1d8d479a0656cf781267531b9451" @@ -4890,6 +5005,15 @@ electron-to-chromium@^1.4.431: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.4.464.tgz#2f94bad78dff34e527aacbfc5d0b1a33cf046507" integrity sha512-guZ84yoou4+ILNdj0XEbmGs6DEWj6zpVOWYpY09GU66yEb0DSYvP/biBPzHn0GuW/3RC/pnaYNUWlQE1fJYtgA== +electron@^22.3.0: + version "22.3.27" + resolved "https://registry.npmmirror.com/electron/-/electron-22.3.27.tgz#b77451a53f0c502e7559cceac28ac58eb289eef8" + integrity sha512-7Rht21vHqj4ZFRnKuZdFqZFsvMBCmDqmjetiMqPtF+TmTBiGne1mnstVXOA/SRGhN2Qy5gY5bznJKpiqogjM8A== + dependencies: + "@electron/get" "^2.0.0" + "@types/node" "^16.11.26" + extract-zip "^2.0.1" + elliptic@^6.5.3: version "6.5.4" resolved "https://registry.npmmirror.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -4920,7 +5044,7 @@ encoding@^0.1.11: dependencies: iconv-lite "^0.6.2" -end-of-stream@^1.4.1: +end-of-stream@^1.1.0, end-of-stream@^1.4.1: version "1.4.4" resolved "https://registry.npmmirror.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== @@ -4954,6 +5078,11 @@ entities@^4.4.0: resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== +env-paths@^2.2.0: + version "2.2.1" + resolved "https://registry.npmmirror.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" + integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== + errno@^0.1.1: version "0.1.8" resolved "https://registry.npmmirror.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" @@ -5139,6 +5268,11 @@ es5-imcompatible-versions@^0.1.78: resolved "https://registry.npmmirror.com/es5-imcompatible-versions/-/es5-imcompatible-versions-0.1.86.tgz#e9583ad3a7a93c1b13835fb804a3bbd05e30a662" integrity sha512-Lbrsn5bCL4iVMBdundiFVNIKlnnoBiIMrjtLRe1Snt92s60WHotw83S2ijp5ioqe6pDil3iBPY634VDwBcb1rg== +es6-error@^4.1.1: + version "4.1.1" + resolved "https://registry.npmmirror.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" + integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== + es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -5532,6 +5666,17 @@ ext@^1.1.2: dependencies: type "^2.7.2" +extract-zip@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== + dependencies: + debug "^4.1.1" + get-stream "^5.1.0" + yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" + extsprintf@^1.2.0: version "1.4.1" resolved "https://registry.npmmirror.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07" @@ -5609,6 +5754,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fd-slicer@~1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" + integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== + dependencies: + pend "~1.2.0" + fetch-blob@^3.1.2, fetch-blob@^3.1.4: version "3.2.0" resolved "https://registry.npmmirror.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9" @@ -5743,6 +5895,15 @@ fs-extra@^10.0.0, fs-extra@^10.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^8.1.0: + version "8.1.0" + resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-extra@^9.0.0, fs-extra@^9.0.1: version "9.1.0" resolved "https://registry.npmmirror.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" @@ -5840,6 +6001,13 @@ get-stdin@^9.0.0: resolved "https://registry.npmmirror.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575" integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA== +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.npmmirror.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-stream@^6.0.0, get-stream@^6.0.1: version "6.0.1" resolved "https://registry.npmmirror.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -5903,6 +6071,18 @@ glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +global-agent@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/global-agent/-/global-agent-3.0.0.tgz#ae7cd31bd3583b93c5a16437a1afe27cc33a1ab6" + integrity sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== + dependencies: + boolean "^3.0.1" + es6-error "^4.1.1" + matcher "^3.0.0" + roarr "^2.15.3" + semver "^7.3.2" + serialize-error "^7.0.1" + global@^4.3.2: version "4.4.0" resolved "https://registry.npmmirror.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" @@ -5923,7 +6103,7 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.3: +globalthis@^1.0.1, globalthis@^1.0.3: version "1.0.3" resolved "https://registry.npmmirror.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== @@ -5960,6 +6140,23 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" +got@^11.8.5: + version "11.8.6" + resolved "https://registry.npmmirror.com/got/-/got-11.8.6.tgz#276e827ead8772eddbcfc97170590b841823233a" + integrity sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -6142,6 +6339,11 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +http-cache-semantics@^4.0.0: + version "4.1.1" + resolved "https://registry.npmmirror.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" + integrity sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.npmmirror.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6156,6 +6358,14 @@ http-proxy-agent@^5.0.0: agent-base "6" debug "4" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.npmmirror.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -6829,6 +7039,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.npmmirror.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json-stringify-safe@^5.0.1: + version "5.0.1" + resolved "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== + json2mq@^0.2.0: version "0.2.0" resolved "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a" @@ -6848,6 +7063,13 @@ json5@^2.1.2, json5@^2.2.0, json5@^2.2.2: resolved "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== + optionalDependencies: + graceful-fs "^4.1.6" + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.npmmirror.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -6877,6 +7099,13 @@ keyboardevents-areequal@^0.2.1: resolved "https://registry.npmmirror.com/keyboardevents-areequal/-/keyboardevents-areequal-0.2.2.tgz#88191ec738ce9f7591c25e9056de928b40277194" integrity sha512-Nv+Kr33T0mEjxR500q+I6IWisOQ0lK1GGOncV0kWE6n4KFmpcu7RUX5/2B0EUtX51Cb0HjZ9VJsSY3u4cBa0kw== +keyv@^4.0.0: + version "4.5.4" + resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + keyv@^4.5.3: version "4.5.3" resolved "https://registry.npmmirror.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" @@ -7052,6 +7281,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7086,6 +7320,13 @@ markdown-it-link-attributes@^4.0.1: resolved "https://registry.npmmirror.com/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.1.tgz#25751f2cf74fd91f0a35ba7b3247fa45f2056d88" integrity sha512-pg5OK0jPLg62H4k7M9mRJLT61gUp9nvG0XveKYHMOOluASo9OEF13WlXrpAp2aj35LbedAy3QOCgQCw0tkLKAQ== +matcher@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/matcher/-/matcher-3.0.0.tgz#bd9060f4c5b70aa8041ccc6f80368760994f30ca" + integrity sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== + dependencies: + escape-string-regexp "^4.0.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmmirror.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -7170,6 +7411,16 @@ mimic-fn@^4.0.0: resolved "https://registry.npmmirror.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-document@^2.19.0: version "2.19.0" resolved "https://registry.npmmirror.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" @@ -7422,6 +7673,11 @@ normalize-range@^0.1.2: resolved "https://registry.npmmirror.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.npmmirror.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-run-path@^4.0.1: version "4.0.1" resolved "https://registry.npmmirror.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" @@ -7543,7 +7799,7 @@ on-exit-leak-free@^0.2.0: resolved "https://registry.npmmirror.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== -once@^1.3.0, once@^1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.npmmirror.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -7600,6 +7856,11 @@ os-browserify@^0.3.0: resolved "https://registry.npmmirror.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-limit@^2.2.0: version "2.3.0" resolved "https://registry.npmmirror.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" @@ -7745,6 +8006,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -8210,6 +8476,11 @@ process@^0.11.10: resolved "https://registry.npmmirror.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== +progress@^2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + prop-types@^15.5.10, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmmirror.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -8241,6 +8512,14 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.npmmirror.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -8298,6 +8577,11 @@ quick-format-unescaped@^4.0.3: resolved "https://registry.npmmirror.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== +quick-lru@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" + integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== + railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" @@ -9019,6 +9303,11 @@ resize-observer-polyfill@^1.5.1: resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464" integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.npmmirror.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -9070,6 +9359,13 @@ resolve@^2.0.0-next.4: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + ret@~0.1.10: version "0.1.15" resolved "https://registry.npmmirror.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" @@ -9095,6 +9391,18 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" +roarr@^2.15.3: + version "2.15.4" + resolved "https://registry.npmmirror.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" + integrity sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== + dependencies: + boolean "^3.0.1" + detect-node "^2.0.4" + globalthis "^1.0.1" + json-stringify-safe "^5.0.1" + semver-compare "^1.0.0" + sprintf-js "^1.1.2" + rollup-plugin-visualizer@5.9.0: version "5.9.0" resolved "https://registry.npmmirror.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.9.0.tgz#013ac54fb6a9d7c9019e7eb77eced673399e5a0b" @@ -9234,17 +9542,22 @@ select-hose@^2.0.0: resolved "https://registry.npmmirror.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== +semver-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" + integrity sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== + semver@^5.6.0, semver@^5.7.2: version "5.7.2" resolved "https://registry.npmmirror.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@^6.3.0, semver@^6.3.1: +semver@^6.2.0, semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: +semver@^7.3.2, semver@^7.3.5, semver@^7.3.7, semver@^7.5.4: version "7.5.4" resolved "https://registry.npmmirror.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -9256,6 +9569,13 @@ semver@~7.0.0: resolved "https://registry.npmmirror.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +serialize-error@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" + integrity sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== + dependencies: + type-fest "^0.13.1" + set-function-name@^2.0.0, set-function-name@^2.0.1: version "2.0.1" resolved "https://registry.npmmirror.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -9463,6 +9783,11 @@ split2@^4.0.0: resolved "https://registry.npmmirror.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== +sprintf-js@^1.1.2: + version "1.1.3" + resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" + integrity sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -9742,6 +10067,13 @@ sucrase@^3.32.0: pirates "^4.0.1" ts-interface-checker "^0.1.9" +sumchecker@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/sumchecker/-/sumchecker-3.0.1.tgz#6377e996795abb0b6d348e9b3e1dfb24345a8e42" + integrity sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== + dependencies: + debug "^4.1.0" + superjson@^1.10.0: version "1.13.1" resolved "https://registry.npmmirror.com/superjson/-/superjson-1.13.1.tgz#a0b6ab5d22876f6207fcb9d08b0cb2acad8ee5cd" @@ -10056,6 +10388,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-fest@^0.13.1: + version "0.13.1" + resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.13.1.tgz#0172cb5bce80b0bd542ea348db50c7e21834d934" + integrity sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.npmmirror.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -10179,6 +10516,11 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.npmmirror.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== +universalify@^0.1.0: + version "0.1.2" + resolved "https://registry.npmmirror.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + universalify@^2.0.0: version "2.0.0" resolved "https://registry.npmmirror.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -10474,6 +10816,14 @@ yargs@^17.5.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" +yauzl@^2.10.0: + version "2.10.0" + resolved "https://registry.npmmirror.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" + integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.1.0" + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmmirror.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" @@ -10485,3 +10835,10 @@ zrender@5.4.4: integrity sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw== dependencies: tslib "2.3.0" + +zustand@^4.4.4: + version "4.4.4" + resolved "https://registry.npmmirror.com/zustand/-/zustand-4.4.4.tgz#cc06202219972bd61cef1fd10105e6384ae1d5cf" + integrity sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw== + dependencies: + use-sync-external-store "1.2.0" From 1758e0fb9d087bfc5bbe4bd14f1cc7f02cd363e6 Mon Sep 17 00:00:00 2001 From: shanhexi Date: Fri, 27 Oct 2023 00:08:21 +0800 Subject: [PATCH 1069/1069] clear away assets img git history --- chat2db-client/src/assets/font/demo.css | 539 +++ .../src/assets/font/demo_index.html | 4190 +++++++++++++++++ chat2db-client/src/assets/font/iconfont.css | 711 +++ chat2db-client/src/assets/font/iconfont.js | 1 + chat2db-client/src/assets/font/iconfont.json | 1227 +++++ chat2db-client/src/assets/font/iconfont.ttf | Bin 0 -> 57236 bytes chat2db-client/src/assets/font/iconfont.woff | Bin 0 -> 35904 bytes chat2db-client/src/assets/font/iconfont.woff2 | Bin 0 -> 30668 bytes chat2db-client/src/assets/img/add.svg | 15 + chat2db-client/src/assets/img/ai.svg | 30 + chat2db-client/src/assets/img/connection.svg | 17 + .../src/assets/img/databaseImg/h2.png | Bin 0 -> 12968 bytes .../src/assets/img/databaseImg/mysql.png | Bin 0 -> 2802 bytes .../src/assets/img/databaseImg/other.png | Bin 0 -> 844 bytes .../src/assets/img/databaseImg/redis.png | Bin 0 -> 3552 bytes chat2db-client/src/assets/img/empty.svg | 19 + chat2db-client/src/assets/img/login-bg.svg | 3701 +++++++++++++++ chat2db-client/src/assets/img/theme-auto.png | Bin 0 -> 3484 bytes chat2db-client/src/assets/img/theme-dark.png | Bin 0 -> 1994 bytes chat2db-client/src/assets/img/theme-light.png | Bin 0 -> 3408 bytes chat2db-client/src/assets/logo/logo.icns | Bin 0 -> 1714502 bytes chat2db-client/src/assets/logo/logo.ico | Bin 0 -> 27690 bytes chat2db-client/src/assets/logo/logo.png | Bin 0 -> 17421 bytes chat2db-client/src/assets/logo/logo.webp | Bin 0 -> 27566 bytes 24 files changed, 10450 insertions(+) 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/assets/font/iconfont.css create mode 100644 chat2db-client/src/assets/font/iconfont.js create mode 100644 chat2db-client/src/assets/font/iconfont.json create mode 100644 chat2db-client/src/assets/font/iconfont.ttf create mode 100644 chat2db-client/src/assets/font/iconfont.woff create mode 100644 chat2db-client/src/assets/font/iconfont.woff2 create mode 100644 chat2db-client/src/assets/img/add.svg create mode 100644 chat2db-client/src/assets/img/ai.svg create mode 100644 chat2db-client/src/assets/img/connection.svg create mode 100644 chat2db-client/src/assets/img/databaseImg/h2.png create mode 100644 chat2db-client/src/assets/img/databaseImg/mysql.png create mode 100644 chat2db-client/src/assets/img/databaseImg/other.png create mode 100644 chat2db-client/src/assets/img/databaseImg/redis.png create mode 100644 chat2db-client/src/assets/img/empty.svg create mode 100644 chat2db-client/src/assets/img/login-bg.svg create mode 100644 chat2db-client/src/assets/img/theme-auto.png create mode 100644 chat2db-client/src/assets/img/theme-dark.png create mode 100644 chat2db-client/src/assets/img/theme-light.png create mode 100644 chat2db-client/src/assets/logo/logo.icns create mode 100644 chat2db-client/src/assets/logo/logo.ico create mode 100644 chat2db-client/src/assets/logo/logo.png create mode 100644 chat2db-client/src/assets/logo/logo.webp 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..6d1d85e25 --- /dev/null +++ b/chat2db-client/src/assets/font/demo_index.html @@ -0,0 +1,4190 @@ + + + + + iconfont Demo + + + + + + + + + + + + + +
    +

    + + +

    + +
    +
    +
      + +
    • + 𐂽 +
      发送
      +
      &#x100bd;
      +
    • + +
    • + +
      重启
      +
      &#xe662;
      +
    • + +
    • + +
      提醒
      +
      &#xe6cc;
      +
    • + +
    • + +
      提醒
      +
      &#xe661;
      +
    • + +
    • + +
      提醒
      +
      &#xe716;
      +
    • + +
    • + +
      升级
      +
      &#xe69c;
      +
    • + +
    • + +
      快速升级
      +
      &#xe65d;
      +
    • + +
    • + +
      全局_升级
      +
      &#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=1697982809195') format('woff2'),
    +       url('iconfont.woff?t=1697982809195') format('woff'),
    +       url('iconfont.ttf?t=1697982809195') 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"。

    +
    +
    +
    +
    +
      + +
    • + +
      + 发送 +
      +
      .icon-fasong +
      +
    • + +
    • + +
      + 重启 +
      +
      .icon-zhongqi +
      +
    • + +
    • + +
      + 提醒 +
      +
      .icon-tixing2 +
      +
    • + +
    • + +
      + 提醒 +
      +
      .icon-tixing3 +
      +
    • + +
    • + +
      + 提醒 +
      +
      .icon-tixing1 +
      +
    • + +
    • + +
      + 升级 +
      +
      .icon-shengji +
      +
    • + +
    • + +
      + 快速升级 +
      +
      .icon-kuaisushengjix +
      +
    • + +
    • + +
      + 全局_升级 +
      +
      .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"。

    +
    +
    +
    +
    +
      + +
    • + +
      发送
      +
      #icon-fasong
      +
    • + +
    • + +
      重启
      +
      #icon-zhongqi
      +
    • + +
    • + +
      提醒
      +
      #icon-tixing2
      +
    • + +
    • + +
      提醒
      +
      #icon-tixing3
      +
    • + +
    • + +
      提醒
      +
      #icon-tixing1
      +
    • + +
    • + +
      升级
      +
      #icon-shengji
      +
    • + +
    • + +
      快速升级
      +
      #icon-kuaisushengjix
      +
    • + +
    • + +
      全局_升级
      +
      #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 new file mode 100644 index 000000000..26365dec8 --- /dev/null +++ b/chat2db-client/src/assets/font/iconfont.css @@ -0,0 +1,711 @@ +@font-face { + font-family: "iconfont"; /* Project id 3633546 */ + src: url('iconfont.woff2?t=1697982809195') format('woff2'), + url('iconfont.woff?t=1697982809195') format('woff'), + url('iconfont.ttf?t=1697982809195') format('truetype'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-fasong:before { + content: "\100bd"; +} + +.icon-zhongqi:before { + content: "\e662"; +} + +.icon-tixing2:before { + content: "\e6cc"; +} + +.icon-tixing3:before { + content: "\e661"; +} + +.icon-tixing1:before { + content: "\e716"; +} + +.icon-shengji:before { + content: "\e69c"; +} + +.icon-kuaisushengjix:before { + content: "\e65d"; +} + +.icon-quanju_shengji:before { + content: "\e659"; +} + +.icon-guanyuwomen1:before { + content: "\e65c"; +} + +.icon-icobanbengengxin:before { + content: "\e67d"; +} + +.icon-duihuaqipao:before { + content: "\e657"; +} + +.icon-jiaosequanxian:before { + content: "\e658"; +} + +.icon-preview1:before { + content: "\e654"; +} + +.icon-daoru:before { + content: "\e653"; +} + +.icon-zhongzhi:before { + content: "\e652"; +} + +.icon-tuichu:before { + content: "\e6b2"; +} + +.icon-kongzhuangzhongduan:before { + content: "\e6bb"; +} + +.icon-chexiao1:before { + content: "\e6e2"; +} + +.icon-xiangshang:before { + content: "\e650"; +} + +.icon-chakan-copy:before { + content: "\e651"; +} + +.icon-jurassic_edit-data:before { + content: "\e6f2"; +} + +.icon-jurassic_edit-table:before { + content: "\e6f3"; +} + +.icon-baobiaoshujuluru:before { + content: "\e7b5"; +} + +.icon-bofang5:before { + content: "\e656"; +} + +.icon-a-qingkong3x:before { + content: "\e64f"; +} + +.icon-shanchu:before { + content: "\e64e"; +} + +.icon-newdocumentworksheet:before { + content: "\e792"; +} + +.icon-file-excel:before { + content: "\e7b7"; +} + +.icon-file-markdown:before { + content: "\e7b8"; +} + +.icon-file-word:before { + content: "\e7ba"; +} + +.icon-HTML:before { + content: "\e87d"; +} + +.icon-HTML1:before { + content: "\e64d"; +} + +.icon-pdf:before { + content: "\e67a"; +} + +.icon-gerenyonghu:before { + content: "\e64c"; +} + +.icon-houtaiguanli:before { + content: "\e64b"; +} + +.icon-zitidaima:before { + content: "\ec83"; +} + +.icon-banben:before { + content: "\e70c"; +} + +.icon-cheweiguanli:before { + content: "\e73c"; +} + +.icon-dianzhelidaochu:before { + content: "\e64a"; +} + +.icon-circle-f:before { + content: "\e76a"; +} + +.icon-tubiao-hanshu:before { + content: "\e6fd"; +} + +.icon-shituguanliqi:before { + content: "\e647"; +} + +.icon-huiche:before { + content: "\e643"; +} + +.icon-quesheng:before { + content: "\e642"; +} + +.icon-jinrujiantou:before { + content: "\e88e"; +} + +.icon-youjiantou_huaban:before { + content: "\e641"; +} + +.icon-xiangyoujiantou1:before { + content: "\e660"; +} + +.icon-shujuyuan:before { + content: "\e640"; +} + +.icon-question:before { + content: "\e67c"; +} + +.icon-xingxing:before { + content: "\e63a"; +} + +.icon-kongzhitai:before { + content: "\e69f"; +} + +.icon-xingxi:before { + content: "\e639"; +} + +.icon-a-zanwushuju1:before { + content: "\e638"; +} + +.icon-kaishi:before { + content: "\e637"; +} + +.icon-guanbi:before { + content: "\e634"; +} + +.icon-xiajiantou:before { + content: "\eb6d"; +} + +.icon-gengduo:before { + content: "\e633"; +} + +.icon-shezhi:before { + content: "\e630"; +} + +.icon-duihua-weixuan:before { + content: "\e628"; +} + +.icon-tubiao-weixuan:before { + content: "\e629"; +} + +.icon-a-bianzu13beifen3:before { + content: "\e62b"; +} + +.icon-bianzubeifen:before { + content: "\e616"; +} + +.icon-biaoge1:before { + content: "\e618"; +} + +.icon-a-shoucang1:before { + content: "\e61d"; +} + +.icon-guthub-weixuan1:before { + content: "\e621"; +} + +.icon-shuju-weixuan:before { + content: "\e622"; +} + +.icon-a-bianzu4:before { + content: "\e624"; +} + +.icon-a-bianzu14beifen:before { + content: "\e627"; +} + +.icon-guthub-weixuan:before { + content: "\e615"; +} + +.icon-24gl-folderMinus:before { + content: "\eabe"; +} + +.icon-24gl-folderOpen:before { + content: "\eabf"; +} + +.icon-24gf-folderOpen:before { + content: "\eac7"; +} + +.icon-yunshujuku:before { + content: "\e744"; +} + +.icon-baobiao:before { + content: "\e612"; +} + +.icon-gongzuotai:before { + content: "\e614"; +} + +.icon-mongodb:before { + content: "\ec21"; +} + +.icon-Redis:before { + content: "\e6a2"; +} + +.icon-HIVE:before { + content: "\e60e"; +} + +.icon-Kingbase:before { + content: "\e6a0"; +} + +.icon-yibiaopan:before { + content: "\e60d"; +} + +.icon-presto_sql:before { + content: "\e60b"; +} + +.icon-shujukuleixingtubiao-kuozhan-:before { + content: "\e60a"; +} + +.icon-oceanbase:before { + content: "\e982"; +} + +.icon-dameng1:before { + content: "\e655"; +} + +.icon-proxy:before { + content: "\e63f"; +} + +.icon-openai:before { + content: "\e646"; +} + +.icon-guanyu:before { + content: "\e60c"; +} + +.icon-yifu:before { + content: "\e666"; +} + +.icon-shujuku4:before { + content: "\e609"; +} + +.icon-shujuyuanpeizhi:before { + content: "\e649"; +} + +.icon-jurassic_server:before { + content: "\e6a6"; +} + +.icon-shujuku2:before { + content: "\e607"; +} + +.icon-shujuku3:before { + content: "\e625"; +} + +.icon-shujukushuju:before { + content: "\e63c"; +} + +.icon-shujuku1:before { + content: "\e636"; +} + +.icon-peizhishujuyuan:before { + content: "\e62f"; +} + +.icon-SQLlishichaxun:before { + content: "\e80a"; +} + +.icon-zhongmingming:before { + content: "\e623"; +} + +.icon-ico_shujuchaxunyutongji_yuyueqingkuangchaxun:before { + content: "\e8ff"; +} + +.icon-clickhouse-yunshujukuClickHouse:before { + content: "\e8f4"; +} + +.icon-rds_mariadb:before { + content: "\e6f5"; +} + +.icon-jianshaojianqujianhao:before { + content: "\e62a"; +} + +.icon-sqlserver:before { + content: "\e664"; +} + +.icon-sqlite:before { + content: "\e65a"; +} + +.icon-queshengye_zanwushuju:before { + content: "\e760"; +} + +.icon-weiwancheng:before { + content: "\e755"; +} + +.icon-wancheng-:before { + content: "\e62e"; +} + +.icon-chenggong1:before { + content: "\e620"; +} + +.icon-jiqiren:before { + content: "\e70e"; +} + +.icon-huanyihuan:before { + content: "\e635"; +} + +.icon-icon_infomation:before { + content: "\e65b"; +} + +.icon-key1:before { + content: "\e775"; +} + +.icon-mysql:before { + content: "\ec6d"; +} + +.icon-oracle:before { + content: "\ec48"; +} + +.icon-postgresql:before { + content: "\ec5d"; +} + +.icon-h2:before { + content: "\e61c"; +} + +.icon-cc-schema:before { + content: "\e696"; +} + +.icon-xinjianbiaoge:before { + content: "\e6b6"; +} + +.icon-export:before { + content: "\e613"; +} + +.icon-jiaoseguanli:before { + content: "\e66d"; +} + +.icon-console:before { + content: "\e619"; +} + +.icon-24gf-folderMinus:before { + content: "\eac5"; +} + +.icon-chakan:before { + content: "\e606"; +} + +.icon-fuzhi_o:before { + content: "\eb4e"; +} + +.icon-zhihang:before { + content: "\e626"; +} + +.icon-m-geshihuawenzi:before { + content: "\e7f8"; +} + +.icon-github-fill:before { + content: "\e885"; +} + +.icon-baocun2:before { + content: "\e645"; +} + +.icon-jiantou_xiangzuoliangci_o:before { + content: "\eb93"; +} + +.icon-xinjianchuangkou:before { + content: "\e603"; +} + +.icon-loading2:before { + content: "\e6cd"; +} + +.icon-lianjiekelong:before { + content: "\e6ca"; +} + +.icon-SQLshengjiwenjian:before { + content: "\e63b"; +} + +.icon-sql:before { + content: "\e610"; +} + +.icon-lianjieliu:before { + content: "\ec57"; +} + +.icon-tiaozhuan:before { + content: "\e685"; +} + +.icon-key:before { + content: "\e648"; +} + +.icon-bofangjilu:before { + content: "\e8ad"; +} + +.icon-chenggong:before { + content: "\e605"; +} + +.icon-shibai:before { + content: "\e87c"; +} + +.icon-shouhuishangxia:before { + content: "\e790"; +} + +.icon-zhankaishangxia:before { + content: "\e7b1"; +} + +.icon-shujuku:before { + content: "\e62c"; +} + +.icon-baocun:before { + content: "\e936"; +} + +.icon-chaxun:before { + content: "\ec4c"; +} + +.icon-duigou11:before { + content: "\e61f"; +} + +.icon-check1:before { + content: "\e617"; +} + +.icon-gailan:before { + content: "\e632"; +} + +.icon-huaban2:before { + content: "\e63d"; +} + +.icon-bianji:before { + content: "\e602"; +} + +.icon-shuaxin1:before { + content: "\ec08"; +} + +.icon-caidan:before { + content: "\e611"; +} + +.icon-biaoge:before { + content: "\e63e"; +} + +.icon-zhankai:before { + content: "\e65f"; +} + +.icon-shouqi:before { + content: "\e61e"; +} + +.icon-zhuti_o:before { + content: "\eb6f"; +} + +.icon-duankailianjie:before { + content: "\e65e"; +} + +.icon-xiugai:before { + content: "\e60f"; +} + +.icon-delete:before { + content: "\e604"; +} + +.icon-gengduo1:before { + content: "\e601"; +} + +.icon-jianshao:before { + content: "\e644"; +} + +.icon-jia:before { + content: "\e61b"; +} + +.icon-hao:before { + content: "\e631"; +} + +.icon-right:before { + content: "\e608"; +} + +.icon-search1:before { + content: "\e600"; +} + +.icon-download1:before { + content: "\e66c"; +} + +.icon-xiangyoujiantou:before { + content: "\e79c"; +} + +.icon-shanchuxianxing:before { + content: "\e6a7"; +} + +.icon-cross-copy:before { + content: "\ec8e"; +} + +.icon-shuaxin:before { + content: "\e62d"; +} + +.icon-tixing:before { + content: "\e913"; +} + +.icon-shezhixitongshezhigongnengshezhishuxing:before { + content: "\e795"; +} + +.icon-zhihangsqljiaoben:before { + content: "\e759"; +} + +.icon-xunishujukuguanli:before { + content: "\e61a"; +} + diff --git a/chat2db-client/src/assets/font/iconfont.js b/chat2db-client/src/assets/font/iconfont.js new file mode 100644 index 000000000..490a42523 --- /dev/null +++ b/chat2db-client/src/assets/font/iconfont.js @@ -0,0 +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 diff --git a/chat2db-client/src/assets/font/iconfont.json b/chat2db-client/src/assets/font/iconfont.json new file mode 100644 index 000000000..d28cb9997 --- /dev/null +++ b/chat2db-client/src/assets/font/iconfont.json @@ -0,0 +1,1227 @@ +{ + "id": "3633546", + "name": "chat2db", + "font_family": "iconfont", + "css_prefix_text": "icon-", + "description": "", + "glyphs": [ + { + "icon_id": "29290564", + "name": "发送", + "font_class": "fasong", + "unicode": "100bd", + "unicode_decimal": 65725 + }, + { + "icon_id": "8765142", + "name": "重启", + "font_class": "zhongqi", + "unicode": "e662", + "unicode_decimal": 58978 + }, + { + "icon_id": "673801", + "name": "提醒", + "font_class": "tixing2", + "unicode": "e6cc", + "unicode_decimal": 59084 + }, + { + "icon_id": "13646873", + "name": "提醒", + "font_class": "tixing3", + "unicode": "e661", + "unicode_decimal": 58977 + }, + { + "icon_id": "3457210", + "name": "提醒", + "font_class": "tixing1", + "unicode": "e716", + "unicode_decimal": 59158 + }, + { + "icon_id": "3814162", + "name": "升级", + "font_class": "shengji", + "unicode": "e69c", + "unicode_decimal": 59036 + }, + { + "icon_id": "12437192", + "name": "快速升级", + "font_class": "kuaisushengjix", + "unicode": "e65d", + "unicode_decimal": 58973 + }, + { + "icon_id": "3141572", + "name": "全局_升级", + "font_class": "quanju_shengji", + "unicode": "e659", + "unicode_decimal": 58969 + }, + { + "icon_id": "4171088", + "name": "关于我们", + "font_class": "guanyuwomen1", + "unicode": "e65c", + "unicode_decimal": 58972 + }, + { + "icon_id": "1285185", + "name": "ico版本更新", + "font_class": "icobanbengengxin", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "5203239", + "name": "对话气泡", + "font_class": "duihuaqipao", + "unicode": "e657", + "unicode_decimal": 58967 + }, + { + "icon_id": "12694416", + "name": "角色权限", + "font_class": "jiaosequanxian", + "unicode": "e658", + "unicode_decimal": 58968 + }, + { + "icon_id": "1462912", + "name": "preview", + "font_class": "preview1", + "unicode": "e654", + "unicode_decimal": 58964 + }, + { + "icon_id": "11399566", + "name": "导入", + "font_class": "daoru", + "unicode": "e653", + "unicode_decimal": 58963 + }, + { + "icon_id": "10933419", + "name": "终止", + "font_class": "zhongzhi", + "unicode": "e652", + "unicode_decimal": 58962 + }, + { + "icon_id": "29570629", + "name": "退出", + "font_class": "tuichu", + "unicode": "e6b2", + "unicode_decimal": 59058 + }, + { + "icon_id": "25071396", + "name": "控桩终端", + "font_class": "kongzhuangzhongduan", + "unicode": "e6bb", + "unicode_decimal": 59067 + }, + { + "icon_id": "4890374", + "name": "撤销", + "font_class": "chexiao1", + "unicode": "e6e2", + "unicode_decimal": 59106 + }, + { + "icon_id": "1204", + "name": "向上", + "font_class": "xiangshang", + "unicode": "e650", + "unicode_decimal": 58960 + }, + { + "icon_id": "5793838", + "name": "查看", + "font_class": "chakan-copy", + "unicode": "e651", + "unicode_decimal": 58961 + }, + { + "icon_id": "10792673", + "name": "编辑数据_编辑录入操作_jurassic", + "font_class": "jurassic_edit-data", + "unicode": "e6f2", + "unicode_decimal": 59122 + }, + { + "icon_id": "10792678", + "name": "编辑表格_编辑录入操作_jurassic", + "font_class": "jurassic_edit-table", + "unicode": "e6f3", + "unicode_decimal": 59123 + }, + { + "icon_id": "10465899", + "name": "报表数据录入", + "font_class": "baobiaoshujuluru", + "unicode": "e7b5", + "unicode_decimal": 59317 + }, + { + "icon_id": "1004675", + "name": "播放5", + "font_class": "bofang5", + "unicode": "e656", + "unicode_decimal": 58966 + }, + { + "icon_id": "34080274", + "name": "清空@3x", + "font_class": "a-qingkong3x", + "unicode": "e64f", + "unicode_decimal": 58959 + }, + { + "icon_id": "4880413", + "name": "删除", + "font_class": "shanchu", + "unicode": "e64e", + "unicode_decimal": 58958 + }, + { + "icon_id": "586995", + "name": "new-document-worksheet", + "font_class": "newdocumentworksheet", + "unicode": "e792", + "unicode_decimal": 59282 + }, + { + "icon_id": "4766469", + "name": "file-excel", + "font_class": "file-excel", + "unicode": "e7b7", + "unicode_decimal": 59319 + }, + { + "icon_id": "4766474", + "name": "file-markdown", + "font_class": "file-markdown", + "unicode": "e7b8", + "unicode_decimal": 59320 + }, + { + "icon_id": "4766477", + "name": "file-word", + "font_class": "file-word", + "unicode": "e7ba", + "unicode_decimal": 59322 + }, + { + "icon_id": "4936958", + "name": "HTML5", + "font_class": "HTML", + "unicode": "e87d", + "unicode_decimal": 59517 + }, + { + "icon_id": "12904096", + "name": "HTML", + "font_class": "HTML1", + "unicode": "e64d", + "unicode_decimal": 58957 + }, + { + "icon_id": "15838516", + "name": "pdf", + "font_class": "pdf", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "37118827", + "name": "个人用户", + "font_class": "gerenyonghu", + "unicode": "e64c", + "unicode_decimal": 58956 + }, + { + "icon_id": "37118908", + "name": "后台管理", + "font_class": "houtaiguanli", + "unicode": "e64b", + "unicode_decimal": 58955 + }, + { + "icon_id": "6337464", + "name": "字体代码", + "font_class": "zitidaima", + "unicode": "ec83", + "unicode_decimal": 60547 + }, + { + "icon_id": "27193693", + "name": "版本", + "font_class": "banben", + "unicode": "e70c", + "unicode_decimal": 59148 + }, + { + "icon_id": "10021618", + "name": "车位管理", + "font_class": "cheweiguanli", + "unicode": "e73c", + "unicode_decimal": 59196 + }, + { + "icon_id": "31775147", + "name": "dictate", + "font_class": "dianzhelidaochu", + "unicode": "e64a", + "unicode_decimal": 58954 + }, + { + "icon_id": "33841930", + "name": "circle-f", + "font_class": "circle-f", + "unicode": "e76a", + "unicode_decimal": 59242 + }, + { + "icon_id": "2966901", + "name": "图表-函数", + "font_class": "tubiao-hanshu", + "unicode": "e6fd", + "unicode_decimal": 59133 + }, + { + "icon_id": "11121428", + "name": "视图管理器", + "font_class": "shituguanliqi", + "unicode": "e647", + "unicode_decimal": 58951 + }, + { + "icon_id": "12685114", + "name": "回车", + "font_class": "huiche", + "unicode": "e643", + "unicode_decimal": 58947 + }, + { + "icon_id": "36566502", + "name": "缺省", + "font_class": "quesheng", + "unicode": "e642", + "unicode_decimal": 58946 + }, + { + "icon_id": "2076256", + "name": "进入箭头", + "font_class": "jinrujiantou", + "unicode": "e88e", + "unicode_decimal": 59534 + }, + { + "icon_id": "12754142", + "name": "右箭头", + "font_class": "youjiantou_huaban", + "unicode": "e641", + "unicode_decimal": 58945 + }, + { + "icon_id": "630094", + "name": "向右箭头", + "font_class": "xiangyoujiantou1", + "unicode": "e660", + "unicode_decimal": 58976 + }, + { + "icon_id": "11520190", + "name": "数据源", + "font_class": "shujuyuan", + "unicode": "e640", + "unicode_decimal": 58944 + }, + { + "icon_id": "11891865", + "name": "question", + "font_class": "question", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "32077818", + "name": "星星-copy", + "font_class": "xingxing", + "unicode": "e63a", + "unicode_decimal": 58938 + }, + { + "icon_id": "6560687", + "name": "控制台", + "font_class": "kongzhitai", + "unicode": "e69f", + "unicode_decimal": 59039 + }, + { + "icon_id": "22785129", + "name": "星系", + "font_class": "xingxi", + "unicode": "e639", + "unicode_decimal": 58937 + }, + { + "icon_id": "36197034", + "name": "暂无数据 (1)", + "font_class": "a-zanwushuju1", + "unicode": "e638", + "unicode_decimal": 58936 + }, + { + "icon_id": "33940358", + "name": "开始", + "font_class": "kaishi", + "unicode": "e637", + "unicode_decimal": 58935 + }, + { + "icon_id": "5643512", + "name": "关闭", + "font_class": "guanbi", + "unicode": "e634", + "unicode_decimal": 58932 + }, + { + "icon_id": "4175511", + "name": "下箭头", + "font_class": "xiajiantou", + "unicode": "eb6d", + "unicode_decimal": 60269 + }, + { + "icon_id": "5978833", + "name": "more", + "font_class": "gengduo", + "unicode": "e633", + "unicode_decimal": 58931 + }, + { + "icon_id": "36150033", + "name": "设置", + "font_class": "shezhi", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "36137656", + "name": "对话-未选", + "font_class": "duihua-weixuan", + "unicode": "e628", + "unicode_decimal": 58920 + }, + { + "icon_id": "36137657", + "name": "图表-未选", + "font_class": "tubiao-weixuan", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "36137693", + "name": "编组 13备份 3", + "font_class": "a-bianzu13beifen3", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "36134221", + "name": "编组备份", + "font_class": "bianzubeifen", + "unicode": "e616", + "unicode_decimal": 58902 + }, + { + "icon_id": "36134222", + "name": "表格", + "font_class": "biaoge1", + "unicode": "e618", + "unicode_decimal": 58904 + }, + { + "icon_id": "36134223", + "name": "收藏 (1)", + "font_class": "a-shoucang1", + "unicode": "e61d", + "unicode_decimal": 58909 + }, + { + "icon_id": "36134224", + "name": "guthub-未选", + "font_class": "guthub-weixuan1", + "unicode": "e621", + "unicode_decimal": 58913 + }, + { + "icon_id": "36134225", + "name": "数据-未选", + "font_class": "shuju-weixuan", + "unicode": "e622", + "unicode_decimal": 58914 + }, + { + "icon_id": "36134226", + "name": "编组 4", + "font_class": "a-bianzu4", + "unicode": "e624", + "unicode_decimal": 58916 + }, + { + "icon_id": "36134227", + "name": "编组 14备份", + "font_class": "a-bianzu14beifen", + "unicode": "e627", + "unicode_decimal": 58919 + }, + { + "icon_id": "36134208", + "name": "guthub-未选", + "font_class": "guthub-weixuan", + "unicode": "e615", + "unicode_decimal": 58901 + }, + { + "icon_id": "7594805", + "name": "24gl-folderMinus", + "font_class": "24gl-folderMinus", + "unicode": "eabe", + "unicode_decimal": 60094 + }, + { + "icon_id": "7594806", + "name": "24gl-folderOpen", + "font_class": "24gl-folderOpen", + "unicode": "eabf", + "unicode_decimal": 60095 + }, + { + "icon_id": "7594875", + "name": "24gf-folderOpen", + "font_class": "24gf-folderOpen", + "unicode": "eac7", + "unicode_decimal": 60103 + }, + { + "icon_id": "2611804", + "name": "云数据库", + "font_class": "yunshujuku", + "unicode": "e744", + "unicode_decimal": 59204 + }, + { + "icon_id": "6607912", + "name": "报表", + "font_class": "baobiao", + "unicode": "e612", + "unicode_decimal": 58898 + }, + { + "icon_id": "6977892", + "name": "工作台", + "font_class": "gongzuotai", + "unicode": "e614", + "unicode_decimal": 58900 + }, + { + "icon_id": "15378610", + "name": "mongodb", + "font_class": "mongodb", + "unicode": "ec21", + "unicode_decimal": 60449 + }, + { + "icon_id": "3172491", + "name": "Redis", + "font_class": "Redis", + "unicode": "e6a2", + "unicode_decimal": 59042 + }, + { + "icon_id": "16300754", + "name": "HIVE_2", + "font_class": "HIVE", + "unicode": "e60e", + "unicode_decimal": 58894 + }, + { + "icon_id": "35572498", + "name": "Kingbase", + "font_class": "Kingbase", + "unicode": "e6a0", + "unicode_decimal": 59040 + }, + { + "icon_id": "35892480", + "name": "仪表盘", + "font_class": "yibiaopan", + "unicode": "e60d", + "unicode_decimal": 58893 + }, + { + "icon_id": "13487185", + "name": "presto", + "font_class": "presto_sql", + "unicode": "e60b", + "unicode_decimal": 58891 + }, + { + "icon_id": "5978710", + "name": "DB2", + "font_class": "shujukuleixingtubiao-kuozhan-", + "unicode": "e60a", + "unicode_decimal": 58890 + }, + { + "icon_id": "35222282", + "name": "oceanbase", + "font_class": "oceanbase", + "unicode": "e982", + "unicode_decimal": 59778 + }, + { + "icon_id": "15192821", + "name": "达梦", + "font_class": "dameng1", + "unicode": "e655", + "unicode_decimal": 58965 + }, + { + "icon_id": "2995206", + "name": "proxy", + "font_class": "proxy", + "unicode": "e63f", + "unicode_decimal": 58943 + }, + { + "icon_id": "33483666", + "name": "openai", + "font_class": "openai", + "unicode": "e646", + "unicode_decimal": 58950 + }, + { + "icon_id": "11542846", + "name": "关于", + "font_class": "guanyu", + "unicode": "e60c", + "unicode_decimal": 58892 + }, + { + "icon_id": "1301391", + "name": "衣服", + "font_class": "yifu", + "unicode": "e666", + "unicode_decimal": 58982 + }, + { + "icon_id": "6850413", + "name": "数据库", + "font_class": "shujuku4", + "unicode": "e609", + "unicode_decimal": 58889 + }, + { + "icon_id": "12993182", + "name": "数据源配置", + "font_class": "shujuyuanpeizhi", + "unicode": "e649", + "unicode_decimal": 58953 + }, + { + "icon_id": "10594601", + "name": "服务器_数据库_jurassic", + "font_class": "jurassic_server", + "unicode": "e6a6", + "unicode_decimal": 59046 + }, + { + "icon_id": "1817682", + "name": "数据库", + "font_class": "shujuku2", + "unicode": "e607", + "unicode_decimal": 58887 + }, + { + "icon_id": "8765123", + "name": "数据库", + "font_class": "shujuku3", + "unicode": "e625", + "unicode_decimal": 58917 + }, + { + "icon_id": "9710796", + "name": "数据库数据", + "font_class": "shujukushuju", + "unicode": "e63c", + "unicode_decimal": 58940 + }, + { + "icon_id": "1305114", + "name": "数据库", + "font_class": "shujuku1", + "unicode": "e636", + "unicode_decimal": 58934 + }, + { + "icon_id": "1472570", + "name": "配置数据源", + "font_class": "peizhishujuyuan", + "unicode": "e62f", + "unicode_decimal": 58927 + }, + { + "icon_id": "31104431", + "name": "SQL历史查询", + "font_class": "SQLlishichaxun", + "unicode": "e80a", + "unicode_decimal": 59402 + }, + { + "icon_id": "6550626", + "name": "重命名", + "font_class": "zhongmingming", + "unicode": "e623", + "unicode_decimal": 58915 + }, + { + "icon_id": "6607690", + "name": "ico_数据查询与统计_预约情况查询", + "font_class": "ico_shujuchaxunyutongji_yuyueqingkuangchaxun", + "unicode": "e8ff", + "unicode_decimal": 59647 + }, + { + "icon_id": "27571255", + "name": "clickhouse-云数据库ClickHouse", + "font_class": "clickhouse-yunshujukuClickHouse", + "unicode": "e8f4", + "unicode_decimal": 59636 + }, + { + "icon_id": "13592725", + "name": "rds_mariadb", + "font_class": "rds_mariadb", + "unicode": "e6f5", + "unicode_decimal": 59125 + }, + { + "icon_id": "8817896", + "name": "减少减去减号", + "font_class": "jianshaojianqujianhao", + "unicode": "e62a", + "unicode_decimal": 58922 + }, + { + "icon_id": "7119672", + "name": "sqlserver", + "font_class": "sqlserver", + "unicode": "e664", + "unicode_decimal": 58980 + }, + { + "icon_id": "12600909", + "name": "sqlite", + "font_class": "sqlite", + "unicode": "e65a", + "unicode_decimal": 58970 + }, + { + "icon_id": "26519722", + "name": "缺省页_暂无数据", + "font_class": "queshengye_zanwushuju", + "unicode": "e760", + "unicode_decimal": 59232 + }, + { + "icon_id": "1330772", + "name": "未完成", + "font_class": "weiwancheng", + "unicode": "e755", + "unicode_decimal": 59221 + }, + { + "icon_id": "5979977", + "name": "完成-01", + "font_class": "wancheng-", + "unicode": "e62e", + "unicode_decimal": 58926 + }, + { + "icon_id": "24056325", + "name": "成功", + "font_class": "chenggong1", + "unicode": "e620", + "unicode_decimal": 58912 + }, + { + "icon_id": "16323142", + "name": "机器人", + "font_class": "jiqiren", + "unicode": "e70e", + "unicode_decimal": 59150 + }, + { + "icon_id": "6234556", + "name": "换一换", + "font_class": "huanyihuan", + "unicode": "e635", + "unicode_decimal": 58933 + }, + { + "icon_id": "26130627", + "name": "icon_infomation", + "font_class": "icon_infomation", + "unicode": "e65b", + "unicode_decimal": 58971 + }, + { + "icon_id": "6150969", + "name": "key", + "font_class": "key1", + "unicode": "e775", + "unicode_decimal": 59253 + }, + { + "icon_id": "5961392", + "name": "mysql", + "font_class": "mysql", + "unicode": "ec6d", + "unicode_decimal": 60525 + }, + { + "icon_id": "15378663", + "name": "oracle", + "font_class": "oracle", + "unicode": "ec48", + "unicode_decimal": 60488 + }, + { + "icon_id": "15378704", + "name": "postgresql", + "font_class": "postgresql", + "unicode": "ec5d", + "unicode_decimal": 60509 + }, + { + "icon_id": "19657927", + "name": "h2", + "font_class": "h2", + "unicode": "e61c", + "unicode_decimal": 58908 + }, + { + "icon_id": "372246", + "name": "cc-schema", + "font_class": "cc-schema", + "unicode": "e696", + "unicode_decimal": 59030 + }, + { + "icon_id": "1789259", + "name": "新建表格", + "font_class": "xinjianbiaoge", + "unicode": "e6b6", + "unicode_decimal": 59062 + }, + { + "icon_id": "15550146", + "name": "export", + "font_class": "export", + "unicode": "e613", + "unicode_decimal": 58899 + }, + { + "icon_id": "693661", + "name": "角色管理", + "font_class": "jiaoseguanli", + "unicode": "e66d", + "unicode_decimal": 58989 + }, + { + "icon_id": "11348953", + "name": "console", + "font_class": "console", + "unicode": "e619", + "unicode_decimal": 58905 + }, + { + "icon_id": "7594874", + "name": "24gf-folderMinus", + "font_class": "24gf-folderMinus", + "unicode": "eac5", + "unicode_decimal": 60101 + }, + { + "icon_id": "201556", + "name": "查看", + "font_class": "chakan", + "unicode": "e606", + "unicode_decimal": 58886 + }, + { + "icon_id": "5387764", + "name": "复制_o", + "font_class": "fuzhi_o", + "unicode": "eb4e", + "unicode_decimal": 60238 + }, + { + "icon_id": "10936703", + "name": "执行", + "font_class": "zhihang", + "unicode": "e626", + "unicode_decimal": 58918 + }, + { + "icon_id": "21164516", + "name": "m-格式化文字", + "font_class": "m-geshihuawenzi", + "unicode": "e7f8", + "unicode_decimal": 59384 + }, + { + "icon_id": "4937000", + "name": "github-fill", + "font_class": "github-fill", + "unicode": "e885", + "unicode_decimal": 59525 + }, + { + "icon_id": "8626141", + "name": "保存", + "font_class": "baocun2", + "unicode": "e645", + "unicode_decimal": 58949 + }, + { + "icon_id": "5387931", + "name": "箭头_向左两次_o", + "font_class": "jiantou_xiangzuoliangci_o", + "unicode": "eb93", + "unicode_decimal": 60307 + }, + { + "icon_id": "1371", + "name": "新建窗口", + "font_class": "xinjianchuangkou", + "unicode": "e603", + "unicode_decimal": 58883 + }, + { + "icon_id": "4503683", + "name": "loading", + "font_class": "loading2", + "unicode": "e6cd", + "unicode_decimal": 59085 + }, + { + "icon_id": "2505938", + "name": "链接克隆", + "font_class": "lianjiekelong", + "unicode": "e6ca", + "unicode_decimal": 59082 + }, + { + "icon_id": "11661836", + "name": "SQL升级文件", + "font_class": "SQLshengjiwenjian", + "unicode": "e63b", + "unicode_decimal": 58939 + }, + { + "icon_id": "15214527", + "name": "sql", + "font_class": "sql", + "unicode": "e610", + "unicode_decimal": 58896 + }, + { + "icon_id": "5961312", + "name": "连接流", + "font_class": "lianjieliu", + "unicode": "ec57", + "unicode_decimal": 60503 + }, + { + "icon_id": "16853978", + "name": "跳转/退出", + "font_class": "tiaozhuan", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "1433771", + "name": "key", + "font_class": "key", + "unicode": "e648", + "unicode_decimal": 58952 + }, + { + "icon_id": "11372646", + "name": "播放记录", + "font_class": "bofangjilu", + "unicode": "e8ad", + "unicode_decimal": 59565 + }, + { + "icon_id": "3640170", + "name": "成功", + "font_class": "chenggong", + "unicode": "e605", + "unicode_decimal": 58885 + }, + { + "icon_id": "9626932", + "name": "失败", + "font_class": "shibai", + "unicode": "e87c", + "unicode_decimal": 59516 + }, + { + "icon_id": "688104", + "name": "收回 上下", + "font_class": "shouhuishangxia", + "unicode": "e790", + "unicode_decimal": 59280 + }, + { + "icon_id": "688143", + "name": "展开 上下", + "font_class": "zhankaishangxia", + "unicode": "e7b1", + "unicode_decimal": 59313 + }, + { + "icon_id": "970749", + "name": "数据库", + "font_class": "shujuku", + "unicode": "e62c", + "unicode_decimal": 58924 + }, + { + "icon_id": "5399558", + "name": "保存", + "font_class": "baocun", + "unicode": "e936", + "unicode_decimal": 59702 + }, + { + "icon_id": "5961297", + "name": "查询", + "font_class": "chaxun", + "unicode": "ec4c", + "unicode_decimal": 60492 + }, + { + "icon_id": "150096", + "name": "对勾", + "font_class": "duigou11", + "unicode": "e61f", + "unicode_decimal": 58911 + }, + { + "icon_id": "1288113", + "name": "check", + "font_class": "check1", + "unicode": "e617", + "unicode_decimal": 58903 + }, + { + "icon_id": "14151855", + "name": "概览", + "font_class": "gailan", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "21918566", + "name": "概览", + "font_class": "huaban2", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "201638", + "name": "编辑", + "font_class": "bianji", + "unicode": "e602", + "unicode_decimal": 58882 + }, + { + "icon_id": "4686545", + "name": "刷新", + "font_class": "shuaxin1", + "unicode": "ec08", + "unicode_decimal": 60424 + }, + { + "icon_id": "8084544", + "name": "菜单/列表", + "font_class": "caidan", + "unicode": "e611", + "unicode_decimal": 58897 + }, + { + "icon_id": "1305461", + "name": "表格", + "font_class": "biaoge", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "4867114", + "name": "展开", + "font_class": "zhankai", + "unicode": "e65f", + "unicode_decimal": 58975 + }, + { + "icon_id": "4942664", + "name": "收起", + "font_class": "shouqi", + "unicode": "e61e", + "unicode_decimal": 58910 + }, + { + "icon_id": "5387845", + "name": "主题_o", + "font_class": "zhuti_o", + "unicode": "eb6f", + "unicode_decimal": 60271 + }, + { + "icon_id": "11195023", + "name": "断开连接", + "font_class": "duankailianjie", + "unicode": "e65e", + "unicode_decimal": 58974 + }, + { + "icon_id": "6010907", + "name": "修改", + "font_class": "xiugai", + "unicode": "e60f", + "unicode_decimal": 58895 + }, + { + "icon_id": "10142371", + "name": "删除", + "font_class": "delete", + "unicode": "e604", + "unicode_decimal": 58884 + }, + { + "icon_id": "77822", + "name": "更多", + "font_class": "gengduo1", + "unicode": "e601", + "unicode_decimal": 58881 + }, + { + "icon_id": "4511969", + "name": "减少", + "font_class": "jianshao", + "unicode": "e644", + "unicode_decimal": 58948 + }, + { + "icon_id": "5334173", + "name": "加", + "font_class": "jia", + "unicode": "e61b", + "unicode_decimal": 58907 + }, + { + "icon_id": "7424757", + "name": "加号", + "font_class": "hao", + "unicode": "e631", + "unicode_decimal": 58929 + }, + { + "icon_id": "11364467", + "name": "arrow drop down", + "font_class": "right", + "unicode": "e608", + "unicode_decimal": 58888 + }, + { + "icon_id": "11931077", + "name": "search", + "font_class": "search1", + "unicode": "e600", + "unicode_decimal": 58880 + }, + { + "icon_id": "15838464", + "name": "download", + "font_class": "download1", + "unicode": "e66c", + "unicode_decimal": 58988 + }, + { + "icon_id": "22303251", + "name": "向右箭头", + "font_class": "xiangyoujiantou", + "unicode": "e79c", + "unicode_decimal": 59292 + }, + { + "icon_id": "27901498", + "name": "删除线型", + "font_class": "shanchuxianxing", + "unicode": "e6a7", + "unicode_decimal": 59047 + }, + { + "icon_id": "28093125", + "name": "cross", + "font_class": "cross-copy", + "unicode": "ec8e", + "unicode_decimal": 60558 + }, + { + "icon_id": "5667975", + "name": "刷新", + "font_class": "shuaxin", + "unicode": "e62d", + "unicode_decimal": 58925 + }, + { + "icon_id": "22303207", + "name": "提醒", + "font_class": "tixing", + "unicode": "e913", + "unicode_decimal": 59667 + }, + { + "icon_id": "6129178", + "name": "138设置、系统设置、功能设置、属性", + "font_class": "shezhixitongshezhigongnengshezhishuxing", + "unicode": "e795", + "unicode_decimal": 59285 + }, + { + "icon_id": "19113301", + "name": "执行sql脚本", + "font_class": "zhihangsqljiaoben", + "unicode": "e759", + "unicode_decimal": 59225 + }, + { + "icon_id": "20104543", + "name": "虚拟数据库管理", + "font_class": "xunishujukuguanli", + "unicode": "e61a", + "unicode_decimal": 58906 + } + ] +} diff --git a/chat2db-client/src/assets/font/iconfont.ttf b/chat2db-client/src/assets/font/iconfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c39a639b270d11a6aec6269acccaafaac6d57e44 GIT binary patch literal 57236 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
    fuQNeC^#ZXx`@XWx+-1 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

    WldNR)DgL5mJoplYN#O>wDr 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�RaE>&Abjz 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�zhJ?`n#YjaU1}Nh+uuO)Y;%Dr7Z|dEt-Z1nSDq| zars@)8k(9uUFlb3RiD!8TJ`f)`lqfAX`wW-?9q1WglfOO!^(uon(Ja zpr}3V1<&Kg1-!!n0ZGSjo+TX8{`41q(H=Z}#QOf^7p-9)_4(14Y_+4+9=+#~ zZ909z4x*+`!<3%_MQ-@s2kq#cJMCnKRV2CNS?^dL0vex(9Ktvh2@e@{xyYq7G2Inb zV5FucNj%NQ$~>nbNh4{Ndb{&oo3Yh)4<|g-8T7eRHXil}kSkTL@09)&y+hURzS-%ga*Qh|*S`cbsQI zC?Ree^#pxJIW#Eu#E;v*srwzmD_;QYC1!seeB54(Z?QLmc1$rF@S*6($xa-jj0!l5 z38#tnXhWfpvy&~@^+7cpBs1GEAV7OD2mD0g%XUkC43xN&Hj8HA-w?ljxM7m}gZze5 zVN(%6p28|XL4(9+;~W@FP~VVUNOyo(S8x5~Q8*o~gV2}keXE!4eX~bx%ls&^5#x+w zY4GH8FsT`Yrwr=mW>#)x2zHWZkD!?->Ag^QufiFVfH@_WA&ejwy82wgtSGpv%-Pqb z$x-W@@W|i)@A?V3eqT~N<6U0&U^h-@Hgg0$2z1m>a~QK>hHMkh&Mt+Jvm5e8XeETnO0 zZSO-6s+%EEB)&SJMb1tFF%6>+z}%>CZJB!p68H&~jNv6HBt;2Oc|sglF9OzuW+#o- zEZ5DD7kb_mIdwAjW21zB=E?hjHl9})!Kv`~mp)=g^Y103lQ3FT^M(d#aWG|{-TF~v z=aTJVe%?pA=>ISTymtO|`^mrfPu4+pzbn0Uc6sw&`|H-2?cUaIEf2rTHCLI{Iop2r zDg0qd_GaC??cNtp*r7A8TWU7T*dm)Gnib(jOuzd_>@slJv%S0Rdm6{FZMW=i?Q>^;=`(?Qc%HDPH?tQ`~Y)kL4t2U3C&L)|Xj# z>K(az!w3Kx4_6ts#zr_0G*s3Puxn_H2Ah|w4H|vxbCvmrEBb2RQr0odb&d8l8miYz zZuM(@bTEt8#7ByRv?v@=s>-j?ab2$m4=+^>+N83KvtpG7TLOnZdIz~jZGqXI!%=QT z;ecd%6F>FTxxKBHeC3qwJ@J|y96n!dPTJHa+)vtKlhl8*X^~gu@l%F z%a+{puzib3e~M7Sp-=vly)d{FAq7TB?0RTVhoC|Qikqae8ZLYDy^ z=vvv)eG_qa^*!HeAGK?>z5Kk|T=if__c$(T6!cCoHF6SWU*!cI+*D9eeM7%T1%cZQBieELEZwmieH|#_aakMLWT2@qTRD zgG;Aj+(jECx5G9}IWMp>Y$k!u@#H?-6SmlG%Oij^owhsXiSVc&VAZumD&AQa47-CP z_)f;{LgP;RO5yoUyiPqDKf9UF~hR zrGbpik?nA0HV1+v4s3*4#t03KgNoROCB)kb=Q1HIM&xXXQ=#_(jLnzE;bfy=b>Oo1 z58tRm)x33s@w;#=hKlEUiLl2w?-f^62%h=gr*FMRS^+d+0kP;s{1nPEnW0YODi2q5 zO<<0j%_39~^ByD8q0BsxFGRc%(;32D1STF~EEYgjEHB{HSE;uqpr%W-P4>bB$|;<3 zLHL=UAp0S0mjq`)vWf&mg%wH$Vc8J27jw0YU{zr5MbX~nH_swGljV=$h;(iBJ&f4Y zFzWP*;W!T@C_NZf1h{@jhWr1#-I6ZU-`~1_z!EX@56Wg)P{psO#t*@jj_vysl@>Sz!Pau)Vu%>ij7yG^Xv?#x`rm0jwFm_xkojcH&M{k${!VV2hnsgKVX-L>o4bE<~Ulp&0O(M^5{nm>NaxO-i5cW&_Aj86%l|xgRAs)&Q-c<6*dIt) zTks${8SKBPhGo#HpvB;*Bl9aGJv@dl3AuNA`tNmeH{TKATkY8TcW)cMkkJ@xgbGRjLU6oYFK@= zRr_phU%yqpc5dD8^_Tf|Do6e6_w^gbiT#T{WDF(nM`$3S5jgtEI%$kx{+e60Yjm61 zzN!-j09q$8bpV2$9VjL}ruUg(PRuhFkZq9a_;8HKhhQ2Fzyfn?k+471^uauE)o4W$ zFugb?q5_GOR#xV01+!V2JPfGd>BBWe0Tg6dzge%X952i zm-YzpcCEGt_o`3C?fbWCM_f9L{>@X@{vuFF75S zZ2v4Fsjt3iw}A|qxH8PMAP_Wn+k9iAO>G_kNDM)+r4>xBHrfxNxUL0%T;4@m3Teoo zN2r$&5c$^pmhi54Ncsy|7;J+5@+0~+l2$5kD6R_TGL%;BrFB2 zkbKH>PTy;r%J>OgClii5^z|KnyxS?`!$IGam07@PhJWi=_*?f1_rv>!H{$N<6SNf` zMIb_1z0Pwqphq2NeT$?SRUP!Dz6l z&qeS!5i(~Vh5XhC6{Ch zO{7A1=@_}sI}r|0C+7h{2>@*YNXHwOzMA4bucFsIJ-Uf>y?NV%y87o^p0$5wkJ{zR z8}|FGUcXYoG}6#!f0_G`{c7bY>rcJ{GaqCjNK)5q7LcC>j~#z+@SKfd5*SHuwO=3H zVSU{o^i_xqAnfYTn!4=|8g2p5>3*EWO4iIucNoI_+29WQT3fsA#HsN>Y{>4-Px87k zd$oVFz0r~ed2o_E0k7GNLbm{!$paR11mK?~grXevl1;FW6=q<$g`}@L5*)6U+9-*rIhe-(s7Zo9zXl zgI`0+NX+3|urh1g$H_c7127gLq0L0+OiwTJ9tcF6yDXbUOQ1*{OnYSs{Fse4;@o?1 zTf~kb7#tn!X0@+AqOAfs%_2b|czZ4lc{yrKKwFm7XSA2lBDIKdIAG4y=C^F@YIZSW z4XLo}b)fpgkLUt^`(H8WaL?(cE?(QJ`=Y~?@x>VD+^o7*X5CKpTHX7)o6hU;IVo87 z+B00Y>yTocUUhMGu&!t>?br`N1A^J}pi=Kc1i3lyPdPc!>BTh4^wGXnmFl2s|L_o;xXvblp|*L0 z@^}vp#&E1O zaMD}x00%O~y0GK^RMR-lT6z1{+*bS7v5y0r`Z2q+IAK4Y`KIkozhIB&4&lSU$9}s1 zCs3C@U=QYwfvDDMkF|H=#2MJG{1dhhCjJ9+lkV#=Pt10a72FY!84K~tHq+v@J=J6Xl@fd7K*4 zXn2yOU|vA2+b%2yR%{hkbt@n-M$ksYxXuLtwY^qGKGtb_^_x#HzP}e+t^2u!PcD5k z{sV-lA~vcR_U8zB45CC+1OO046e8-vqZhpP31IFK#+|@~JTcQ8<5LnM^DV*{=0&KQ z7m!n~gjUUJjVEc-8t@B9A{^pu7;8xqIw>bxLMWL#S`8|rT?Or#!UT(W^d}g1nCjX1 zgPNb|MLA_Lq<(G*h@_>uIg1NOiSzZd^&okYtr4N547d@1dY0BuB(KB_X1W~x*+I0- zgTWbmLy%+el-Ss|O%F=(}YwVjGw%zlS1bGDb!(awFe%Q{=&e&$gZv{vS7O|7rV2Y5``#jj?@d;NZ`o@FNNUY22APtW=EKKRyh3 z(HgbUH`2F$L)e_XYhY^S=mT_JIWciQ8p`f`B!Z|i)nn|jiIIo*@Z>^FU^fi6L^X!eo;PK;JrFVU;T=U>54w-N^CYl(a>#%@-ktXW?B-lEgu6x7I$s zOV)Yngx!4f71oo>7N`6wIz=E(2=!w!YXPKCI7#ZP^nD+VL6k|G;(a4b_zKUEpng4C z9F?f-aw{EBxVdQwZeA7E?-#^-~e*8eSOzq3-(+jB#&nMNs1P_JfiSg^%L}_Ak9OGPp<8pM6 z_5)`(SFb3BzsR*9{l$_OtpN}F(h89jF*GTl2Uft@U&5|gmSZ6*)O-#HLIjHKsTE@C zi6(GEFZC;Mo_;FBkd&}yHJG*Av%~g@m1Fj^l|M&5ShBCOf_npkJ%opTAFIygY=d3G zMxRXKph?+>E{(th@I#ozCxcl0r#iP0?a^c{Xic6;A#fqM>>wR)XL1;SxDm_dK!eP6 z0LM$rKMd>yz%>&%>>60%4gvH$G-YUn^aUj0sI%gYbX4q3RF+L z&9AC3?1G^fi@2wp=^7b7?Wol25#BgdVrB2Ebj&58n(R9DYbY`zLF*vyjv!saCq(%GR9@n;vKS^2i16q4aTKBLBB=Z& zph)mhradwsXw+F{lQy`6X~LmWkF7S&$#1 z{9tE@lxe`>G0B|$M&0dpj*OFg3EBHZ{_FPO>^JQ6G-!Ny`N-Ggc-P%_APJ;zeZ`*I zdB1hyqf=^#;LovW_fHN%M6!&>gzbLuxb+;lK&pg-H4}ncsLR+G!eg3oEzMv8fxTWH z{s{3IB%C$~LK%T}>!mT<^y*oFj7U+ul}HSzi$gH`oZ?}%A=oDgvAh*$!p=DyRspC= zAy7lf!YK*8kgvT90u)zI=@b6pIDHm6cj{oc)ztstc&pCs>N$0>m#5EbLsX~uRV`6H zzxM0eowa*Wk9+^q-L+>4hsf1>)?ay}nE-AW0pREQZvB}=G}iB8r^X8Zw1RQ=UL9QC z>E5r^;a6q&DkWy^BT0;Ft?wx(CdqjxEF2F#SXIVr4o8iyp_F_I#22p$0~(Iuz5JUQ z?jeB?J(crk<=Mo56)zhw;T&FA-!K=<)tJf=007%q1PXPL74{VfFx)bv{lv~#_$006ERG2`dNLUUp2&@SN3dAuS<6^9A;;10y;1%EmR3bJ? zs=ES=YDqRvK!(!Pla}4IllW^Si9Cd`B%3ETNT>wQz)Lhj0NS{*9RFC^c$_`V$!C*B zmv*Z^L*a+t+SDxGZ0v+h#Bc3aKi)uI^4dRg>Qi}`v(mphcfhOOL4V{mE^Z-Vhm2*e z2@pY6w1Aj^7{9axieDlL&yQ{VBl}3|aeHOzVcs2ErZV|~>2EM;9|I0lMDzp#K$83f zS=y$A@Pt7X;fs1urOJI_o;DamD>DZQT;EbFu*W%jp!}wNIDX7V`2BQtE13P@BSf#* z9~M7rKfm;c_Q~=ipasH+<1=8puiE?U4ZANlW?yUAfz1~`4SW}d2>m(}+3O|-Aqzgt zGdJRkaGorV#Td&=(isau@KmzRMrryta3=gxPY-?pTkQn?2(u9VSbW5KR)F~hFQR}U zp#er(CVxO06HUC_17cgFUE0)X7YCX#IhCw!K?sy+4z%hOq4z#fyH+<*a8bxwKdp5h z$2j%(-Zb%Crr-}NZmGsAto|d7*yM$0v4E->htr6>_cb9T zelwoEzx6F4RlD8C~$ukt!VKFK+B^`6)h3N zUm}VhOCVnsJ{8hA2ly;R^5h*@mXAO)T8I?Rm%s=!UJKK_6KxyWgb0D02|u<9_ITYs z2EY!;JE--`U_@Y5Tk1etQ->?ff-Qnmu%U9<+QB_I9(~AqsB^Hegx?4$oe?b0cJ5>x zRP43#W;@+5VBPJz?d*|xusC{&5*WiDXpk;As@G-s2a6uU@fP#!FfGIwVku9?;yv z>PXRUhcG9!6D`z0L`_n_Q)onF#`LzMU?_J%QRyGiV|5e%*S0mr%n`}?61()*%c*Sj z8eQOJ-y#5nBqXdmG`Fgx%GRz8&xJ>+*#$f95pMO6R@%?Tj`uNI+N4IaJjx_VuR zs4;H|64i4Q)V`cdRQ{>}z15r-*Lr&L|uf1203gMDOwl0Lu+%0Q7vx}C+&c8w^P&dhbwolighA!Bs@cn@T4ddr|K&j(}_=nS( z&m0j9y_ASCyKYm}tP~Lr8;|emzU<4j{n|M$@lB0>y-mUe?Sr11vlP-QM4?Iw>ekXK z1Dq{{s0pUAvEdb~s{;<%)%~=Y?gu2V91xr4PumYQj9G3NhbbZi((&`CQz7V$(+CRG zi3vr%0#a`#Aab(^5Cw1p2wXzlp0$qBxNS{bVexndLXCR5v>kwy1yBG}HWTZ!->y4^ zIl9h%vg17OXvF_7(Nqz;vqJji$VE{5_Sl7Fw~ZzGZHCaY?M-hG4RF}*M3ZnT*h)Cz zKJJaMhACqkj(eT7@7Wky7@qakGQ^!@P2eg7`8o$%yr(j3pUj`Nt&s)fvy{D@>9faL zH{0Rny|x_RYL_wt_Ikq>UZ=qZ7Et{X3Kv@?!$2Ot2v%-Qn3%dqnA5lnNDV@i$3d^P zf3=@{vZ&ds{ii1J_uyELRo|p;|%Q&|Brwx8)9(4fu%*_ z_C5vQdL#G3m9R%Ws!z`=^M%Huz7Z~DT8Ol$pP3Q#Z$!vE8k+*BqV6c(cu@qv2!e48 z2<{m6d4=T0FvDqxiD?HhN!OAFbS=jTsf=Pi2{5w+_!FcM9*PLv5)P9Hb1}vvet5RR zXNfr>ys4C;8Nl}_0rBn6%l(w3Xo1L(IZQn{#&`;Qdp%}{P32MBln0WUP`f(n8pZeK zO5@G;dSWw4o_5#>(813~x7y3I_;-+YcyjxI4NShl`(^9^PQiCS^$2N+30veD3p1C1 zCGN3?%pj2o%zF<*jN`Bzu%=k)q-IyFZUOKYyl{0p&pANlBC1d(h#28=e9Img$g>!meS)$&m*nO#)a#W+ z5W-HM!!{kYrS1*ZGjrJn@qoVAz5#+TV+%wiRLC#y4pdr|NwqE=rV~?c3i0!g+iThP z#!(rbNDo!p)=#vxj#3#uYrn%*^-EZ#IHM5Y3ymcO>Aa~}Z6XD82pk$1UFTWq!rSOy<2 zK4DQX&<{8F+PTU`%OH6CpOq(V^T{Lj%1zsG$egnCb8p&jB|d3y^flQ@946CQ%m{dE zKbzfSv&)a#4|RSGkLY{scL?$NXLbL|KAPWczq#~PQYtUo3-vvyceh*XA`usO_cve) zxQBQ7X8JTe1~3bF10$q}m4@hKfba!Y!eC*-KHl(x4X%E}wy{e6+odgbEcSqXi4e6d zAm-H-vi80rNrCVInISvk^CWLMiqMv4Z2clC<^5CCgA<$5BWJ*n*p7{PbsUf&V&x-f zdZdNYiaQkAm=kJ#AqA@A#Podf@TKGWtaYgRmjgbZD7$uC`(99m*KInk`@mV{3eS)Z zSbYX?&N3?GnAH86Fd9?x9Z?-xN;0>uyUG6U_UY>Vd<<2eUn_pJHUSwZi}+S3yiu$5 z@*=BrggVVN&EpsXWsJETaq|HVg{n%-5ay;=kHHMfOtc~8dsyf;%jrkxi5G&&Bb5Z(5NPudg z>kb1QzwUOuF;gvA?bf$mw*Ct5I}Yd=qs+JPJb(3QPMp;*;Ze~W4H zEnhqQ4*tGj1b_qsO_NY7e|4cd@~LCgS3WuQgELMP!hKj`xZ^}kJrkwi4zl#ggZTxvoHAGef={!GOJ;#f1rQ}bLXWu366HVV z%Ed4xYy@Jb-w9UpC9PZ_z$*}v00c9wp7q=XoYE55$9t}fVO!p4)BU6b1wY`uFTacf zTE<=gcVJ}W9jponZ-Q`pSb=h~ivfuF>(%t(XZS}?uk&g@9jk3#ApEg*yjG3_K1ubh zBKiX%_dZ4i=>!*|shD*Zi;4v4z(3;nY}|$-F!LNvOKWXRyYc*SnBv39m+TI*v3&`r zzAvoqCG9#n5b*N%2VdPt{oN3KF2ysvWuno3VdzhYddk}4ZSS@(M0eOQ>0N(dKX~ZN zr>rM-)DFfE+8^i7qnX)0dtN0=N z1ddp@PhYW-rgobu6K9^eKwq@kuf^}Mqu9MS0`q$(c>$JiOp`BLtk8{THnHIO3wZvw z*uTsDur(%o?XQ>rz&?HDPi-##l3l9bW&gJCZu?sVivM=uwEf`9H(@yQHafe_ej|Fb z9VV;ZebZyCJ{N4dqZ9BM+74B&A_ig%I%T!2VB40nsrM61A#hC#3{f_5RNqK{w2uTy zJ|Ob>psdab|Dpc!4yL@0`%n6h-L=ij)P8YJlcyf8r_7O!V{3Sv}UnLTD8cSTn&RM_hoqOIc5+JcK(S|%VXZ<`!4_bqvEk9rp9RDJZK7oCo z=c^|QfjUY!6VoCfLek!-?QsksZ8m-ZsQ#sa6rd4rL39Aj9|pcCnrw-1TnXh`@uhNO=# z4FnGPxlkAg>++MTxVq_D4L1m@s-RwdB#%gYP=-LdqYD$HT7CG~QzIl?$Bp`r#B@sd41PD@Cd3qvKGoa3mSZ@*x_42%Z6BNMN4YxrUH?p$k zWn@!T+_sucD|FpRudZ3Hdx#0^n6|4O6qXNKD2O?a|aAHr^$Y00HJh z@|S*W{hu#)STC#pS2}5RYJDorl)6UaVC_TstJIzo>o;|% zuGfF=BQ1l#p=8z(Uhnr}<9$j%XBYRWj~U;rp3_)8B4nzJGp}4&#l)jD!x5O6+Cdo* z9WEN}o3wa5A&M?y_Oa7fa2PDY+~Y)NWDum|L|!PdQUp(aiZdJ$wh@p5v==1H=1SD+ zXx9>F$pay?1A=v-mwFmzQNh!E6ErQp9F1*Y51^S+GpHW9p{(G*T2H+HhQcg(70Q7S zMdm^%z)xY40MZom|7*b(J5}6ZJ1MgpyZYm_@CQP;km*I~$6YJLrJ{+A%gfe5IOY`q ziG1!z5M+$xrSat9ql|eis(lE+{i~FRtG{119*(C{=(iSmV)p#!qvPLs3(Kjj-t5NM zzDKj{AHOj)XMMrpeHeD=)u+1JVUpmkZazf$$+wt^4_R?Q5hM+ENy2=uU7{TZjgE<- z>bwf`?U%2WqZpfLsSbr2gz#JY(geus->ca}789QM& z@w%tUt62d9KhoaFgq2E?m|{}c5=;((lY<|sIF>YV5j1|ww&;VK-+J~qM5>4bV1ODu zhZ&)iXJstLDsn(wdgcU@K6J+Fkz5+M-y})S3jzbdm8?;g@bn;*3o4}?pMEWrOH5zX zQnR1;3x8-yGOY;^+F`3+;`^#vkE?nmfQVVRQxXga0m-m)%g%4C z@bE_BytE~hA^A=o4ae^;%uYwKI>>EvmI&>r4oMk>R}kzD3PKY>_YT_U0@a0 zlsRudjK}#$rkCu{D5;5qMQ?f^i;S0mF(zW~t2M0@Il)?%)ga6a;vUCT6oiIc#u)9X zK4h%;x4J_8;o1SlfqsM)c^B#Qcif!lGG7_TicpCPILZY^)$tv#dK)Knv7V9YuyvPO zXUJR8X~7iZU^;Z{lZHVG0Np5%lb*(+OXplBjR(ntlGeHdQUP2(36 zd@t}@CFMuxuZV+dyR`xaIHJY5&0>EqK;#bz(jd9*pZm!P(8q&!rV4(4Y_6H* z!4oWMB%-KY-0y^Lzus13`ZXcodS%{;#~4~S#^&RULF!)iJ|aZ`!mY+{^h@gcwXIgf z$E6r@$nc$WQ%Ki2N}vzkT4Bmj1u||(136c#zh>)K{aW`s1%s%BmUvxHSyokI$kGV7 z!=WEzE^^Fe0p3COexFbrQ+^v3FeIfT*#^#b?JQJ<^}dP?H;97Z)l_HO5o6;_=rWFP zQC4ID-nW7ujVoCY^G^vde0D?Bm4GC{sxAS@Nf9sQ3L#<<7=UvF-4?a*z_A!Hp3HcKWj(aab^iD&SwzOp;pNhnaKz*Jn~QKNjfZLJ}z z1@)Wy!>wAmj)_5Od1H$APU6Rc~}?2gV_7b~@3sU8(>gKZ^t z`_u8iu+P~Rdv2-A{?@ za1xNFRGa-ip8q4sL)L>SWHWgX?#iCEntM)EZmR24pPs>`8)h|#k@Y0N3MR>LI1Rin z+8SxCigF0$L1Q@nIgprZ^mJVXyFKsS3aAf`dPtJI7GZ5lpaeQ&K*(uvj(Nk7i6P_( zKcSETQW=bfDB+heh0E$SB*-vOCG?k=_XW&(QPORv@a7LNMcE3G0+@?dar(<301BQ$ zS^?34B&NOw#>zNKgb@y|;t<%ORXN zZBH~=8?=B73Kooz3j$QbSQ9~mpo2!y&;@d^qz_B@HI!(-L?em9xTi)zT)sgp8Y(&3 zN+FCrKqND@D!dDC!f2rX(WrIig%v@#tdI$T1=gHwgI1)WO)db?hBezijZh^o&}aSl zJGL65^&)zx>kT9huP?WLzozMY>n&81-@E!ptN${KfjC+a=N;F(&$>!#_wa9ju5~Bh z!Mh@(H^e47AS69!ydpl{(R_=UicVEGom$&!M8=ouTw7g@>tdC2J}jX^Km0<}VGu=a zTD`l#pdZWu(VALye%{M#PZB(=%nyNkMgCtX-0j9v& zZeSJIki{(wWS?Ht-4~VXo(O>xq;I`>Zq6o0FIp#|L1m6B+%LzfUbeQ3RJeXa?g)V^ zGC>ol24gVul`Jb=hyb@k+baetf)6o7pwM$g(oGPiZks~ow*p}(Z|GsQnZnV2-Zo!; z2Ew=B=9@OKDq_XWZNx{pJp8Cqp5N8EzR%CMyXn#zZ;|Jth$*W$*&uD1lUmX1{gng> zrs%78b&CWYR{h)CU$Of(9kDN-egFU^OaYl9u=bQA4x4yN7$DcN`c{V}BIj8RCAnQA zzFt7iA`i5XcqL90z#L1j;nFS}R?;GM44(FQ20DJi4pu@9*)+g8InpRkL2wuB-(H%v z7usg*^lTY4!K^)6ce8!2ZU;nl#s06l-F7ozE+1K*VwKoTw!W=)3!pzgWZ(uAFWN`( z>OWH&uoqzBrw}OQ-Crho%xP9%N4m%Cp%sWYu)V9~uwThQm`?__d5pk5oZ8+PP4zvOzWtkiJ!bHsB!SS&kwAlmT;M?azmX zrr)JeQLse>o43A|l3G>(jL>L>oOAe?@&2v#Ga4hqj*T6O3;1aDD@Q^NWKB-*P`e7m4Iffse(TuxU=u^9&WM)X=g1DYkYmNCND-b1`+J13?CF8g8hHSLJFoGg+}`J< zR>KH_)ElSs0vyD*_F!@(vfVU%&k-s z!Gm_mp*1Mro0Af_5&i@*qB^=Z!deMcT|o~f%(VKY*aLN z5CSTg_$m;tl){PC+*S2`+}kX$k;|8@u>(ILV7`TPsKrHrdlyLqoCYu}MOMv~X0B|< zk7O}wgN%ERI%4d_=yPc@MI}RO`aSB?@4>K86|M55n3HPWcL__PbV&YenFEurewjcya&{-O{ji6@G1W)Pw|n`;e}Eds#oC zio$cg#~S0?OS}guSxa?UyJP4W{4}tqcQ#wG7Xqurlr~K& zdE4uq+^(Vayuim{m9_)Tjjpu6k)l7YM}kojKEim`Tvd+Bl5z&rTmC z8BZURdmLuOHNq`WCrjHQc;NB?^$*z(C!V)f5Yt8p(7uJ0-k+4W1K$f+Phy%^*ocR; zRMOm+Yx-AbZ4XXUF99F>0uFc|DW9|-IrloCIthEUuh)JD2;p4iB1wNpMGWA}zUa6O z7yIlo(Gw?QX?rF51NKTY3}G32eq|0uF=waC?UX&74F#<#H<_6m`dlDw_h}}KZZa+PLJ86Q!gF4dU_Kozya_hTX~MCge%&o5wN1osRdgtrR-y|bG8F$ z?)&qvTKS6Ec?8)Uen6e8jW%4EA(8+XZxUM-VuYb$a*!~hcP%MKpYIAGugdrs=Y4$s z(*{ulC#6BIBipFHWgaLX9HhJeJ`6Y*7WB@drF5?WHy!0!Itf3H(GHIL?-Y-7)$KYx zUOUCNI_mE(;iqeTy6(#Fw(F|?>r}19mfllN+3q97zN0vPw+e5t^Xk4EV$;|x!;otH z*~gULOr6iX2{$ofHZMfyYgbG`1WAmfx`nHCDa68m@{+!~X`pbN!5`BAat=-vdH~;Q zH={o86P|{U@E{(hsJrrC#ib&XL$%a{3iFb~mA+lghK)cWfDtiMB>N1GM6Cd9^s(w_ zBJ3|V!K$vI&ng4NdK0gI_u{xU!l3Gac#V>fBuZ#dz78;v=qPIJ9(Xd|-4JBKb)w|+ zRh>MTeYMYfI8A2hA)B2hJVpf=hCopuWB`!>m2XXxqn1SbFtNGUUf;FXE_5EWVp|_C zz>Dj{!ra?iFX!!{wop6hTdmDjYbQ0g4%M1X=W3h!#{~cm=vws+fJT0Sypi2-3u6}3 zl4m+^98F!Jtzq<$R$E%$ft~(UtO4J!SK{xYE=Ve#+Xi7oHNuL#k@)>9b`Z4n4`)vU zVbEaBFvmkSO)A!`{Y`AA^t+-cv;Y_M~9 z@qcphJV*H!m9&G&G27j7 z+TIr*vOU5sSQxUqK=w0M`~i*|74QNU6CEf++8wh<`Q;;7+GcSc20dcYW`sS!UxbcV zte;{vEHyXDobZ7VXjGDwT`p51Q!~zMFy$oW60~)R6>bEZby`k(z{k1(CJl@a9_EE4 zTa8&ogRm;~!LJV1c`w6luDAJ^fD1|hsWE?z55}c$I`4ve^=Zs(O_hPaNX{AXj1nkSBKOj0Gj!y<`kZ^af83@gkA}~S! zR8S)qVfV$@wsb2XH@T8%4b45WW`>1{X;lG=mAFjF8B>)dtgV+AV+s#TQ5zHbaI(97D%b z0QHbJz!&-?Q7VbH;OHt7kTZIm_T=;!gRXf~5K@>B;v!}qWiKqz?nRA>{sGKGpfO?+ z$nmam`Zj|Au?TGT)RsQ71=3bB1qqak-8k(Wr#%ETCShAN(q(wfPlSzVt#8#e>R;a| z2NA4g0aO?H=I7Oaw7>Rg6Oj@vj-1BS`kKSFOLX1U{qWkxrg~di#9X6yrE=B9->_cF zJG`?l(mlABr!VHAUDdDpB#>&W-Yap<5JNoYpBLH0O1@+{*tu`W@390wJrzsE$ z1D+hmdw<TG8+)sC*!p(0+r|M3faZ~b{c%53nF#h?kFSO*~d&R3dYOhvRWd zqDT!>K(09MjHQa1TNTYv)YnnGqf@BA(+C0wr;0XuYS@nAMIJOZ*_EIEg!TRUf7-^m zN&o;r07*naRIu(9}E0!kgjWRP7VCGy}5|hIv3JSP2l2A~wzdFrtJKoGjspw7_HsyS!s|99mC~dab0AgVaNt zd0lE0o?Vk($FyB-w(z%#tlw(CHR%)rq32OQh*ByIuksAUa6Xcq8DgXu+fq$e)k`1OoDnab;;}9b7zqMpe!yc zM?dC6G(Mq-3@go07$!_z!wf0DOcaSCZ4#`I%b@fXI1xch7ANUY6f-~tjfZ=c zQ-=%s%-tfnHINx`4@48vHD$|$)T~Mw#k3GPSV3`yJP#Z*WsyyFFFls#i3|>T!VpD9 zWc^CulMrVWp90XQEk;^s(myBBRtSAhPJT{6DmTirRiGPj z2~?=(1{FqyDgoA*xDD&w?o)f`9^e4&Kjw28j>CZC$5@OzURytZcqgvKS0ysvC4&K0PJjRGmm4TB)IOJk0yRY6xOb*mR+ku_wi%8pGXN`{Faq9 z9C2E4^YLfmq)WAw;$c)ughb#Jx@-7=%v(Jus{AZKDIGAv)XajNY$C^aC-%3VUR&&J zvF0;)p)=rPmX3zzj)6KT1Fagx^6;z=sNZMqWxk~Uw&2&Q%oA6E6zVuqP zLu#M3&C6D=(Td%>g}#s=fIJX-{38S??XcAHofUg9{X6!1Ex$;@oQ>9uQ`K*^|Au|6 z;qx{;wS~~RJM8sf6U~cv)iTc~r=k-&_x_@3pFM{CevXF7;`RKQmRsz28Ye`J=RelNMv9$dIgVxS>Amfe6EqQ^cm{EU5U=}EiK#%&`Eq#K)aob;_F!Vuf0$}sm) zR{6AlbH!F$rfseLorvC#Rz69+m9nn<*fPHn7jtf2)a{~=c`|vHn zKm?xu-*sC*7HVTh<6fV)v+0)C`{-Jpp@_+>+yj6ONP|VvYkOJ&3E0dPX#zzRG;J%a zU;_!=tfIC04pv~kWD<-Mo0mXmpJZ-IH6DTSmx+$3-~+%+f!Xq$XzUb5DAO7~bSTAI z@WbgqlY#?nv7UK}!yTx3t|gF6%k&5s>i&vcE*`B{`2X2^4=B6O>#*~_GH-ex0E0mx zzzSB8GD(plO?AoAT9Rw*wdJyD-uPsF&gSIoNp?4zlaJ5z$^A3=I?<=Qb>ck6i_QAX2 z@ejQ>8ar+xEgm*0{mx1YE279O|DXAWmt|6I+pUAOcE z*l3>rxyN7l*{fgQym!u>>ms;(kw5`=wvAf5Wh{P?SuqiqlJezwlS@94e|~DsxYj+F zc4!a2&#CF3Gt)oYW~Fj!UTKApsM~rtTbbx8-xYyt&S0+MMP^}DHX?|n#O`FnT`fQ5 zQo80;Gr2f`ZF*tivR z0_p@J zfweE72(^YQ)qV(qJ;>re5Wf!_|E`{soQ6h!vO83KFxPWOe3iaNa7z`*AGYs`9suc^ zzh4@M*x3oPpzPAI=tQo*)^#w>4m=S55(TfP*ItQpDB^u$?NI#S>Q`cCQ!k3Q#IfCn zqWh_dc+1*v$JoBv_}tK~aT4NTvVxNqoU-hKC|PdI#al0q;~5S=>w}a-pZ5Owd{k&g^A)Lf~ed4zGx&1f76d8^Oube~i5m&&N57sU(0O--; z1{w51@RTIN=xfdY6bm`;pb%gwGjB0VlVAk1fdJ?;DMg|e2Sv5wnx;M8#i;+${*pXH zYs!3ciMK^ggl8RCQ|w|(!zfll#B9JVsnE_Yh!t(t-K~ndonPKpnX}ryZ}xUGm1(Pl zDG_ox!p!Xgwkd87K+9j^`B;s0yDz1g3y9|8b`lT$7c&-P()!ZY)o zf*GeBub3QLQ)=s`NbAHgesVdB`rENhNJtCVyY6Q7ctl%yuTNlMbk{m7Mae zi+Lvz!ir?sac+4vx1y5ho}cSrfZX4n7Ee?A`IRIV;$duuy9#6)6;g^&m6pa($3*U;>* zQL(yE0V1HNqZKRk7V6kPmtYKbAV{ph2J8b8)iwv?)Sh?8;alDrPv7}4yG;;p2v)G_v*R0w|pd?uiX~in1!ux&a$D=ssXM6Rsn%|We2kSw=SK-ef)Fr-r1*7 ztl|jP>K*ZMtR$utEwF1p?D5Q)h&`}!B+fG@zH9CKp;h)oV`w>kWM3(MtNOlps(V*_ zZ1>CY){TE3&n-U`FJIx9)!KnLibnXa?A{gcZ@w7szx<{6tCL6LtGHbLn>V~QKDYBg z{N%ac`&0NoSb}Z* ztuOsMfb)DTbxcGLE`ZCW6ET6Vz@zIIF-csF+c{ErFHT+hCTS~YN2n80Ne&oeBC&<- zd&47anS&`0Wl}5aZ-BA#yZ{l30&#a*5&?!>ZjQST&MB|oL}(Dwe*KJM%x3v{^>}B0*qx4om9|SMBMP04G1&XPhbBYRjIUoxQ3(zL-JQf%; zTXx^Fg{#cDjyejK>~hN>XPp*q^w}p862cq&4DfFgqN9oaPGyJ=;iSICB>6&2)%KI; z9_S^C+Jybz4$uo(jnvCT39-UymXkA+`*M~ z3~T-@%7!Qz&M=qf7tnXX=L2l|T!Pdq;TdBmoD!}GzvPTc0N7U*Wd0p+3+r$Zw2Yb1^<*eLh`^n#a^!Iu}zzxvIbOIxt z9|*)31<3S=CS_RkWgL8|vjydzJh?Wi)CEoYBtlHXiDkV&*UilX@vCz?;@45w+XoAD ztT`Fe@KG0mU3y!&d1)>_%Hcm>s`FyuQrt3sDPBQdyDq_q(%V4kVtjk;C7Ra}4_%mw z`=?ICO-oPUeQ_X;jUNYz9*JLBzCXS)bR%+Hc4=r`KgCIG9qW7J=F*9{v@sqtcpLos z;FB09jB}#f?eV`)9f)^figrIctpD~ahf(k7j9;g_@u{8X zeS6M%#ZF%$Ku!HJyVEoSrEX0f{p5e*^SutAUCx_af)Walo82heO7I}!O66Rf0F}{& z*@S~rM-QiVVHtKj%5>Z zp8=dE>8@R~;3`A|^hh3HDRSz8j)wbAnQQ^jLFvLI6sLnZ{dHWWzM^yrAVJZ448je# z;YL()=Gmb`05nk8UBLgtDjq^5%nMt5Rz7*>ALe!DI>L0@s3H%!btWQpJbg0Lkwoqb`0dJg53{rWY?6GBY-ER z3oa;bj8o99t@15iEbo;vN=(AE9$**ATx8Lcg3mhYu!f1?Xoxs1ZF2#>r7NWvCyxq1 zE8pDux=gj4pB>`>aSxsY&@om9OB65#mR#R1&V9=Iu{_w0{F9e*rO0);@lxpQzD z@e?XpZ`wjDc($SZ6axsg49RieY7*L&otqt1^ottXm2ph4FuPmrqU`Jt@?b}ja%Gh9 z(q_Co50UUP1j2suxRo-=Pg=KK9_V-_kKDbC%>FqL`rx_mh@*qZ#^)}?yE&BS z)|pSo^FsspZTCpkjreSHFYc2kVxV?1zSMUPCAz`*{xc_I9{^Hq{KI(Xd%E+M=$^b1 zKQ?nTb^$zZUcMA}pp198e=v@MWDdcC$C5B6@VD^8XFiYSIpUf*oHv~Y;iH&VM%goV z(8xiyfJh}`Kf)gRcu019KcbtB>FASFA|YwNFYcrN&^W^aASN$MbM*ZFyHH)>o}L> z9D&L#rkCSzi$H)Vq-s--5ddp+4p+E~u=HX5yNhOR=nzO`T47yQ@j+0%0wIAmeh(HI zYm+G5sayw#favJ}$ffAAk*OvtzKPh&!#8c3;sPD-$&?Wu6bQZOm3;?bDBM_&!;jCz z6Q5M@HyqvQCTy&5JaKi7GEpS3$&6h^9}mJe`d@KoA=+`r4xGW{w+v^oL7OWm|JK;;W1az%7ra6XB4#- z9odd+9lSTR-xW`9{J#DD&3R?UUmro)zshb~u!QjweiReW8IvQQgP$jV@x9G*azV6% zY<#hJ!(A}13+;R-LlL&_{N|)JY2JHf=U@d~SWNPC7tCuZ?2Jc>v$y+$0mrO%Gu*<$g?p-0+3Taz&Hr;Z(>&Wr=9ZUc(JiI5) z{peqg!(TrVx6Qs1S7vb(0)&q((D4$xY0p zHsm(ojHI&jU|c~SKGTCqA)d@ic+P8*XF@O6uYD-n-ZdoBF)tOt04mwWbQ#P$+T#EG zmi@qe8z8uA0B1Gea%^a~kIjAHvY5!Nej=fdmcedz2dwWJcKqiy9spwQ<0WYj3lxEf zBBSL0N;H@d1gF^`&Tq7j`}`u>%L@nbx6U_wM&4*JL~nBKb(;bbM$E(@l;E z-b5DsH_u**qf>`t9^iZLH_wk!2i8*GGrktN)PH^1eT*gVHP;mr1u$aIo<+XO(%je=z@W^Q^wLFp1!5uASj zzq(WwtBlb%a!YhMR3u4uIW1Xm< zL2ruYSG(;Z_C?3H|2O_kA&xcwGy&H~#t|If_)ZYN^ zoole=G{gdF=3dnw@sLZgEn-3_Fo&l8p zmpFKe^$5W& zT&2W36iC9trYJrVj%B+naw|BpRzd<(_$NFvJ?+hu+&C}zNjL~gokH+dwsN@U#yOF} zt3P^%3bf~tdDP)h=oZ=eqX}Zp2g)tto7j6p`$`>c1R;qR=h}4=SPj=pa1!}ra9ZPj zwPmf*6EQnETjRGy0JKKeXG7sH8`d{~t@Y|^R&Q{Zh@G-Uq(x`}MY7x~j}L&*Z8>mH z0S#GZ*1=yEXq?u+#Uhm*SKA z$}xNBmU#L{zcOWZh99PC@E< zh>lKU*yWFn#mdf+m>cYg7qL7zKZY;+J~$5$2tdqi6;Ad6yxdxKWvI4=SGhA3=m=>f z0)R33fgMi9Pv<7w8wBK})7@+xt!p0mk-z@^S*L}50mv(nITjJicgLfc)HQ})#WW9u z#;yf;4q#?lCx9~xDj~E?2%4F2+u%sm242%<<871Y*|k9o0VHbx`wEH{cu~bVA66{{ zKrh?79tfu{l>b&BCaSn<+ks0}PyuBiYX#UZfT%V>%*`sum<|}nWbP-ALdGsXbA=;YDA8xVAub)vOBI8~266dB93y9<8@HO%fxB7FN2CB-J@mW@7k z_G47c9ULbfmXIZ51V%GKwC)yXs5^G!6}U;cv!hD|tV*MsT1+${*eI*H&Uj)n21er@ z{nb20L}V*Cm&vm1Lmbd(4I;2>tU{4N$W_oM@L(x{b798@L4aO{r&dZdYY*SSIF~UW zr8mf5AP!rYT9)C!lsA0OKM4Vk*OkS@bBS>f*=KO>=lpl>i&X6Z(S|YQK3HFsY<*qS zuWhR3`YXddD#?ELvs+~e4eLu+=AlNu^EHH068#br^i2t8JNklb&T&79(kldTH+~Uh za~HgKzRe;MBR^@{Rh%eQA&hGF;~Lz71=?Oi(_iF`ZC?{YR;~NS2K@@r-f4d__uZg0 zK#;2_b@q?#hwz-my~R1i4#JJBHf53reT}7JGH_7S$Z4-$fQ$|a02nh#`zQ`T4>9T5 z3beRfg15Yfp+uMEiBHjESmjho5PiKkomzyouSZ12DE*ow(bht-l*wVEIQRi+W9loR zvW_|H#@unvrYOgefdlcI-}}Cpx$}UX7k8Yy6z`q96cbAj2fiV>i5O6!u;(Tx7Jxu_CCba3}(yy%pr7%6XIHL5DQiF?4W9 zPICan&*^h#hWp~o_zq41+{ezJ?Pns`qQ453tPSMZ`U-=cSpr|=$GJ;0{+8zx0JELm z>K~h$X$Ai7L`2vg?&CG_1n(S7sdTvl?oxSPfe*5B_&|IctlHna?@C#eZfB!jj=`m_xasAExM7+z=Ghf(V&Bz|sAUBA#GSJ!Q6l(N zTr|!cLyw%PiQWJZy+cG*G?XiC&iTo1T51N!?C!gQj9DTk-O@6LoAiyxNxP%-IFsDM z;yPH%h&uI72UTR-6nVq4)}|d91@A#dC~P(%1Pj*dIQi-!koMviK;Y<(yaGo+M>C>g ztpM~)F<+ib zsmrl#t>wHR9$c}e^Tj!lYlCvQ6;bB;vd{RH+}+bZBHiS8IOilJ<$h3)EYGV74|m$vZd^``^{3yfjD3G%OY1{^3ybX1L4r8` zDd`0ej*70f-c30!y|C5fGI)gJO z<E_oxJl1a@)zgN##GfMwqxp-_-T;Ra5De9GREyrXTg}bduOMdy1cM=*bw6v}D zio4P>*EO`cn*)`&vA+}_Jhc>4&t8m~4~)k%f8k?s@I^Gw&$FZ|) zgA9{01o9PmkrqkaHDwZk2rM?yoW6)Bw3B;x#fby^IaH7Bs|($V0yNSapUB<%$&Q6( zodkf0yyljOpVkM;=-COThdBB{_Gadh9o5RK-3f9FKvww)1f3L8w%_t7%fnXOse)vm z+kGIu|CgVQyTAI0_{DdBFxE!;sWXTNeSo2h7h(b%{41q1xci;uNLBPFs53qiIUZmY zZGPnP9J18Ik*>Y4h-)6uf{0}k;->)-U|%@Cn?TQ1+Q#Xtv55KGDx85%RIq#Sq2ELQ zcfp~tITeua?n#jTrm|oW3Fjt2q}Ww7f*(kmCv?&I`P}}sCrdHEiiXohQ`(=GXtTde zA#ySva|&chjq|?fOqVl=jBlVS?)?hKK+y+#TntTlV_8>!kbt~r`=}L1&Tr0;AYPfH zXH2I(O0w0l?UqP^QA>`>AFTV;d@K=6$+$3^D7KX2OB@^T*0>Ly4Y#oFx(!Bm9!L}z z$NacTfaD`Q`EqiB_zp5oPLActMZ(8^KAYD1iSwJVd6c zG3Bxr%en+-&ARq0!Z`>4xe#R(eI;@N}y;-&Y!8-~%IsLU_LedkZb z?v;zElVCQAZ-6E~0OVYyc_D*9Pf-Et4bK=yk@=f^_%{WP~y4>y0%P` zo=KKQ_4bLA-?t*=6lmj|%!^O@(KaLJbwNCsdGX7*=4tSI z#<_Y;SeYM<-wxaEtyU`&mkYDAh{SCwB=RJSK6eSW0rqhXoL<%Q8o#|DyC2Vbm?r&T*mJCp?Agk_k1aK{^}=D(81&jg||u{E=oIxKy+8Y%f;k(cVKN~Q3@!V zHGnMk@aq8dwTUsz|8~V?<)=ay2m>ev>d4hDGocmpXB4XraM+2HpgjQ94dNfg3~K|8 z;zaD?FL&u2fRX||9y4V7&4ZtUQ4xj(C<|6ztBbY)L>92Rvx7lYO`O&||LAS;-~Hy# z#KBL0IUOR+1N;@^?4UjqjT%R)!a2dIO(~BxLrDsDXzwL}IPC3Jh{g_F1AE|74FKFh z9R%?OqnSEzPY;V`xC3DFB)ixr2U>t9Qk3N>>a_zrSctRXxVQdxh^c972H*SaH#-VK zCxac??o9JHwB|s@Ex!4t3=!xqRiJ7e{8H4;e2cX57}{mjA$W>?Eppvp#|n8;ki{S{ zTo>arl3ET^d)V3O6Or=+aB$S%U5U7wgSOfE3<~HE;1(=c`FffvJv)77$ zKJjgav8N<^FvSwwn-X3FcfPk}xjI{aeOI(lVnU@YrXr4T7%vvKxj0YU`4?*uWXrR3 zrq(#J(UWkX-|O6OvKucHzff#M1i-K-EmC3;RWZ@4aR)J)xnHuqOGdvVY z4D7)i)J2_185Qmo635|DsDz?j2?9`pp}^L(o_bUf$>>XtF6M_29 zwehDL3N&Evwb#nF@~>8N+sik;Z4()J%>jAixNoK}LZ|B@HRK&B4@r^?A^DX8QO*8Ou`u@fGAX{|GM z;m8JCw^-?YDDGW)CCaDKU%*VTj_cbEw?}eJmd8N)09AG>1@gh88zrLNnB{`@sk*FJ z6d!Q_1jz$h?BK6p3zIui*(4W>=nQWSyb-8Sbn7R&62u`$$TUTVi1h6oTS?#Ly*+F& zwLhPo0N#5w_|9}9ajy_Mbg*buq*>HN6l-ln;?~FVuOj9mcWcG`qa}ELpspZK_#bj6 zidk(rwu~KOjPTAnPV=*M`kAwJ_JVnc&Xw+SMnLvaLGnN@X0HB)he8caYYM;H5|JX} zBy|q>%S`1b+85;;!}rd+qAjLqe=FK#Cd?NwuDM8L;gB~|s}*mpiki_=Ai6qptsuzk zJ2@HF##&f24(#C_?!tIh46(sKPPgN|aZBvh)At2+QK51Jwtf@D-vsej*hG~3$s4~2 zT@VVnUO}K8h($U?q;jUD2;@-k>>^Gj!(7+QoW?G`L1M9nYCSmH;RxU{kK~4vl}iZ( z>R~}O2!s{=Q0y3qjf0)g1dJB`9m-f4)ba4Ce=7AXIqDevxS zJ~PT`*_gC8Pe(@=1RwP*t0+Ld*|%Kkj>Ee);_~E7oVfdesD1DE$Nshs`jW z%r&T{s)oOG(5A9eAVVj905-Y+t;?7_y^0mVp$Z=Bj6%B=LV6%fqC_rMOdOYHR-hCR z_5c8)jvx8;Nqp?XX8&(Ek?sDY0OYBq*!{v>>^Qy{gPbVV*HXw5*cZDy`a|)9yM5V{ z)H9!9qn036O32lbRPuAQy|Z&fy8x-cT7h1Ltl^705@&$Yb`hZnN0cKPpk3|~`R-M1 z0s*21RAt$?fWL(%3VxXrhq%xY9bngn8-1}?j);>zv^vO?z@noud_{C*+4l8 zuEhw7Vxm{ch^wRmeU(8VQq*09X!-WPWJp0g7|(PCura2wBm@!*@RYk?YUV#`K4=_#t$X2MEHluL&R?|p8e=g#9{w3xAwjCDM*iJj%cB#oSMgXp9^HI_yOB>o_y0TMIy)GUzIG;sL0$! z^!8FQ%GShQupYDdMQdf@wd+|9sR+ydR5vW2SCS}O6ELJXkt&q zbF8mT^gbjMR#zc-Q4&r|j;xV?)P#qdfuiarY=rLN9e641iNzx?14kBOvUeb!`N*G( z&6^%%SkZ4eel8xEx=5ce&spES=(bdo#aa+tw#to*{gcR_x+I5zH;QDpHbGnE7dOr) z^(}G?SX1c&UEoO?l5Pb-Os)msu#nxKaJWFs`7z__FU2QCN$O*%w3?g4V%L;$EGyRt z5#_EXDQ$fInOfyn^L5|YRv2uHi_;abERX9X6j*9$!0I zuEuS}F&QCE@Nyf@BybTs84c!@Y-ykQMg~b;@|cLdHIJD?sj$P$%4#G_-Ta)ee$46T zLSZtK#%Y%>SK0A836p3b)>a^iWu-e$5LTUCNXJ4YMyEw#dI$K=R0Gsdpr z<}+8~AiS>;cOiI#ovhX^o1zzMFha%HWdVkYwWV!#kl(};V4D( zzy`!cCvESBD>=VoBCdFP9KPiXK_i7|re{-f$IlHrj6oa&J@(XMy#3kb7=Lv(CIFbD z9AZ@7n2DWia~?x>J_sAWe;NG)fE=YzUyx{al$}@XtI?kZ0BTVUkQ9K}PrMNni)egy zuj$ei`1)2Y{7twEt85Ebk;~3<&iW#U+tg9Q+8Cl7%(%J`VaP`Aps%!pRug>#b4NAU zH$mj<+P%}}P1u_2^vP-&Uj!ZVxx*>}C`;^usyf#JS9GiZ(9yb@9q)@1L!B|%k9l2# zBbi6LV`Kja-|DdoZ)~GeSPh(6id)Yt#?9v;7E<#JjdE%)Iu$Jn&O~lSyV^_A-MI72 zPPT81;k_el97&SmT9CJbG!d}h?U!~u^YG{Qc3$S!&J)>uA~fT))1gh42$4(1Yo~Lj z7KpaUvnao)kL5``Sx±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

      LU?Jf0{jBQ2bGMtG-8g+BQNs8Utu;lr}c8 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
        Ep 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

        4k~;3id2G8~1I z&?P0*Ib20gj1#vYW=qKXM?i2Fx^VJ?zyGcA5gc(r(ci|BBDRYW>bgZQ&LBp>tWeeh zZLIq$oF3d)Z!_dMEONvZD#HQGVD*9OCn7uoZzVwr!~weRP+x^zuF=r=27vj#giW{V zw|~gt{Z1JggJ4EHpfm<6*eSe)K2{G%_2r{4Ko2}J2!JcGX<9Zmw6O1eJv0`DZ!k<= zcV9~aEx8bB(8PD3%X>!*xDOXo2+#%0Ym^^Md(RwHL&0L%-#xdyj*P${$aYXnFE&ZG*#=J~<}Y}QFzfO?aU z^g0yWE_zrH)PYi8Pz}@yKvDAPP!8;ULoIZMe<$q{15TlT6<|cBYx-$xRS{*(xDxQZ zBNE)Q8Z>1IupZ3OwtL~1Qb#X=SW4|@ z?X4q6>^ql!Vg0w4?CQWtd+ph?woG82Z~yeq9r-EX&}ISiswyIdKH{veuFNBvD4}@| zaKQ9TN|QC(RfJ_*CPqRmiyi^??ed8Ly9Bl`z+X`S0SJ3=4T{*0k06pr;<;@XmU%1M z=KWY2)DT=_;ASC=59&BoDCi@8I=5P7S9*_xtpWuVdL^4 z@6-lAfmpkDoEc=BkQ`R*&1m}Us zdz%ZE!!)za%_;Hhx3Y**U=p31jIAygtkmCXxyhnz`{DoqKmbWZK~w?Tn_jecCJ$Q& z^86ONX74=tFKp@1A^RWR`#$C4TnVHy2Er1RzXRm^SrCDG6kiLt$c};d$y!I>wjYsC zf94P0r%?!oUi(+0t90@k`lQhx!N$QHg>A$P<}Be;kROE&Pri?LN{w%fZ<4peGX4?7 z5zs#oKaLdMTZt-a)F1368UXj>&$^e{shT80#hWJa4+D5`&pRJ2UV5<_Tn)1lH?g-Q zw$MMlbQZtA(-W1&8u0XpI&|i#&WdXg9Gn~JgTlF`GPO~4vU7VLrLMC-c|CgWW$BN8 zAn2M{A35|a{OP4<@>I0!;LZt0^1Hs&1IzI&U${R_yU!P{?e{b|nA5>v2S`A09S-fs z4ZR8q(|IldzUGa&QIi8mD$*qIUzm?fLMFmOd~??#@t-y{u0g4_FvEI+$tj@95HQ;V zWFR=J{mnn0hK-D67D8TbYhjUQ@ewa?ehL}Pb^=$MO%qVPkCIuHGBU8awaM0Dsj@Bj z=Bn_i-d3nvl`G`pp8ld9y%*8yvROeC`a#u0Z`8X@V9ZL(QUiUq!9|3bxNb z{g=;M5z)hUestL$n_b|W@nXj&SEBwno5>!gSr<|Ay6}yst&S_*5zOX}-d?djT(XW3 zsOI#|9Xq#P!St(Wr_W!uS1vEvg`r+sob0eHN?qeO*6hN`Q9Ffw_Q0iO%nCd0`s5LQ z!!~j0A~JsfMxqi4;)`FCHPj%bo7 zA`)98@8_V6-0)1l@ZdZ2EzO627iqlQ=Cn%^V8RCb!rNe&3zn7!9id(m9QGArx6Qm ziC*uq{jo{AL%8A1kz;oD_NExvNytWqnPE8$N};#jR$Bc@Y8m zh^k(`sRh3QQs}L*e)YY?%4kHX(ah6CUm3Nu&#eyu}!8^2%%G69;OQRw}f=f~C4Y(~g*JV^S{MH0Mfe(nHS ztFAHU{iIud`Z~3v%BC&gEwiog5P%ji)8PmVfLmI{xL)yZ@hMCimWXd5TIqV`hsqDG z{2Z35XG-I+3?x8p$g^p65mps-J1R<}c$l^S`X|rYm5H?d{$IXp-+Xr#gri`2_OB9R z2m;mGJ}lNN*wDZAyH6VAv-Mq{x38R^!xo!xw6J-5)^O*8`_&C2W~yKH+rYrl96Pj<7e9G0By zx9XKEZWG>$LS!9#>lS4DBFgun6y3QGK%<71e;qG)H7GVwZ>>>G=6GMgVj+ddM9A63 z+9tjf%*OFHSEB7DoDIo-UBPG)+9*--YynUyLL7o?&$G8p6g#*fZ8$D!o;%*R^gV;r z=<(10r1>PLHL5q_$X+gE&Ikc#0`;dv(Qnu{fzA=ik5$^a?&9N+5sR z8^n+R=^H8Gw5Tm_ojCG&rWJR-^Y2+}6;FTlE`mJ4B?J)=1un}AQOa^t+yYQi-`>-| zRH^;yU=4m;UkT7VfK(!ocR^j|C8eqmj*x^gZe-zG8NJ@x4&NAq0N{jW1@$`>97 z-=%_@=R=s_6VKuPsBdig&ZepLiA8*w;MeMbM+O0C{(4RDyOW$*)R>e}HeAiAfC_2O zp+yj&4xU#FjXV$H0^FNR$%0v`Q{(a=iOSUbaDg2xltzkB6rtzg;pH_Jx^E1WE0Kg_ z#h#3fP+#F_d9Yau(^o5C1O$s;g((jwm7#yZ_o)ExwApIo;Z{pe@<(rj^5`RYuKVEI z*uc0JtYV8%cI(>Jrp5-trn!U})sMgXyj^;%-;Vv=oA#~u7ofm*a4%f5?gF0GAhBgl zb{io0y;siK<{6^b&2QW9{_LWSY{6dVT{}UD5aK$^FN56niFb%S57qwtp%Gg-dCD^R znjJqsZLfdrlyzRO*!43{*yS-i&|v}b)N-n?oTUp!_f;T13M5|WlEg4x3ZL}|NY zhY24W&$3}cVT$#u5Ug0K^VP^kWSPNpDQ2bMngr%fY}O8x_5toVa#8=#QTsTg$)iM; z!I9D&Y~b7|G4Ij*hY>^D=@)?0>H+~_(D?7i>z+EXfL6M+-P=HXbOduD=vN;Pb*cj} zf2~e$Tr^JiPpI(i{prHzO=mBkbV34@T=3#y^Eqsn&w-{gKJ%se!Vy>ISX0}Zzbc$p z;d}2}sAEN$^vOkEYx?itharO(#@1MyZ7!@ZJplfg?KzT5f2()_`8dE;n(q~;>puAu z;5+yB==T(kfp!ao(uIqV&x7O@J;p=#VcZOZ)K&50AQyfqc~qo~J?_O5YmxICya=vL zPg!)O-Fk<{tkhGnRpK~Q26F@n+5`#MwGG5RyST4Du?@1jb{$<4<|C6qLA{bFxKg1b z7;3LCG(IZq-*9ZyzX2=#ZV!e1qIWuc?0i39H##6tIe)Ed?YQ>Tjpcp1T&Q+V7{ zpTn5rw9k077QBOeHo z&Ynm4IP_ls^gsA>e+j|!gZdkJg7kV9lo4D#?63OFD|lFfNBTLl=g~UuPUhgdG=W0f zT0PNFYrusJG!J`onjmNJSLKO1(ebc};&nD{ctOsO{tfeo=jO7*)c#C$(l~*20ouma z80;eelonXdqLOHkzjFx~>7_-jpB&pLVqldYOtGL00Vy56hD7}aOHEHj-RoZ3&yHMB zuGXXM+w~kNCg4)k6*7OQq?QUI1<2?XOtAj`yJrdA+iR!({-S;N_1iQF4}K_JZDZ42 zMHiuk4QUP5|C`5$ZS&bl>Mz?%_Ld-w{d>zs?9&ie8dJ4}9d*Uu40hxoib{tu^hPG zTd}#)42p_)90W0Jui%LiZjz8$KFHUQ#4#?C@vja3gzLZQ(L*j-nA)ZI@5|lo1?dPp zcp`k_xmLdVQ3$Qhjr6*rkMBPmnBM>Jw|K8MdNW1!GZsQ;=sdsH?sxZVe!WIMs1JU< zk{;b$chSW!_%{kz2^>BMSZlL!YMa^S)Gr%_0G+1VN0-Ux*AZ$A^oaNIasKaMfy!Pz>Suy{os$!**jkyu_Hft$Nu{tOvv`YkC~a<;DX?CYoP1t~JLr?=?3>{p()e6(R)> z$a1iD^iJ(xlwN=Wg^2Vn)3o*h2tIhac#veet z&AxHL#((bxtHZ+InqRd1?Rl$73=390iFMSAm+WLYkg|AzV=q0En^MAx!hu`ZAUyE4CIb*rC?C9Ye`Ay0c?bhzEpx z99g|$JLtlcnCty`t}Mrkw%UbDY!pOUIP`PShhT~Roqj>u9-FyML@hJ-V+>)qnI?Oki#FPAgxKs zPpn~|l1hJ2z9%}ErXc#BhNqq)C`&hCdxGj3RW_==+eYOJ^9YrJZt#MX!t*rlRZBBq zP#$qJWWnH@JokFvBA`QNDIcmJv0B+psUxf<_^#lVca)*;WcziFfY1U(7Ia_WAOvMt z0x9g;(*;7CcE#<7fBKZ&Jcnn#@4sVz`uc6M?*Yg$QABQwrJ^N@5nJd@*$>eRxbpS0 zkVs}nFU{H?|LiKU00Fz8|+)&w3H-UPNPU(g!@3; z0Tc^&*0%63k4zr`k|%CJCNhMZT$IV?2zLu5n#H}ZHtv0Zj>5t2z?DE4OJ{;?0sQw- zZc_(4kkb%rV$okZ!jaK+LB4gW&1r(bZ(lf zAJ-bMOXWYPWvbx5BTN^*@p_nxQsCQXEeeSd^2H>^SOm5{J|8xSYtTyQWrYvug0){j zF>~|Aymj7~v!1RKcKOxUt%&!%!xIxI34*ZY2#kbMY?%N~lDW4MeQ*;FP>Z}8f=pDT z#L0L>(IH^EpnZ=QKajr^l)tp|2@QbG2lE@H)6S6*Yh53-`i&V|n47Ux#7ez`1D4w= z*b{Hx2H3+BOV_L)%hDo)T&okAko_cv_rC}+3-p~D3D7Nds`rMQ+6JTIfA3w1Vb9## zr&_XJ5@mP_9@Ifp#bEIMVoZftXo*YDBbZE@P@m-p>keBKo~fDoeeQTWg6|ME87guH zwJ3jB2A7UGhR--RvIS>KYrI#vdKHQplp~n6Tr}PV<=!nT$gB4pl;dqXxMpopp!b>T zxc~5H4|wkR?@t`wtGz)h?zV;f;b5l^(|vKzj|>8!=@kBg;Rt4){%T1GIO^Jgq%SD$ zpz<^&oHA2xp1Ym^uSEZf^7VY0^yHV~jacL15wKPpcPh?H3*RzFDPMKBh@a1)z0rSu z#~J`<lX&*~TQ;(17~Hj6$G02dcT2h9Z8QF`97`9)xP zs~jyc9a>0Y`|JDs8}(4HsH4WS8URpASHyV3+MsTm2}A%(+-DAch6Q zE^dA02~G}s^qqicbs7{utYKuiLP?T&Hd-77)zDE$EucTJfH@Z%dzHHW=TV3-k*B;YT|l%Aomw}v7e3V!=-h-u)`#F@8y zaEem+gxI}t$ub~}yZC;HWEi7D75Bd#AaihZXi!c;f4I(Hyk#lECYLbdtmm2gGQkA6 zw-%UdAb+3=jgj?{O<%!;w&dzQb=u3s!>|a@yV#xD6f{wEnK71Yy*Hv z%RU%&Jr8pS*K)2E^gVf*AmS<&-RxH&NF4|VOXVnCIJ}KHaQ~R86{HLbZu%SS3wj=u z6Sj-0sGD)4YLz96CY}8{_{5+Ti6I=gYTWfLdKuIsj)mR@ZJ`Wrqv{F1U)uUU?;Fih zPXC1u^=;bSs6%=5Ptp#3iHguOY5OZYdJwpW0wgAB9G*VBQ66P_(9!%oP4C4Oy|)bN zCar#t%JIk`03SCXn&g^!yB{C#pbegBzcmA)8klaxmq!5J{uDJRN7Q9SLCb z6_>z~8E+6&F$++^WQy#a;sV5BYepf}KNv~dkN@li^!v@8`47LaZ{I-P4#i$U?%&3a zS7o6VP=G4oLGLZ7|My-vW^p2^oqliH9((h;4YI*U6&Vihgv=6SEY>9B0X!!Xp*#de z?dQ*q*d`nRRQ|31>WWRE02l|j#tLw6*vzGEUjaJec+KWIVt87M+0%6YsqRm%yv|U#}h+_W&(}IAEEL;J5`yu4sJ`xBw--JBObF zv84Ccp%^ju5=**<=690lOPg00Ee0}>?8Z=xzTH8Syh7lYIMMjzW>}sMGm<5MOBmBIeo zNcX@~(>#6P`^LlOIzBF4^E>77wM2g~5!r}?x6SKIPkfH?-o;hqQQ9i~qp_C)UltC) z%FP+e!Gftmjw?7(4WKL2z64(R)@PQir4_k8GXKrlMLN;!5MCs=3CSCkyq*qgWvt5h z3&^&oQQ{*qsHm7mR3wI#}k{7414Vaw+ncbNrAkRErAXjJ$z8>;;t!y1+ zQWK+Me{0hY<+||$uxV>6tAtwa#u|W{#9agF^O53P0?`_GArua}!2hZr?(NrL0qR)* zTyc z;IRlehav(28Fh%x{tTes0()A4RyX;qg_N?u1Z`e0} zcFmrjhgAy@+es=8I%P`MjW+iV9tx*=+U>_rVMc|<^;s+fUYUhDUPUpB^fhU7sV??U z#fD!6b12fj;gy7~5ejzt%qS6RaGZ+wy{|3QtY>DIh=KSr(L~bco0Q-}q>Zl7 zFa~p$3m-3aFXM4=G#+XwbqH_Mb$IU&@Bb4Pq~`*_4>-d0pjXXv`vFN09)&@H>IB76 zv2q;a2aOR2O3ys(HS<#6s5$S3F{q&&_u6?*Yipr>ng5ZI$_V+9&$~uF>Am*eIbTlgb*75C7AH!SY<0q{G z61Y#^RxAtl5#{WoASeu~1NYARljF9_{4Eg?u#6vwrE9nB$+IW1VA-|;2*w~msTS6k zFezjn!)1~RUYvHSzZDTuN6=sQjzIHT4;+vv^s*tv{M$DFP^^@Q%2- zgDCAG^W?YHzAx+j{CnV$M*zYuYu?<#XW=GSLh!u29C!!QyAY!fm6wz^{IS%Xg{i!H zA8w`2P7e!iItN((M^|~UIj|u%O3=O6;TyfEDhU9{$A9@|7x&`s!8@ncgW6qL5I~J5 zb&^Ot5Ty0ZSA^N!jM}OE&Yw|+{0YFkCx{JhgC&4xlNK@~k7al$wRHgYyU%vntv`6e zqR3$X=nX>n0#hSwzUg8Ohd^xPutrmG2vqYefbd^EH)QR-efIKyK5xUK$fyT@ZnjOR zJplp9Llc~%Y`PiBr~p)db*SBb_S|728Dc`0jad$tyAwaSVEwpJg+#=oS_Gd0=pxYk z7#nvTgl7#(uOH>Wp>3kAQC^OA4PM^0BHxlY)^8QO`;|c??p&R-))lD#@ou|{0$49* zd8Ii(M@@d%(Fmtc6a=P%c2&H+$L3(!pO`pqb302m0EZ;c`_@dGmDf<}V_aHbKUZNf z=kW7iS}$60W)(m5?H~*Q9rE_}cj2m;cK7CN^w=b!Yp2m6z&jiYTyjl|qyx4z-;XRr zKKDh-)dUo=Snzql{XRMg;`B&ajftUY;A1DnL5;es4}1z}esJ&48e^;UA34HzA9PV#zNNw(Q@VY=-2+}B zK8nGCS)W_7(w%k0IN0fvwxT&hnMvF(FCh1?R&o9VU{c^KLjhA^oL5l#EZ`kbx)<&E zWvJtyqRM>jBb;%O(9;R@RW_jfx8{Cf!?>Z=ysnFD-o}_SPb1N?ZJfu%P;C63w8h7J ztmEX6)j$+Jz_-Lp68{P%&59$u{DXZG|pu_uy1fEu@8k`eHIMmLE9LncP zp}g1kf70RgG!nVkp(%rlp}1Guoxsw5+xXstWKH)$TMynmG8g}B5P(k_F^zTeAUBQu z!E>J=O|$xdqG1vI#tw>ByHt13KAlyY7J0Z~=uW$kp6i*?2Mbg9gnyeq)t|d<75d?B zkJ-R?@qw;71e>(^R@kMb^mpe>hyCF9C+y_;OZK%l6nKRMY`BtTV>xH-Y_cgf!*yim z*N1cVlUGhaMa1ouzkSD^o7%K8gj^ZVdqpW=vI!zmW#bRT1&IHxXBSGojYx(s9!xI@l}`0b2%Gy-pA$F%RPMZpb%owX$jB-S5RExB(luJME9y*()p7 zGMl&CFOFJodDnyf^qk+eZt^7KDMNZ}JIIPxqABYGxZquwZ>AA!yrSC6q=^waS7u~4 zZ{D$vvxf})!eTvL*0MyKMUj#&0+qkGfTu$o4JBY3SMgq0p?!x=9k-?1cdYmD2ufm@ zH1f+G8?!d3*TtzhD9K~K$rlkFWaxu3tdm49G4Da}oNL24utNs}M!iA`G^7p~G>9DH z7HA%b3*-mo=uP8w@a#cu`>%$S`f_M?>{ey+5xZnhCg2dg2fH z(-2hw?Dj#JR*A%?qq~0q_sTf;!2@I(g}=%U)c=Jk#7M~YkxSK460N{dsJC}nWV>j~ z7p_?fWD4xb@#{01nZ_P$ZoX08U2frX)q2lj;a;0#xIlk1t1UQ3)Fvp@ab z75jsq-?lM;aWAGvIk=}{;cnq1&722=~kbL)>&%#x0v1ebsYRBh^gN~;jxowR~ zi7tv_7aML8s#|(Pqysmb@QRD-a9? zkCB*J&WBn661!={0=R>HY?XEcT(lRbZp~YvKV>vL zK*9oQUevu0rVkeg7UBs}WIDok@&Vxrbsz_K(|QvAs23i5g!B00&doVB??S{QocGEb z08yVefV6xo*zYc zGitjlwtVBdbsvV4HQWcI2&9A$gMo$khF)&0E9cS)Vf&1S0IZM*&(FMm@WBW)`thMj z9-Q$rJ`MJja9zlFch_A9sFd1qo3G&0-Ac~z>^tXZ~UCM zC;lEkQI+9O_kkaOElvS>byM){fOXW^3^m*#FxB+~4}g!e>~$9ZlQUZ>=SJQJ=IsmK z_XFAp>F##RO0M?%rbAOz0f>j>_krRb8AQQ)nQVBCvbBM`e3tK5S;1bS#SRNwA_b~X zbP?iq6u_sn@J8!wo@HLFV$!!i*=u94@#C*uwjz2(F($zFA3zK#4}AwEUAId_NW1#V zW3U2x;S410^xN0$t3Q7m^Do+o)_0wtI(x7kQc%lLaVF48EKpDl*TO4MWi%6s2>;Sk2tMnrPw}|CH39dy1jz$*E{8i+)izq85^O*nP4`3H<>InJ) zDO|kPwo%|yyRi-^)4u4%1>5Uyw{@Hg)#;-edIBh@qw&q0KuN6(RLNr^*fNbLIm#|h zuh|aBg+egLE-t!~VF|9r{s!#nTf`)wKkC?_EG%q2*s{DT?PpV5FJtA2Y^^C9wBj1%_Yp(_K7kl{R+w5#jW6v*ew7p=CSYYnsxy) zC7Cb1%GhfSkd8^BHK1q8T9IjFV{W>>h?3}@cXHm;Lb*}^5ydUJd_7D4i&nD1da%H%`4AA}69$zFcfl2HEph;HWa z<)7|swGIOL6nUOye#Su@D*FU;f{V~af2ObBL7Y;u?xT}d-@(iegi{e2D8EtZe-%> zaWcpocANT>iRAr47tv)}XFPkz5h7v1Yw80b(z^6D&$uzWeI0A-it!C{FU<(^RPPvL z20+F8uEdxI<3-=ALELGrhujVc-j5e?CE}h0)+gJIJST4DzcCmB}|A{2FzHbv4c!r66CB_TD;p**L;6{lvXK z%+)yqUlE zyT5zSIrrRimvi@N*Gg{!Mx%ivZlIc*(Ez=v< zON$bjs_w9dT(`VUEJ_JU2~RX8)6$ctbmQFq^xPrI9}DL$ z>CSt4fBSo-VR5@^l43zGM_haj+~S#iiPi9y%}`&DSq!$Q>A0ElO3n+l_W8 z0n0H`EaH2Y?tEBb`=omNER3V~KcBN)AVML4cwGKxLkKzA@MxoAp^;?p6H@*1qi>W&B3(%id!`Gq&R8az%H%Vn4HlKfEo!{)cn&j2Mg$=N-`Fg0C6V-J5E}P z^Mvw5QyNJp186it>8z0r)*dh|!`wM*W$Xa0EKejLh=#1G>@k7k zmA?q7=QVrklJ({||9nLXdiUPFQa($m{jd&Q(H1#Y+cQ*7`BS|v6fM}ILegsd&%b(K z`pggCllFc6+vx{Cdn~88VjmKLX;IGLXQ+LKzWs0)&`E?|CcHXVG~izU+q zEp$7gIR{;Uqho6qI~o?($VNwV<3}F&rmmP(Bj7n5GNbhhG#r=?px`o8&H`5Q0}6CX z4TBSpK9&}A_|O<}Mut?p*6UId=E|4_d(mjcAEmM^<-JTg8!ge7bxiW-GM%pCW(_b# z1T@mHtVRJ_IVB!7q_q7}%5qBiVe$NYTDZic{rozV2Ss}NktfqBoyBlK^ZD>Ib4)8S z)VQ*<7Bu&M?7@e1W`Yjt5nbY;Y@?%l7qm1}GFJTV(ACjeETL_9nvYkyFKZs2NV zd%pU(Xs`uQ6|yR2R&8q_z}_@Khy}x#GyoPR$J3#wbo8rw{0%9P360A##Ep{aXs*~6 z3reZccWuFgdUq||)xQ7eg*5TCr_=PTda0TUV4A2S&A2Ef)6j7Cq(&;1k6jdwdfDfs zh@{B7kDij^(CYM;A5N#$vt5wFTNEGN=X3zk-3=9Nx67sgXtD zOliv?4b9!kEA3;?rgINHnod0MfKCp4EX|%dskGIi74`I&)L2>4>3PWs(&PCm z%8gB{T`nbFum9?=lCEXkK9KP5 za~}8^@!tr}AVP!VHVE?iaHHPX7DkE#vpOyVoK^#k?gF`Zz#?M}%M_*iK;9rQ(0C&u zDwGnWD0fx3mZ{b2Zs@^yuRo?Gq~eMK4lET2W=u+PT$MgUywaPhAeg7u+_S1vwkdVg z%VYkO`D}HQROLzuXq4c=Hylpqbr{YeUFh?@4_!>R>E7QbsF#?>n( z9H~>CcqjV$o6!nuCeR<;JC+{)q1UFQJ>X+n%y;-J$I?6h<_S|24DWM79z9Mq0T8Yt zq{@#95T2>{)nK?ohyLu2r?YGn+1ZW)uYn( zJx#7E&N@>;3VlJN3p5udwNP>8tX7&Y>F}5%Isr~21uG}jAo|v6jb5my``WwGIjugQ z)yan!pFJlfsm6!4u36R-11$}OFX&8(1&tzbQrprwDI5)*Ozzc5b!tpBE{vxmUwlM& z*6-Hb{;Xinr5D_KIGxcj_?%XzkED}YjGL0wl3jb%>W(?Y{j zQpD$Ura@ah_c5h4I}ld1>Ykk!?0Qg!(3Hdz5~Jj~TX8(C19&vFzIgOhI;S)DS=86m zk$j6M&Z+St`f8w@k%Ax75dW?F4yL)sp3-fK=QJ9msUtPa=5&hUvepi?bWXyI_TIC< zo^nNlbCGEWjTYUsZ(llj^jRAbK=F~JmXuQFtRGoc9OgA*viH`Tm19}wv6wDs`hdpK zeogaCYc0sar**CLXCBnFjKZ@mh~y=WCglb|S)LSpWfYVfn3A!ws0Ik-#~3l!z8`tH zf3G_zRz{(sc$i&|;noY?`vpuWeC(Kb_d;&uD~D~2ZH%7roOvG<;(Gpra5nFzc<^WZ zAlJ1i;T_M~ki&h+4muGw=ZfKQ*Ncy9g6oCbwfLl_+D68d^g21ZY+zVIGTdv`sJFsF z_cOv(e@j8ogJHNc45#0%sHbKMo}RZ5Ce#z8Hw`@-J*L7|&uW*H${y*oH-7G!^l~ZD zUFvl=H8(w>X#}PNG`eo^EVi1KzCd}jwNPk5%A8f}R%Fx*{?hJ+^vNH4Ra$(>ZPxp5 zK65<%kj@Ku$vG)w9r%L+m=qsU*HvMS&KeB@sWQzIPo}$`nMp16U@vMvT~ovTEGiU> zK4Yk?;00CRiU=p~(bJ#TGH!U;ts(psH5%F)il&#W{*{jNrFTe}>P4%^I;Q#kJ)M@e z5?<7mw9{$&Gf!wPc2D~D(tJAb-LFV9Y6Nt&u<&WkJx?9n=Lg_OG0{Wi;F`z3{;-Z5 z)*i_73R&MR8zoRjbg`9AXt%DNpsRpE{~^;oJe;LBOg+rJGQ-L_v)%@inP9Jp0&_ zwslTjV2z|mD2i`az51p`4kpzg8dn-Nr06Hv3Mm<$e)vfG_7@(oE0b+6xaRZOY}l1j zrK06}-9^!AtE|nRJ)=d9TGg-970>IGzCF4hVNNIJJ@@eA>ChqV>d7qO_rd8VZFnGV>cMSrJt`rdgpG+L4q zXFpgCuMsAuA2=&uTKV^q7U?~AS~p8-oyxpMJtmY#Ik~c_a~7tw<7D^Pv_`Y0wRlkQ z3ZHGLUg;?>h!oE#hS3L8G?K%pCK~;Lr>t#J7@dMbqAbw(*+S#>!p7q>3JX`a0U(YP z8R`te)kl6t;dHYwOqp$MdTf@Ht-#whG0_QRTHMd@r3$trW2=?*9fcfM8)7v7DE#>Q zLq;keR6+HKpzR7EgB5V*55C-&^{yVit49^6{CMy$-)mJq@N27b6{M}IRZ4L&PlONO zWP0AX5uT>4^(`%+3Y~>q-ku&EGtfZc)$l1zPpaO7-)*Sgo%)bd-!1S2ZN#z3L6_V&R(9>!P+< zwY9x(K?~`kPbH6;H7ANzW`^Q7jy6|U~x({Z{|ITjJv$(5AVoHPk& z0%%==@b(_pVm*|e;zWTQ({2GZ1r(khdi8p*NDj9>2E%j_D}Ci z^PF3tofEfz$1BwX*TFVVN}2B3oz83OV0v0tA>VRKTKwAabo!6Kp!1RTrwi&)wgl#ort0aRQZIakwG3(iE@{mHBN`_kKc&%)lN!m_ z)P$x0E*w9uYmU{>QCd!`XMOS5$&@a(tzmcm>2v9+&pfP=8#N4X+Mk~I;uq7b{8?%L z#OJ@Mg^fqFghR5tbUvNc4SgJLv`=!HRbv8L6B<#tq-%U7emg8^ObrdzaWq7WGR3yK z2`ThN_2eg|^ry82^MK+%bLL_?sRjnqP-IE!TpMLkDzS>3)#Qsh0dQJ*kB8Y2pB({P>O|$~onjDGskItZZ8-A^-w3^(gkn zFZF`c%Oe`WEG&vfX)KPCn=p(=r$>e1-!iD{`rf`$p7;@t__A<4GDctD##hAG zU|i1TPsAm6tF?%ezOl>E@dDp`DC2?vMKBRwB}>6N9q{##(JZ{oS7|Q=GQ?iX!Q(R+ zZ2R;!w@m{;SJj+X7Da;j;oBHO*WZ|Wp!D$B zUbR>HhZwO=K%HE(w-gpP>$OC`t)Aa~uev>*`JUHnI6*CgPa-eW)xsqtM%30O|ws(Ppuh^W=Ijf={2uT7cX8+m$Z#- z`VJi-yX#OorQv?g5}@tE7RjWmk|)kK)7dX*@tZ~m&dpv-ja}18_XwGs_b={B7au#V z;dnJ^R(5H}g7&s+82s>y?o7wO@O9l#HZF=l)zo`@GZqm8*vMU|c2w?5O*~gEjamkf!hjUuYHzwZ3wZ-$;XCF;-UpbM^eCues z~|c1mjlbd0lx@^>lDb0r)SW-0?J z>K$#rq`}ovBXdfPm3b}xYpJopt_9L~g6p3c3mCatGP9~GrENlOREeQ?||ne0JwNyV3-M*{YhN*8wK3W4|{HH->nJ& zE!Qwn5rrES0wV#GT3;uiqNK{gTDw-P0&|o%YF?vwx3Bk%^f|F_G@9_q8e96KYt|M`HEMklNuI3_2nZvNwJk?b-zOE z2@UK2{{3nC&>$y+#Y^9jce3nMQ1FW(VBzq z(`RkJ|B;V;B0c&C|2f_Dl2>Y%!E8FIz4xugbUJ*;T^iQcRK#-^(}CUl)4YcDXV2+e z15G2H&_zf~THwco&X^isV`>OoVE;cb8bx4t2zLjxG#bPz{yFvfSqm|9Oba5lurSgj z${^*CIdl=M41NubvY4M}Eu1~8DG&}%BA2?U@;Rd*I+G$6)ddm!8R92z4Kw(~6AO-g z9RbJpwV2` ziSRvjL!CnO3OL!>=%~w zHWDCh6^Y^&6&P;DLw^8xR0asA74-399YI$Lc1jcRQ&LQvq0aE_gyv(vw7-$=d*^G? z@*RhC(T|k>L#NZ5|KzjjR?SDZq!{*U$hN6UoNa?7lZ`+Ol|fe47G3=eg{v5r#ROt+ zlo4X#&>h@q40P1v7rTnQ;H>qf^wygZg=O)zs51)Y)vB4$&Vf^Z`-rwIiboCqw{=JT z{%7e`FQp6nbwJSWNew$|ltqo9WlcXc$EVa2?`R?08Jj{mbo-0d+17l$Mr2y;Db3}x zazTwBZSxwJqGW;OR96j$ht8y9C$z2doo~{~bc<>C;=D#WbjgtD&uISqsZTthJL`3H z^0%JR(2TYk>I?=B5^9Q%ID$v9Rl+df}?>CQoVlLkAX}c=}X&R)_jb za7Hr2`Sj3~#^XBd2idi?ka1QQKV8ys&TI>8NwG`X2w5XGQudkxIdkHSMgi0yl6>u4 ze0qOMk0s%@&(CQM3D+c#RyFO`(axl2+%ib2x+yY7F29sv@@z-ER9pY_O#@;=qXH%c}q+d3G*qIB&u__G2y@>zTg?z(PLaLf_)cD-? zFr(%8>maVGd(Kd&*TdU2(^L=?2%F#YaVe`bY6!{_{=Lc&e+oM-|0wjl<0I4Vp28hh1-s^=RZDCK@2iOSIeiPIaUt)Vd#IqI;ET*_@r{!$A9oU(&^V}KQ}{p8Zv&{ zAAK%;=W|keicCYyvr3-R*VKhk0D_W2R)5CQl6nl8zypRf3_IVzVM0Fkp&#QprY3;q&9xN{CCch~ z?VZ;iKaEIeq1uer4SecB&0jC2eeZl#T7HvO_PF#^9 zl3t7VzV&39(Ud?_Cn@fdVqQ=q;90FfV2kIjyIz>)wTN%=*o+qJX|bfXF3#zcJ+@Nb zbjKZb@X^_?KB~1G+6Jh$32QxAFuJ4#bu&8NnEm`LsB3BqV@vZpkALDzT7j=L(}Kdi zyR?9m`;FCD=xPMvw0iZ2bh{plIcL;}nA8pv)*dio#lk&hM(g!=bn70A`WV6GDqzCn zq1Vc^l8x+EX{RKTsnQpB{qs%!o7Y+gb_kH(;TEvG$uEL78Z=xv1A$@{y?KZcc3X=Z zBhOg=8W6tIgQnAikio)`jN8q+;U9~Z%9zzPMOmwOk{iK#*a$$`Me?E{W1}L3D7Ok; z1WP-9tN0g6z|?+7#NBH*Ar~h61D7`@#8QA-nEiEG_~UMAE<_KDv~oTjM`bJfIc%h5 zod$i$6r|UGyD&{=>jT#i&PPK-RNKS2#J53Qw2(%}iEYe~ZR?D+G2E?yM%Bl|-saY% z_ND9H$%;G*SQ!>>)<_T>og`~K6cte5ltng%Dx)yP+vxU{APynHRN?R;VzvsJ3R(|_ z$~3~l&WiMwy3sQ_Q@yR#f|F9By2CAf{54t;{yjSCS3S{5Z4r2d4*PlKlV^3F;4WQ> zD`hGi6cnex5i-*U{WEL|wWvtz80hI#q5$+x_;bK{Ink@zVA{jyzq_)WEWa_LsFCa!P9g z=8i9;H9$I>}Xu{4yY z4(OWY=N{8;1x>wlW~FeoW?)PUALrE*-^X->ZaLIEhm^JE>a{*Xmlme3djClyG)v-V zTn&H)r8`p_2Nx!__)>G~%i2Duu-JbwudA4ow&aaz_W@Hn^BQsJ>I{XZ8UQRb2Llk4=!yXz8UQ@l!#EFo4OavK8*kzOo*Ul&K(>;Rg^~Se2p|(Z+UM)r z7ll8m2EZ=vrO~kTE)8|>n^mJg3k1*Y)42z_-+&YUrs)w&VJ)aPIya*g`AWyGwiR|1 z2^Ma0z|NFb0JgOqa9&&7)Y+E1H=r6?yeO$_Kf6xQ)7gT1%jk}k|%NPBhP0t@dNY5?q3Z~ojjo{_RXr>%w@!lRuI8tcFPo)@acqb-#u zFQr{Atx3@N_$Ztujcl-ZazZ0JE1GkL&jl^JVQm9v_oMj9L^Im!&rrWw2s)%l4#-uq zVN}F=x0>#0vu#nbcC#v&7(k zH9wX)^Ji(7=&XE7M#acjjkj4_9md9`RO8kO8g9J9z8-J=aDf}{`P&x$K`Z>%qq}wf z$fns?cxdY=KEIUI*4`dwd^(-xX18Tkwxyo1Q_`qt1{oD3MuDxz$Izf)P{8CXPEe~% zSqYKnvZ)me@Pwh^OA05pthfR9Rr%)J^D{0*VT~4{qK<{+tgF&2f)S3s&4FQ4S@e(E zfGrlNaiuTe*)~B!kiVJ{YE}qSe%5i7f0yA^@dv9YI*m1`qBX)!-{MJyE!qOAPCYYs z2*Zg<$>>%`mP$d)abJM&Zqw1ZTm`;MeH{l%l{u0@?0 zufMh~A>n3$7N`D6xk^XS8Zw?&=zYVNvj(#kHV#A|9*CFqg!BfKP7%t(q5~d{DT4)7 zJ#vWgFF@8l*kvYoArJoWLs{t$UY3>q9mb;dVe*1E!OpX9jS5KeW4j}bH=B|X&8ZIO z)5%Da*&B2X>C@|1B54?%AzLXvV?@&j;;}ilkmhw#k)^ z66|P8;nK6ZOzE_4r+c$h|J^sG&auTbJ)<=SS|#6})Z)F9x>w-Nm!>EF=38l>ZZ({1 zoJ&hO3xBG+CtdoR6Y2OXbc3UgPF~cJ#w(iZpVC%B?rG?rRJ=8E(cP_viNZW^hjtw7 znM~)7oYpbJ;(gCelJnWrnbb;il)3m_o>D_Vz559*N}Ro*1%!$k!}enbwG-gD_Ty`g z-_k}6oN<#MQwxf>WW|{iGlCgcLxMjJaZ)NNos{1Y7>S5@^+!pNRmOi^=Ez-625g>| zHAp`akTk5|4G$imhO-!jz9|n2%wXZw{S~6@wUJ#TMQ#+X(c?{nj$iRdKGK7$fhr!7 z8lEz8Rs0$20XIhB@GU`GU zw71#KGQKkfPzvQ*u^xKrlI1GAS8}j1|4X{&z{O?ny@W zD}6U+4_Yu_ysUwa|FX?xdf}wtS3O<|AyFSiIn&CGqGF{8wrD@C&r)ubFOn=V8aAiG?=lpw z$0`@dQ@#mAzsTQ4Irvf-?rxsz{`AMm5peYf@t9rjkeP+y9uBTwtB3UC!}X3$4^{0I zRgUt9Uy(FksW`#08mZS%BOn9j3j8zag``|0d-zpl3LH6GUqgu26F(po~IuPKNr@pS3r zIjySKyuKFLaaKP)PPUMtwa|+klY7f;FGx4vbV#QUY8#-|3v_fs-RtVO|YH_jS!hC@)hiAm&$7$%Vsd0Uy>@y1@F>0M4eRSceuK;m5-qUv7Q+Q zQ&JC@(rrDuTj!4$+Dn?t8at>tv+ZH5p4$#lY3!TVi6>)cMr+yS7%Lz7aIGpgRj6e2 zP{hODrL1~j>RPJ=8QC7R)^tV6IPhpnHW^V@ZE+Su}JdR+7T_r2vc>DYJOlhO^fBi?ln11lI zj&_w2LUF45sW)R7Gyt49{FElXYjs-sZr)CC77qp7)WXA%m&L0fYcTq@QHIM*TT_5Y zUH=|Uk2GgNbad80Lye-TIh{Z#eS75FkEgx5gMM6BHMey@0n-8WcUaZGqMI4#bSyFF z-*3)S}wHI#D+5PP^=Zp@G7E*`y4aQPV+bX%Vr_JF=n$u}( z!Ju^>0aUz@D}UtCDz6~|H44gxrc*YuJ3rT$1frbq?lJl#FObv}XKNUTTYlqac_#al z_pf(jeV(+UO|pA+*~RuS+ol1~Jap?*s#%Vgi`cBgB_N@xGeA5e# z2Xav$$^sJhz*{HS9`C@#5&SUWUcT|;8)JLe`Bu(swshf*hKxan0&c(=KT!#1+T1&C zCKt+}=Z@Jc>f9f^2rlWu771AL4K};|I$mh~) z9y*bBYu>o6;X7S0D5at^_tk@Ji4Tvj=tOw(FZnSAqA)V6=4bJ8Ha&i%m(j|OaD`hu zwX@tJJ}$oshNy^#p11t zOXz(_(&0oGX((uzQ@#AIMjLER zhDAD3nsthru8`ti*1rBrItyXn&9`b4fDV78Qm_0GI&ro5N*G~}H?ly69zqtIK5Mzw zX({F_d>}z9;WD;h94_Qt!vuuCg9qa+dzEfs@5Rbr3fvkx8g*R7e;-27;q9<>JaF^; z;BLf`hsHf+I_jRl1GbWx$t?$#;~Zf{ob6rk*A08n_= zC=7m}gB!TWM0Q4bY==_8XQWN=nS^JMVsKBY4*` z?6zqDyzhN)?=+`7C!<2xsL-pzvQfkl;49ic0v`bb48V4KpPV;a0tixK;A( zBNV*byF%=>M7-Qpj3|DPu4oS7EvO)2r>}}re?iaKE5r|c_?bQPDhv&wOBHLFzpce~ zU9F>-T+!mXr*%n?E)lxv;LVZ~jXE(Rdi1JH;YijtgidcdHv&-V#}c!aCG8++X=;LNgjZlpy8xQ3b;!neL7ospAo@4sQOO^?!lQ5+VhtG@%^@(u zoC9~5f9W21r*d19MRBvHe??`W#ltGRf|lMi)G5=e%fdPtvmm!&Ymru^9I%Hed-vvM zILaRafwOTMr`YXr_X0i0Z1EfZc=}Da_rLq$M)($q&dRQnZ-4vyXku>*qmnRIEgb!TU%W#5Ljp4l$uCjH&N_*S|_+t^s7(^QXuGtQf;)EZJG zU1^uD=AbzTV34uKO!ikuGT`*+lMVo{<5XUe##MUw_cB7@5Bm68-;4>MMzvbVuu=V5IMNR|i5MEY8T4xrbIAsQ=V%`aUq{Eous zwrT(@EslTD63KF6&YhA1+qwoBdY}e{g2`2t!&w&YL5ac;i(3q(7oz9OOF>X`dcl!( zoaI%hs9xO6j?LBf`l!NJWe%?56w$KbWmVv@8-Pjzy&C0CpOqmVroy3P?y6w2zih^n zEx?l-^UL)5Rv7jd)BLd#o}qh`%^n?K)6fd=M|JG)C%^xm)c($wrgYm>n*Qdo^fnFs z-=#C-+5W{&jF#HW>_%Y4IB_6~tfod5Il5L?*oVc>D?YVstP-r`FF#i<0OejWZh8#Y zEEy)-p0r<1y_w0TzD)PBZ7yVDtcIe!Ds+!K$gEe$^fxL*f9AJ z&5WRz+boyMhqQDzVq{<*nEBygWkP8hN*PQKQ~W8;K0*dKFj*N^Fu-~k1k9Hv8XaHo z$^!CMK41<9*PM4K2PR>nVf2dmvG@wd$`D~0&%~8Oo%9Vvz)_Ego{11IObFP*(l>9j6B5`&(CwaI7*~|L zXtOvsjMokIH<-q-(8JaF2#oSgEOH$qB#Tq!9oXTZCiP1Di}AG`-rK4Hu=keE@e3yw z7st8{?KF?XQ>E0C>mu3ciIPh*T!7=93r;}B+Y4WX5zhAZB2qi`W~ACZY#zjrfB?!~ zC0l`un;O_Q9B;h?RT5OaY!Sqd%EIuhvIwmF#!U)=9yV2gi-v+Ff>}<1+tPtDZS6ZZ zI8nq~JHpa}l)tS55FkVU|K&SwN+-VePE-D4$1bEde*A0crKc_lUcE&P6EobO`vs|? zUZkA>sFmRC9fwz<3T+G5*mRp!dRPG{OyvZQJ;X_IHd<8HIM&n()E=whDs~l~IPkdk zfc1R93eBW2WhnH1k-SM?JW!X`^a{u_vQofMW4Ij&kR_KiRf4P6apvK1%A+N0(GsXN z0DV!V?D{soJW|FUR^Q6SR0HHi7llvwX4p2X1Au50M1>9Atjs~dL<5Eq4g#`ESVS3` zT=IjVjCnZ&M*leoxX%)iQXp;cX7j743RM3CjiHCXK7iwKx-Jt(1k#{0#9)e^W)Txd zK|U^h@I^C|@$^%83M{DMmAz$=d-Wk`V=Ur=GLZxRE_7LBGK_zO10f*^fB~RPdfFqa zNGB@?HKJ@);Ie|}4FaT!d3Y1!InF;lnpUL?Z#-5WGdy`Ez{7vGf%mE=!u7 zNE<)dGmf3+X^CfqGjmzKIS%Gn*r5oO%IE``z+>s&PZ7WHx5i2FUD4{Xke@`Qierzb zeNf*nYE^D${I(4rIJcVGwc}#N{@P*Z@;JHe^-B+`p?M^RtS^t+wT(@s5b;Fe#V}#N z-IU?FQBhH|!butT)-VNPZhp>>Xw80*nbeiD>i=uOna+w=4gSg7Tj{a4y*#Ct9n@67 zLVC-8yg$9^OUI=c{4kxk!WjCyrZhUB69Mh$Rz?C$(bUXEd{9V)Pa}n(i7TTV)@bQ@ zM=`Hyn2j1?Jp+vBLYv1%J=Vk7fPcv=^ur*Bkad6j1LjJB@!eua`A z4n4XLy`hSBnKs17p1z2UPI|+TJ+iFA&wTV#vkWlAFJLVZ^r1JYW$~;6sVo6qy{n)% zA?wJq6|rl=5OvN`l-QXi(u325_D?jOoyP|}89Z$;M&Sy%S%9ys~S zUQspjwntjh)txJ^ej1M3`&+_z580{W&UYBTqre3(xCOqwrG*)BqdtZYhjSS0^i&{e zunGnkZ86ty^>~_9Fa0yGIhf|Z@1<$%(Ejw2k9;Nl-A_EBV{?~v9{@MNvEa|84Oa|X z!znu!SP*EuiC)?B4*u|~`2kr^HuNNnVSSqnaToulaItLAc$^&xIaq@U3npHAhukXn zb!t!Gp=I$jSSLojf|jS8QQY)T@DD9NS3fgt-#$uI$tk-H3k+Jm{zEi`G>pFp)9B>j z!Y$(0BObxvUdb~U7#4v1%?l#-WC!dyydV*>^lv;Jz)>?ku1b#}*GD2qZSF0){6X7H z&O6o!4Yv(ahF9ZPQi%A)yCPN4M)rn7+$%<~mOvGzEM6`huP9s@1o?nj;z#)ACjNXF zp73(LAq0XOMYbhf0oRCKjGXujL{ubR)80ZTm_=!07dE2MKjHSOGog zICy1bUi#L#F=<96?f%2N(<5(tNgBWH)^yu_kEgf%<+s!A=Q-GCGEFXO1V*dR&+8<+ zWvyTzXIeqHmLd${YyMv$j8IlmuDJd}FHTOV@x;~Y4BAokfCZq1do$`w@>>d{ANs|U|f zurvTpOgGaP-+Fgi`JV4clSj^^H~;C^(yiP9Fg2;_pq2J6`1Zbw+S|^>H)SSUVeVfI z0D79eVGrVN1UC_h6tPCeT0{X1V8PW3xkd;lgQ%}zG~m9HJ#onokz&>T0o}~kux$@& z1gQap>18b#$%Ok}5f^ym=wV-YN9es&`p`D`xs|jw81zu^k|)_sOuS3?p~yo$kYlwX z-JlA36>Z!K_kx-s_IILaQ=^C{~^WgZCoVrdx+F$Odja zgZu}(H8c{cS<6Q!y0>k+2EewP0(jpC-aGeCf8syi-<}zN)u7}gE4gu74AaK&Im$L? zw+h{codYW`P^&!qEY(6b72eu-$tYd$^9yhM;2s|eXDEx|afj@uWfekA)D(O1Y1UBo zysHX?p?vtEpyxNhL`7=dw&!VGzk2E%&SPNJ&WULqAKQ^4zo_~C-8#zm#B?Kl^1E+M z7vAz6S{9qqJs~l`8xr4-=RR!DSDt{ta#1Fp(zqvBr;u4ev(7aSdD~iFcH>l7#VK0t8+UnWM+x zriCOjz*@fU@$daW1~tFJ& za=>2?KxTxP<4}FeV&^&NO&kE{ZH;j804Djx=_pRlp7!okp~%mv@oRa^QA9#shq~ z2^_w_3)AcF9?Uu}uk616-uHfBF5tGyTT$D7Mv?lM-9)y5sFCnIRtc0jVgoduS5WZ` z@8ttO5vOn;#3PjfS)Jlx6$5pFln50BSIe@8f4|O*IV0u&z}5UJb&~0`)8$bkf4S*>{E4I*(n_cf(1WI*dvL@#NljYw3E>MPAg>H-!H|Rzjd6&k{ zGLT!Wx)0?T$3~a1mA#il<=kTt1HNWJ@nrAwKJAmA(7`@Z)booArie{pBh(F=xa_B) zA(Y7l$ybN}jB#L(PU7D&8lBTTZ5IDp#@m(+fZeyQe4#tG`0QBTqdZCilm+Se&2FP`yBek} zOrZ#YB3`Z#y>YM_Q!YXVp8+@Sut#6fV5R|ZNezIedfn4He*VEjt#tqQ-JUvcxI0a1 zwf|c`^?15Rhx9p8i(#@&c0F-37RDll?LL8kAW8IWsdc`4ogs>A8_SKpZy$8;+qCmj+Ttt!TQ zH(-G7X!-!z(K~F35EuE3>6qSSt|Qzr+d=}C6O{+cGB8TSgi+y)0>1;Zc?ob1)8f+D zvy0;|`oiX^T)UXtvH|cf-+%A1)h@DgnD;W_v&;}ugI;T!z%rXoQJGDys6Z@V;a-O^rt23%QD z&fIfH!*GjI?k6<7|CN^?Or0NkWg35WF1_KS52kxAEZV7n2f2Vozztms+t9JVoDRs4 zGY9^_6C(l}0-Oe>UzDT*bX^3&Gv3r|OO9muG47`;Un+<4&0cxQ-J26uCVmm#K#3(Tzn zHw3*3o3Jd(Asc&>cAkF72-zX~GEBoG?JjUAi6@VH#2?zjOi&L# z0uB`L;2CS}xf`}-?VfrhHEucISi3ryU#Yh;dfvoUnWf}!8szs}a6vz2aJIyUEq={YA zHcyAHnOmafmM5{kN zFDD*5xK~F4s9?(tmznRb4!1XIC^YXi>b5fRC)-s5+qMC)_l~*ymdD!9jBj8XU|1qo z%3+Px;xOE;a4#DGo26$;g5FfAFhb|at=(?xnpxc&FSoX??(GVeDSxw9vN*$D5Uye;mo zqYb@>|210x;!jI=U3SAcsARh)t@SzQh~4S#9-Wk^lMd(E1;Jds8X^5bXZh1|qQ_&c zf`0OfZ^N!#Zl~REyfd9Yw3240bv3W#+t39)R7;QzSD&Z_0*z=s&Y_8QkHa$Z|jl{Rl2CYh*ngxt^z{9^9LNPAt$>P(Cm;W9h4FVmmT#o{wxf` z(x-9baxi{F2=-wZ(8|mc5xIq5-&<2fD>mvT7!#<2TEo!z7Cf>M|7>ftt&M6#M%wh0 z!5oaBjHbogg5xKf2*U)cv^OvJryN++6t3Y4g=kyUO=3XdW*8* zd41rN*+pQo7Y{1B&Wq?p86kAzPvQC94iOmmUcQLC=R2nvvN74(58SvPHsbD6K@8=~ zT|A7jLqcbSI4y%RyOo6ql7&cFRzP?jz*EfGs{RP0eg`UYhG57l@|e@Fpdc$SrF?$O zA^)^=ZT7Qz=xwgf0ik`Y4S2`r};tLW7);1Hi+GVHsk~pQewGQbbX++$S=8L(cYhTV{#$ z3MOGlF!|+Favm8Hr1Ru-EDaq!$at8;aEG~PK3&N`JNOF(zGZ-`%z(I5enBB3=jG8Z z%E@`o(xJ$IKvwy}?EDl2hgeC1I+>hLZvc7N$Y@yrp0_h_g#?V^Q4^>_@kW7in9|LZ zc;&M}i_sUE-Y<_^_-v4)f#9&w?u@sWR(Ab|fdJdY zkrKXb&4FLubN_g&^FTv4(p}Gn%3;t$oN&L~#XO~KD2j91(>FV@oDS;L>jU%S>BO!^ zy0|c#-txug(hIw~pI$|h-YJFT^w~370LPwb3gIvlMYz}B%es^$iycw=RXjajrU(?y z+O&IV#upWdKkib~UW?DbzWg^0mzsxcr$G>P-8w-4MrA#wk(Dvz3p_N}!Q1iPk-(La zK#`wsfLz$rNYzqz;(?nEzv2EXBhPDt>b7nGyzlLA@3ba6|E^pH(60#MyT7t7lc(P= zzDpRZoQp~yKWO(?*%2OZs`xNrgEl7K2Cwgc4mbnCy-)Fi7jD5PB*QK6c*9!_6L<{K zs^zT_f=L}VGc(z+`Td)oxR}2Cv=;iY4NU;nuQgu1N1z3>J+w-?#*>u1g(+V6#=trc ztNPuomR7*n$Uuo>cy(g|N=83-FR=$MVPFQu9rwT;goA&W5)Leb&z^zX|2A*qJE#nP zTY5?e;D^D@+sWUditSa|wzejoq?bvh4{MUd_BGX}zE*xO}-Zxw$Fu0On9 z{4$&#doNheR|(hS5g$?-I{fF~z5VU)(5}w6*xJy_}Bk(;^t1T6bWfm8NwF&2&e~ z-^LTkHUiW?!@b(=FS{%FK{SCH6kf@v;lWZLUyN?_jBpec=dg;b3~N;w{R~=|7L|UV zo^Mq_{wH{!%y4hek%K+%WWVrW+oWvgMd2~TO0n+HMxT6p z0xe}bi0Ah9BF$0r0Os@a_u}yO#gG@6En)DM-&O z_O|!$Z@*HZTBTDJjA6Bd^=qsF!2I_zo5{lrNqtuDbNni62`01{P)7mjEC7uJ=pZ^4 z`&CL=L0YvZIzG1^gonRls2|>Aj(#}wY9GIA-FaZ)3g27x9q=%@0Yl62eW8$Vmiyutm3nvDh49Fn;>c{KAs0X7?jPH#w zwi%pN^r>LuS>=iHt%uGAcuUJU7ttH$4ZUH8j{c$+u^n9CKp(9K&^AD>_T^+cF84w0 z*N?2W$WpKD%scErUkyXhOc%rgv^q?e4e^cmhaWh$Xt78nTMrcgYrYW#5!EXm;AMv{ zGN*Cjhe+WN#(eF=8c*T8#+ybHjU^gcHTG%*7L{>?vjDY#3Z^z_MBEg5)v)TL!KXQ8 zE`k~G267e%eRMX+)~Mh#burbs3l(C?N7yUREwdP9$-)=i^^kn}VcFl-|uNW)W^z#wl@g78L zczD4P@hk0vsMKx|mUcsF0q^|c_=)+k7ksex-@Z0g_1oY4moI$yl%H0a1s}j|`a#ME7k7wA`GL!efD$v}+bJbfJZ_yPgC)ixo?SM*tA|&FBBCy>O15YT57(yq0#;b55UcBQm z@B9-yA{TFi2#WYYQ*Zvp8ajngTX!_hVMKf@n2yqI>7XKp5%uS9C(6zT;$->2nA5ks zxR{4Lz*GNKjL2C%`0U4GNXr~R%4+-xpN0jG7ch^J#oJ(vJ>$J0B=>b(w<9ANCupqL zCoJL2M>dhBxLNwpSWujJ9F72mLwwBmw*}tg4h#U9L3@*Kq9b?W9EHFX9I$%KUT*)W z!l`Llz5rZ)_T>B%VTC&wVT@nnhhUYuk%ux3gEPJY131cW#8my|Jk`i;EhYiaT5PY8enC!4sfhEN%n~J~Cx1o{YsCciB|c zR5G${fTHK|gN7a%3x~K0hwHw>U8P-l5awkJRy^smij##+ej;zun9X_A!iF~a4L&_? zeo<5A$M}VC80UpA%PZy#kJ^a?Uy84X3!k{#e;^lc=pYBeUQsl?v8I@%!1xmhE&cl#UGBl9z z_gbO{G8!hOBW{D-OPGz?z0<2Bix=DJj=K-1rjF&M&ZEe4q;ab?k@g+fXN@Tfn+iw% z6nPFj;wD5YV7t4I%7aP^m_Y_S1!%_!dmw|Lu9{oO(v_&17&*-brN>ocYA2v;S^^Z+ z$R}3LuW4?t9&?=FJ-^sI-tD~b{Z}v4=QpGqrUCH2-+R~b$=#jbZs^3i>&Z}1QSzMB z7nObR&v&>D(7yp@FePl|v4*9pAl4wP(2WAL2UR3ha#=W+t6GbI#87rd=Z>e)Q;9U|fK7_*HjX@5yd29H#s;RwYZLV`QGU zpGuaJahYb=cg&6i21=mFD?1IvT0)%r-x=Hc+wc0NKR#Z&Zg1NS(*PhD8@F8i-Id1t zk+pUN09RG;vZKVJlBH4&r4w$zW1{jcaouP1-=NX2OpRtH(|?&tSWAKPw8{BTiwDxDaQ<6V2%P^3BB}z?iDZ`X=O6DSzSMW%w4RE2<&vRnFi9KUr7T zJlKLuKSd0L;HA-=Nb~b;AzFy?p-0Sx9$cfV-o72LOQT62nHOhHD7y2Ysp$jU^#%C) zMxb_3kszH{WDhh1E$aU78@EJfUmXZX%f>An^@;Nn>eZY%t3DlF1*9fA=zR>qzxlvVpPt;k z^y@knH)q+6r)^o_H;%nFMifd_k3#_|SdrmNpeQWn79}zRgx}>9#t4hBO%XAz5RQ5W zPlOTn!n?BK7mqBJ`=}Pf6)=UnjF&B5lj5fp&kTCcV--UUPgu~QqHSoA!lWwRryqGD zt+cy#cL8pj&mn8Y1SRc7u2M3k3`)hY7MUS_C~1?aG6}Mwfo5`%eaBpV36%Ns>W97) z;^-2f5!6^&9^3cpKmFc6Iz2kx^$&SNH2_Gid*0Ukqw$G_`x;{tn@YVaS*UBpIGi=Y z2M&Y1H1XZvfT&x>Tanzg((Vsu5Kkq4*kL{5SySqVayb32_AnZ~y%tLofzl}O(H*nZ zjex8W4KB;fBqyA4PBhekN0VAN(1e0|)3$bOV8~nGn3HUfVh;Wcq%UnZ5;C#3ydXlp zJuwHm5h6RVmo0%RJuf3IFPn)S0_#eEg@23fUH6ThfAt@&3%tz$ZpfP9fB5N-zGe2z zuD@PhTAoyY){@jJ%qnr!yJcl%p3XX_j*=O-pw zX-RhwnCxMlK=~0aB&-Lmw{3lkx6;zj7U<<(hYbrZr!%lnf(HCDd@hFuGRPulku(bd zF~B*jfAcFRqroN#KJdpNPkZazFGbQ@4lFJN#7T-r*&CV$Xvs3(Ua0tiYm*^63&6>Q zjW6+PZ)4uWH{Jwk=>W~_C##vrw?-@TMqr+I0xCzkiU{8d3m^k@UI;{4Oio&{Zm4q4 z4EXL7$VE9u(;z%w1U=S?5I6Fj{tYfxse8iUPxyE` zZC0~~>5zU(bB_a5owCVHe#F&IEY0CiFWC+ni!*#di#Ly~Jbea=obz9W0j%)?eGebc z<$OmsE9J>^ijm-e%`(XRQwE?VAu4U0AAKpFPM7#NUk*n))b%EO1e1!5X!&aV9FM9_ zLMDXobuK$8tX{OR2`6&~-+{+BYLR9NG{+mC1*QZ<4i@(#zbHlaI+BPpDIb#VlGS=Eyy}l(43ZHVW z0OAd-e^(h={@#P^kU-5>YTZ?&CpJfpAn+iI6dt@~MA z+4DO$qVh+UwWio=$zJ31_Pn_L>toINN7h>qctxWTl|0(0GAKD3-6-3`}XcLFWg*dm5|qm9lqJ8Z;J~Vil-r?`h=>rG}cJKRq$8JgdF`{gp%c3`4?WL z6`l=7@nDpps>fBQ+NcrET6j3L2@&ByrzE^%-;uzM1hzy1O&xYN+n#)+z4F>$-x7k? zA(0!l0r0Q>?N6SW+_(5^V-3#6^` z7h196Jrk_HjY|HJU$`4?=~viGm{NYFIC|Y{z6vsCwVt~Wd*F#vZVb>U=n(=~IbJ4@ zOfW*I==61sB0l&>Qz2k83FE=ZdwvV|aN}KuS^Arw!VLeAQQ(yBuonD~J{7FogQtM+ z4`aRWz^mvFLLi;xUWV0g??)^B`~9~1JHir&6=`$Jc|7u&#j!8%lsFY~zonG3g)u_? z(nllY3tv5^k01O4wuFl~uEHmG=5nZF&FRJ8ZZ@)e5N&V-3;OK$mGFjL0{=q;din6D23I_bcIE#F4)&?m>DBXl?zgUof0I=rU7?xQwZ1e$lB`4?l&xi61za#z4;p==!#^WyRJ zRP0+9#HK01Pdo#OsOI302FvvHRNA|Dj}4CxguP71kddt;=%W>WS51D@<>CS5kFom! zSm?)SfIDsZFL8@_@+KfgRIXZ_w>(DIH$g5VL&5dS-_CJRR$!eEQ+_+<%1eM%|8t8I zUp}>b=fAk}pR-JYuP0pmgiQF+_gew%vO#8 z!?0`l$Xl-ElL-!d4TD2TN|oAQNzGP>SCl+_tJ%e$^HDHB4XwLOsp*NJeyR0|M)*WTK8o8ISAMG2_v_TsdUI>#&nCdS67> z*GKyr=_>k}Ci%*ZrvuQHQ!gpW5SivjXhti4_kk9l!pK2@>n0<|XWyG$AXPy4fo}^Y zBJ|+VX?s#)&ku1h_F@hV#qh|pHSdH0wUVB5J(P-h1bhiq;&I$>r!#SBse9;G-t(T{ zKVQP#i1yHUH)7nYIDhWJKRWv6*T4Jha(m*(yi-P1$zL2O6W>yf>P^*Ok+t-$tk`=W zyapKxW`LsgoUU&X>*~@XvOh|{Qq5?_9%6j(_<=9#LU@VGu2mgCxnhrzY5?0&07c5I zhagWF5smzYsVL-C2u0AwmtfdFWT9ImoD-<7GS>AK0pbFl_Vd^uVZyTvymAWK)Z|p^ zbn}h|`SlMAIzQo##DojqGAl;QAuO)ccv)ah2Y9(52OjqY8b;Nt?5qZ6UNE96*@h=`s4y!|w35B@K7M+SW1)$1=Vg^ftNz z5Fg}emz&cfFD+9$5{@2}FjmhG$STXr0ydw_2iqErq=ja3(f6`lQsdhhYg`DzE4R@3 z_R2^@D;JP;u8gJ;uYX)&MR3p2BJ`}#Y-sYklk9a8}g&hu!_no8$t!xsvYoA_|$fTjMB_cRV3{Q z&(jdlAsBnUHTRs**E#n8e>qnnxbB_ogV^IO3krmcQBd?%(~fpa1I* z+&HmbJEl7gfRK+m0XHAM@N46fbN96xli_+*-%$ae6gFx=DV2U{AC=a!Hbn4fY3!&d zmkH;+s!%Z0gNy>0p+dxA4THDA7~I;hYmgb}WfdwU;bSuuD|p4z69~ymdc~h7o`G1p zS-3qI>wj!K%dAOrDDP`x?Raif0+wt0^T~#?@*4SUqs(k$_{oX!G^f-4E;L{JYbw-R zTw1PQ!R<5vinM#*Z~g4Wn{HY9nX%U5L(SX>ppq^Pd(%znl#T=Jp@6FkTm~q0Qvh-Y z7DLZJ>;p)e(!ibYSHopGsyxcd8+$AU1GlvE9ma7qFwYA0H7cO+bDcH3p%DC)yqaAo z7H?bOPe9^N#FiNfXUH$h!WPP9yYuE19pifjV~k{67P)2E-uTp&BRU*Bo}foW!_8<0 zvfg&mApDRf(C=vmfG4M(Fck-bb0Q^Oqb+z#<%}2Mi9LV`3S`U7XTE%Zh>3Kys!1nWopXZ4ll+3HkyVuA0 z$IYc$`$jmyKXk2Jl3tQM-GRxzuRC>6~Buq)_?ba4H?rGK17JYv|8M`{T@Os}pa13YR(F1E%nmLe0Co9JqbiT6u2Bq( zKs2?A-jz`JWM29{FQ3B!6vT7{Gi;y!0Ib1I5YmGp9Ha~fBV8TWy60bnM}nF1&rKKc z9Ze&UFm5eMEh9j@x16}e1 z4S<#!)$4Qb66l4C@OfXp8J)@?$Z+$wrIlC;*Iw=hy8<(M%Jfn!zv!QImnY`uIye9F zyMF0o50rj8c9+0b$a&k!@>l=sKmX~4OH;q!T^et3p$x{?>UzfY6|!E5`^xTGoD^*9 zovCmSYh`g!NsUp8QI*>ekYURhww^+ z+?osp4lbJC0|(g%$9a=H<&7+AdB~r<_HE>Gk_2o_3?vxgMv7_0cZ7AG%j zW&C5+e!xSEhAC9o1z}+lf0KU^j{MzFFOu%!RY;M}wh~_s#0@+2{Nf$ppkqGv3lH#Q zyyAkto~{x<@_`U)WBy(r*N$#mw*0_aBJr{^u{7U3_zU;G=P&-SA8zN{C1AyCM;L=8 z@Eae#_YZYk@IP*}mb-ODX<2zN!ezR8A@D-lPm1C~DO!@^=dn*poJ{zcT?)+_9_BOJ z4`jSS;qJGB^X8}Y{3%ASfz9WMHeJ2LY_(7Tm7`wmEMT^4h4w!XJoQ z0b_{=`eE1GYtihM(;1Kf#=@Z9j66{VDhri&Wro=+fNR^LJcsg+QLv576^|M*BveW% z@Kmy@KT9TgAZ{5Tki>HGTLkr1+-g(WYuDOlnCpbU9u24Flrk?l0LPR9 z&pg)Q_S^cysHxS`Dq%`{Kds_>M6d#*vik8F6WxXGzJI)<{422xwCpqh28yM2{LK%& z^Y>c27ygN^i|^EpfZBhnY^Cy76JRJ=Dm?40tL7;y3~-=$^to>}Dtaoy4U@TH5$L;FKG?<*phry4e{OEh^6F(zLtUn3 zmX(T!HR_Gc(!SpCQ2uFbqBFO&|DU|`J%9as*Bk#=o8O%Vzz7omKR?|*wgWpyy5LJ8XoQ8!e2-Fv^v zc%w+eguC}>Y}7W7Qq+yGfQfhDuKCXCAAEz0iSKyJA-kdCJ--EAV8WF1`!SkgewIH- zM5i6_Yrgw<3tr0bf-mTJye)_|$p$R%9$I}^){qh3L@&a25c$|hktXBttdl9zqY<>a zJob?v_*sQ1-$JYg8@!fjRE>W1183@y7uJ1Sc%H@?wI>dQy%vqY0ckC`@T=g9a6@JR z)BlcaM;7h(i*M+Lop)(Werv=7*pP9F7xt2FFXcMShHm#;9-C+{EZy|pd*Aa{?+@Ia z_izd9GysOnrpJeofXRJxzo`m3ZN`tW*1x(5*Vj2sd`+q zPyrW}UMLSLHFNJPz~%^i4#$DQb@X@e6cvK;39o(6=oCkX?5{XR$1EVlXH54gu^>?SUP2DCin1<5P*~66gm8v{_L3(ckgZWi-aPtJYNy(Pj$=7SAv;=WpER@ zcoQh2N5|`((cyvIdWO_5V$Oele(|PX{Cn^D%blM8hzYXO02onFfc@s5yz7IL`{#eE z(dwRPjBo4)!3_jspi#+{Z#H)jv_EP=H|0f?S(A9v@psmGcUx1<-_m= zOrI6%(Fh>S%Qa>EO8e!~y^{FI)nqH_UA_d?=d?~2rIy03i~=VuI!j$^AU3o>l)S+f zM#~0QgSm>C8^?moNe5pz>uGS~#QxfgDf|4p%abSOmJa>Yy}$IAAH4QBymmQajqHxu zfCN7GjX!?m```4=PcJVu|IYZzn#sk6eYCD$w~QpV}Y;&HWWyU-2{-Q$8XxAV3-)i!ZD*6w4I*{ z5?g*XZHLBLs^hpU{Fo!DV@y9T=JN9Q|HA zUx?^slklKrFaOE()l}?}tL-SzDt3dFyd5*};6?CRHkNOUUAdDs@F%*qav%C3!)*7J z`f|>Ec-nZ*@;I%QnNfis=$j!7>ICGbhcKWgH`@sTjM2}55I@0~SoKq-v*8HWc2~eY zc^Ei6!0|9l_65G@QSzy1A)g#KLxY!i%sr(ko$$BVoqTAf`@;X`-e3IK|0jSQvjGWI z$-V)c8y@uEeDJ64-~Hm59~qxo_*8Rzid<@XXcWPO0xn^3TkUQoNMICNS4@Tz&gSD~ zkwox~gj2i=uQjb+ee{ClGGCST zUf+#^U@WZ7h%>!Jugyl2=| zZ;=zoBhqRMF@wcAUT(h7fkQGD$8XQ(*JD(@b?zJIktehz3Ti--z!TV#jtpO2E!m&_YBn z_v`1Y>oJbkGS95^*R6hOW^wQDJlA;LkKg;=4?le^^Ls7xcIA$_yOv|UMZ9r$fb`3+ z{OUjW=+~EK8~=OiPVDI}x3`G;YBDGgZxyfh3l|hUiYi6`STtyzhoYWxIvkPdL&kwS z^iAVJo<_TFrq`^Dz}HaPoAmH_u8)lJFIjdBSMDYUZyk91z@678Is^iqFd-+SpOehQ zzkc!!p}ga~BY_4rhyyP45cX^ zYJOcFqtdM4a=KXlh9~~@(Bw4Q_U&>22RGjaC=Z!Iyjk2t#9;O2FA@sc!;DC~FZ2FgQF&57rD)7s6p+BWp$TM)mFX9(*@$bR-;0zA4mQ&E} ze>+}XF2kR+2_9C{4k+GQblK9hOLx};i`^H$<40_p-;VMxCA1m)P6J>wQX7Etum0fO zpPRn@(mTedXMd~F8tY!^t%3ts80thJ+nC9fuaKfpM%`%h;HrS4&>CI7Hx|GuXsbNs zN;tWGTLrVqp{$5Us0-i`Wo!$c^wH_&jJ?;HC46>KlW7JP}vD;~xcu(?BG z6B1AcigFd@ZWC0t9ZF04`;#taxwNq7x1O7O)jNLlmp=TtZ5PSsDMC9_09z>d|Mho% z=A6j=(|`UC|MZiK7bpLhm8Hp7cDhS)VlmhAVNgl&xC*ByfWZ~xU%bPX-Aow@N&g6dDcA8HI_^gff7 zDbWM1ChMVj7&*nwGv@~7ok(;G&hVYLz$xQbySXiJ{mVn;*KH+?ac2(rc?kT*6R?-} zwy+`D%fLuPX{b-O8fjr^@}c(Ff&cmM{i8qsPbqzJB-|aK(IjwXSFVl5!?uI^>^DC2 z@Q?oBkA8S*vC&#tZoFQH_cra455?5VH>0#Uy79(wW@s zH-Ebe&W^+H*jPiqbqVZp*@(W`lEbzhO%zCZqZFmMq|9Th8W^U9z?OT%=qLo%6oer_ zJ$9m>Sk#EU@nyV8!LoI(fl}0;4R;$+Vgp}}Y&^OPuKp*y6^H?2_|Wjju?Q=M@#P}A z))*i+gJ)FqD|f!2F~*)E!qFQJ8hM!1F0j$6BEA_NaB#wRyR~)X2tQ8CK+FzBjJflM zHau5GG_=||Tq5lv?leS9#=L5p%Z5#X0ir|c)1f_P790N%z@PPvVXlj zJhp8NPTf>=;Z*p1d&=bPj=G4<7*MNo`Og$re!3!Lp{sT_6@NvH;Kdz)i^^ zBG z9YNRa91d_GSO~#2Aq01K2n2U`w;;jYC7cl4oddz$U4vVY;O_43@bmpuw{F#azK`F` zbnTw*u9~X-Ful8XueH*5u4vNN62&taNV5VMaHWWRAIZU;Y(BS>oQCfNBeVNLn+JFb z8<}S29(@1SoZO!HJd%3fN+Y&iROh~%#RttQ*-v*b-#o*33(NJNV9yJJ5ArBGS|@~7 zoGWI~TPkh+h{8d>9}>092STwdO_8hemL^>l@wp%o zMj{i*Q1%e(tbFzL?&N)9;V`L1OLB1D%y-V1GYa!i=v`9OUl&tf#2|9Y_ccBF7LMVA zQ1+jw_=Q3+b6&!CKBZg{Y0Pz^5~hu}FAKcQq(fCzVX*R|8 zcgeV_rYrBi}aOnHyYd3v{;H2YYc)GoJ9FE{HYcQl)FIvjv& zn{&5Z4g~Or2;AX3FG61&j89jOJblp3bKAr9{3Ov_}`|RxX)iJKL zuq(@SG*Fi8IrL?ry2Af1ze|dal`~?L$nuV~`~VHs6*T z7j*VLvOlk!pZJ4Y*^7bCnkOT1+ty&c{$q#n$&(%83orV8Qpd#=U0;yNjWxW>%jOBG z$C)pbGAx5wCmEpU%gSyA2yQhiBcMF<0)o0Lby6V zSzEnZJfwMSHQ$awMbL(=OPY6#-)&EW_bGVSG&K`c%S(6|!H+QL;LnO>iea);r}f3o zDfK!Q&lF}w6L()>lUzVa_3oQAf0aK1is{UI3TkW~$aQcLt;Zu>-FZwnwOFT&t!X#4 z!QLNqB`ibnA4dr@Ly-4lRZzXOp#+MsRNb`N?e`@JERk5QB}I5V`IljbMl^$Ee;O4e08 z((w!UXET@U_y?P`PJwY@+Jt>d&r*RrcKL{JQjU1z*SY-{eD0i6+>n%QvBY)1!k6r2 zw?X0E9SOm@q&73Dv<>uhZ=Plc(x=aVl~CKX!IOJxJR1s23gf z;kP!)FVTx~cb!aimyk*9<{P(7-OZI^goc(v2t$NZdQ=`pN*Jc~$*ZGOh_qlu8&-+il_gkbbdhUzG9(|w&$mYvq zHR-0%OSQb7`~JlQZ1~y2$M~{)dhLfRgHwDzHZ|wSqGiXQlU}Qvn)dq-H#V2zp9<=m zRk?FBkWlTa3y&og?ymedMSEFA6*35s8~r+nz85XL=i9#Wk8Dx#gM&BO72imOIQQ)h zakgKjqqXO1PpRjBdGe-Mb|xB)lzUMQxN_by(FuRgP_~3$%_gqYe=``MsXX93WsW^( zv#oymsHjQT$E`E9S^}rvd}n!z8?BDIB}WP>*WJ#a3cH?N-~TF|=?U#R8QH-tUru{k zOkBRHWb}Tiagc2Ggx5POw7-$@e3E#+6o+|S$Y*%2f~y11pgWN4n=qV(*v7o0J0^)c zaw0B7ELq1$b{h%x6MxYGw%-t8BYa@#qV#s}bs-#4zdOd}$AreI|1i-ht7e*zRu{?e zfO~i&VOlf?O~J32==_=Y0*`p)W9Lo~!}U*vbkwq{(ASY0t)KcO*C}RHd1r6~2*ja_ zWts25tir*_8zdKQP>4{Cw}B02L0{kpwSD60g5?Z7^wgsM{BF=xChWJzAE-lZP)$$f zIbyBZJbU-khxq*(7i#U3DR5O2o3rP!Tw4}=UG?j34;f`h<+E8CzpAvZw(c;-8WMy3 zyh;8SddiAY(XTW13JXJUeF?y0oP;d(fJ~k#x0*>RA-pCfxWSWT)h zFB_lf7&W1mC;7xq%|RhNI9pHk#Ap51_I-m_wXz@|f)H^tdZIA=UB%Ms+HVXVRVYt{ zX59L2S3+5+zNGP-sHyIIZEX_CNe&pzh6fqw!Bkuu$>M5)nI1njnva+06&PY| zw`QB6{dle}aJi{}I_uQQ|4E4UbBh$xp(WibQDJdA^Ae$#g$t9%ibdsBM2`$*KH|4D z|7gUR5JY&4GAsJ}mJnHU8e&CXD}(ZB$yd$H3bQs3nk*ielyUz~TDkHQ4!Yk13cjoW z4*^dQ&f}5L|I`9-Rqn_#)ui;uY#O{u9HJFV#@PrjWNP5rE$IidR zHY0g%_wH5&Ej6PSR!Qng*I3so#=m8eUSF4h4&1Mm;m4EA>NDH*q>BooLBF~Jmr&1! z@-N;w3DPq}01M5?jjICgU}?A2iAhJYMxFl~qVW|&B1d4iQoM~u+3F|pRkrD4;+aCz zhk~B>#Zqj^n_p)diGiV*$=q<%Pemb zc+u9sx!jL#GE*cy&HH1-z&ahcwZD@g^v}DkDUdRs9gvpw`T@_sppO0cM>REO=%Uwe zk9gGet7!gzMK%KeKIo+q0n^uwt?-_C(Cm}ZVBQxlV!_kNljqyw7lB#lTor*eJ*@tB z9j)K}*`Jy(=>l(jbrtnjywCUZub&_OtfY+M__{R+KL0K4nO=Z6Aoe=IXs2;bscxEk zdU@P-MjH;ZuuKY}aP*8P?#rZeqYuZEhTJj5E{OWT#N(0wB^O=V&uV)1GXBYmI{$lT zd69R`VRQqzq|rkWyrU(%t>8HHZN!`#6Kuabt?=L9M!tZJYl`;I(ssWhGy^8I9~$DX zR_#u+pXRRH|MtZ)>g)4emiwM=;%1coA{FG+kL5Z}mC9v54$PZ>x?Oqx7fyQo*u?P5 z*Y@n+=~4>F&B@747&naz+~lgo3h%a&VQCw~lqdrKL z7Cug)0u$X%enec)7SK>8eF~}(aFxs0icKbJ;0X(=k)!->wl<>9PpUeAn^?DbE5Xe~bQ zH4EBAW{%pA6EQ1;==%#qWI1ibJR75929$*21J;(LK@%j46GFTNS>LwF4byM%Qk&5d zvKS$#hjmj}9}XXWDN zvlMaRxVvPrW??o7sLQ#(?}_J*Iaxvl)SIeIrPVB^SA>0ctk`B$8?B3r?WPtW&alhR zAx*Jm=Y1J)yXre_V14&ryu+L;CWEZZ;tM$h?V~_PVg11XJ}{Tnr`0dAU@@e=u}InF zE#`GtJ@Y;UetbC|cKvBswQXUs{M;sT(_ZJ4g*hd1t|?WSUxbao$DC*RE-zxv*Uj+s z)c5h3Qs6}dWpJiA0e~uWdW){^PZi_(o{rkO-5lm`=ucH2M9S)?@cq`?OV{@JmEf2J z@{etNF@9i!_!Sc*yscC9GB-Cr^_?j%?o!yvatOoU0(R@~;Rj|A(|^?zU`&+rfbS#f zY@QA;L38|3JUAv-UiYP5rnwU;K^@oK{L0D_L@XX-X3k>w6eu1AtX0le`Yvp`OJ>N< zIP&#FdL^G+xXx4O=kv(3drfaqUBjOlhwdgqb@FxDkhZUv^^ zbQ`NI5$tK^Bqr@2csTyEzCN*W_6WO%;aC zbiOqdXf>0eP8TS;G~S*DQVwjkBL`!akt`-(aZ@%7L^HKX6zHf zvdO|R=y?0v#ri$`nL+8QvB!Rnb+ z4yK0~B(r58&-^dw^i{n0HjjPN(sKe8@YU_=Gqr_Jqx4l#v-kSGL*gg0siEE6h;cWZ zCozV~LGZ-I;U*7nGf51ToqL!6Iv+c)Q(s{+j4H#&4BE5QtZD+U(%Mz(!axdTZ;-wj zE`c<*=;zozUL8VKl8FKN@p3cZ@S7&=y)x-gNt1kSHMBDDj$K#u(T%6&HZAY=#jz&I zdt-YSH)F|tg17Bz0sqv~aVfD~;POOz$oP^T`H;ayoEPSH8QqE-Y`fs;bJ_dH>2`Vm zd?7xCz+Y1Q1k)#qU^v+BvM`O;DoC`a6e0b| z;VLKNs>b*g+7GBgq7hZm-i2XJaZh}s`I^3_K!UU(A1=Iqi`N%dxneKtZd%;oCoQOx zOzi5#RGNrM9ciLfPlOo$WzdS2IG?coAxWVVpFA!9&`#6)%D<8N97A8*X>-`cK)%?V zVz9xC@Ty!hKnlMcErz6lW`0E%!y}aN`hXhHTPD|~>oMRb+8zF@>(Np|9{;*26fJ*E z4kGW?O26vnWGH)=D1-2AYe7PTkgl+AOnJV{z*zCR;omzyFc~f;%nrYS$2HS^E|?ar z(g~2#W2ZsK3M5TVJ3*#^~hXXXPZUBY6j-z01vOJEJX0~|1yj!Lgfzj^;2XC)dE*x6u9G_8954>~DV zy2yGJXp7_Wd=$?PoU^-CUb;9HY!W_A9x!_0g#?b*cA?mM82Uc;nzvs)nrAF8{Sf+s z6eRTD=f58K|LK7i5~N4Lgg?eV{?>;j1Fx6lS2@wLF9reMjG4Frc*~>^1vzoFHw159 zyU-*hM3n#l?zIXBAR)e*OSj4UR|9fX68{2}jS%g&L-jc!I#DC;5F08Ix$qFsfW5?+!n{XbwUJ$q`RgCj zYMa$>ZuZMPuWp>n^{kwAQDnjYz5nZh|9as6^*}LuMIBG`*76X6n}-0Y_uu-qqv{d8 zrrYl19eplev#OW=siTQ<{nXM{FM(8nI_{&9@ar2+@#@-fw<2SfJ(WDBRc+s*F-+$0u3;y?AiTs!CnnFf&50=7E{+I*nD;vawTjB?}iZavm1{zd>>&+~;a) z?v3v@`g{mA90=kqpB*p8Z9Bl#I7~%V4zc;?K^s*eyP9Irv$*pbcjRZ781Ndge2&at z6XrzS4q}MU7d{Z4zgGz1KNjO54IP=6a3;wo3Qz|yc+iz=C?xx`iZZ?2h$Vy^ChE7o$DB?yP>;26ZY@Kx`q4iqFFGw z_?d4@q6KR}rsTDP;e6WK^n^?(hBX{Fxm+ZF&5WH3uFjTCypLiw?n3BJt=qYkS9u4o+4E+YERXBR{Ru9M3rE6R5`q{pwugL zKo_3K!h)*rHOvl9LgVjhvh(%t0X2k$*2)6F; zaLbE+BNF}tjWF9wU5o{Vl-FE53r{w+g_=!u`%K~6c*;>|88I-Wc^8_6T|6i&&+ zvOj6tCafDI7Uxc|#^1L76Df})M`a3ZIjwpBRjr6KUM??<4zd-V*Ok=NR>_cXubgpJ zdjC%jFO01QFAM=9bZIY2@9`f4R?{=>DCp%)ugM7F6_GTM(G&!&4;sDKlL;+(~}i{#~xolw)W ze4n$h_gP1Bv=HU#dinaEviWSj{JTv4%i}*|bAhugb-t_em&*xX4|9Jv-o|`3g{xan zspAK=Sy%q2?1gf-W75{M$6D!j>CcZCY0C?DB?Hz+wL3c(e=Ea!R9M9b&BCB`aJ`cW z)Zui2i!oq>t`VwuMBVuw$yGiv!`BOU&~K}Fq^Tx}>>|hP5|lEQGRIm7wvqFfG(W93n+cfe z6vOKUDa1fLS>!@_rTnfni+|{ycz^-}kaj4!fIc|3?Hzdy;XNC9B;q#D02g`;@0HaMvl`E>S$8 z-|T%fNH}A5F$0VJ(JLWms&maQd&R;DZtaD!Z`r#&p56uXBi0F3C!QP06CzoKb9!%Cozz=~4 z@r8k3;B4!-(1y_cEM`FlQN4T7O9ql8K6<7?KHpLLK?ZxiW$`iimHWI2&$RB3 zCxrAETwx|RjiM*d5w;$4vABZgxd+vAq`bBD4;1<47nI`4nzZ4@cwPzd{0@8jwV>_} zD$II_W{f&FI2U!d!I&dMEX9i(Js!LonxQzxG)|FV3+foM%P>O>L!ozGp;x^dC0Ph29miG*rAHI87HuW|NHGTxZLD7%VdGF@?2bY7B-+p zj?_B*E?ARyXL#^Pf?6&GXBuxRa$j?2rut5V_$})7uPrC#9%?%OxRaG;Vdog#kc$$6 z$70WoLTL#FaXWM_fQ-}#z=*}qH;f;4n(;sY+PsC9vv|B+K7F~#fjlYepZ8dCkHKh? ztun2HTc9M@r_5Z&L$LdeZyGTo>Nz~66p zNrA}4njriQazw%P){nazq>+jwPe_2l1r#_~;hE_&G$!f(jbfdnlFN3^0W(D@Aq2(3 zuU6Njoz#g!YBUiqGnLuKi6=l3Dht^uYWkT$7Z8Cy2cQO$#W4@&mK1NN0%=C>02Pz5 zf7mZWuBaVu%E6f~n)`%>xlO#LBxf`b1FAlIy$ND=n+;XIi8oWn^M@j_f3JLbck z+8$4y;$2Zznf`&36Pm~g@XCUCJIk0gbNyitZS?||bFjx7{rX1NEke%E&H7$i+dp+T zu*}*CA{@bsDQTbujqrcNaTBvu!++0f#F#N2QKP!v0N=iYf`p?Zzo!ganp(0MDhdqk zXmF<+1J;~gFZ{Q>k-{?wzs(8Y>Z9Ly;7N=QW0NNe`*$@F2v}fS5x8%c<`{O+K1M@Z z5y$#gn*=7odT_LS{TKqm3v!{LIQ_JUmY(5~6$hcna6gvAYF$+vl8Rn8+EH}BYsj!Y zz~_c6l%~8$ z=$L-lOZ3(xfS_Vb+%@gSBwmOC$3zM#9YnTh*=zBI`mt%ZfQ|^`PqL&^=0z0lU1pGo zFt<}NuwDgL7-!KDmww@rOXfI z4?-4`rr=xx?c08x%NC7JO)g;WQ(F0fiSEcnZS9^(6Zi8@vo@jc!<~A@3snE&+Sd1( z%x9=EmY`q2gVLQt?<=_dI^EW13Tr0A{bG6~UeNoyfG<6wk38ZpRdoWy4d{m*0dG7^ zw0?rP7Ax360<9t>vTx6tU@PwvNP(b=2><@z5tnc|>WpaP$4EnCgOgPGh+gf$g`}Eq1TW+o$>O zj1nIq{SsGq96o9lC~5ky`68^f%(otHu)UUo#x(TkH8ADY-=#gp1yGH=q1T&H0eR$$ zvhzgK0>g)z14jijUf}wZXTN}9pzbZ*DOvd>>1vkDJzDuB+ebkpH})(C0u6LZqAfrH z=PJ?#X-Nsnl(WhR{o=yCa6XOxiiO7J)*FGk24!W?wV~Z<9^-+E1cbB$aT`7~Z`B(| z@2JGagLG4QT*Rtxj$!ln<2xjaw-5SHA}beJ2=_;u5#Pu>c8QH=@Ur~%pGO3*Cuv=M z9!AsV7M^-9mJYW(AFu?ivxoW-(H?}lg20(hBfVQ>Oa^Xt@tD7K&q288ehdtkQJJtaaS%o<(olNu5T@M*_lZ$+BOL%07!yOu+YZPM zm4yeVpjQGBz{o94vh3(5JR1K?gcu0n-X8zz1O+QVOazBPe(Z=z`6wo{j!fw`$hq7= zEe_Pl5%3BrfR1@i`qo@z#Pj7k8ZT#-NGuhj3+ZC;b7%f&sN}#H5o~L2J*#IFXt*QS zzvFD%A!nD7ZoehS%&)bBvb{1FzYO#VKAb~Ws#<-o=y#fyDDxK=5-~&5*IBrrT!2rG ztXTBU;Zs->Dps#;(4QW^s_#dIBEu=ktuyEyo!eZ^I6b#U@3X#%g90f6fB8?{t~l;bEu3-m*5cP*~J_hq>Pt62)XKu1xN9;RZO zx_lU^+|VF4kVVG@R8&8{oe8m}6pcyandM&N0P_t=wEhESlE9^Y%k7X-pKsuYrC3rQ zRO9&bJ;aPBS#i&xBY7h%_qTq`(aBizNWi_cOk1df6KcOazxh4fs__>@b(C|UAlK1b zc=Wx>QMEVvdCuzF2m5zKubevd5U~R;xTU_&+6O0IYmalTK3f=qPXlzeXQ9RZBEdg# z6Zf9Po*+J}kh-?x`jXn4eR`pUNKOuFy(CR#sf}K#4=p+p_bo9}P^28(w^MAY^IqVJV1C zgo__-<>gIm+}1)a`*G~KaJUFI(cLGB~_BH(W2S@#&W+g<) zenu!3{iB>i3jxGB=0l>!N47R|;seGI)DTdUJ_#xW{VB4y^r*CYB8xb%M}mP-SwwJA zMvu*XhYq6#=nqT2K2zEG9V&0C)&uumGm#!*A_%2qc8l5o%Hvlr zW!$C*al{%Lk$`I>`2d!jS6(0{j)@iX)x?HbbdB=~d=p&S+T_NaRB ztcAQ%dV_GteX7xg-1GF+Wx0UgeQ$tZ)$Ee+k`lD?zlJhvoz11_9bO_WyBuL7lV79J zDvU<{#ySw|%>8l{(%S;MrM`@HeL(UWNW+m zPCtxKh{_YhsX$&;gCi6S+aW>~h-}>rtS|RE2Hmyk33)cjmzEEz$^CO9kx=hxK^&wBmLd^`YeiK z57tS(O%PQ(yKF)+Wx*QO{e#$kXlq4tL0ChNzqfP)@irIXsUQHS&h0mVzc?__6lud- znHF%AH4i-#m)eEajK_ES#ZDWNd&hzOK1elG6!ePsr(JcCPqc!`Zh|?{ev6;l`xFpr zUm|k>gT@=ME#Q-Ln1B4Ep0i>L$GjcEjDI4=IF4HMd?<~93Gx%U*QBl;p6(a^gYpT6 z_eeE0V-pV~y^3ZvP#Nyu2a%xV6AG$8fh{OP%Cx_RK{1hEx1b$q<7(S3y5A)$9{oPc zqDL)V!`i8gmjB8zsM_c@w>pO; z-sTQxE+C0<7f;Fc9m2J{$Vfp`wTmn{fHTr*@S)>_yt94!IHo(#Pj;qzD)*xF3th^L z&#%k$?dqFFrM0ip%l&=-THf!>HLS(SC;!`RTK>c}liAZD%dTi!glvCicw2#SRrd<) zE%gjjJHL31hZ6j*`(09_xFA&n4J#evv2ek=f5^Lg?~|+fF4x8E6W06`4XEdM z&o-R)TvwwbqCjJgJ%Z6P;+L7Dq%ZiBO;YHWEU>&U&>0ly*~x)5oMr6(SE0D9{HU)s zzg6Gz>O(f|q*FRSwti?&h!lT1KXSjsn85WrP#V|Eq)0>XHtDNkfC$VboDTVX<#CU4 z@f14=Ri1G8=NvZq&sL9+qB%kKgg_TALb=|2D!3m}Z#6+%PBZe0VZ^g;e*7nOvwx4N z2a7nnJ{yC{;U~9MFIVNyUGaYO7kTWjM1DUO{x?476ug`dJ$V?yIOMya*S;qhVZU+@ z3zP9Q6y_sB6^3*<=w)D8&HiKOMn#Oq?E*xEQLRGRa2eN`4nyBQ;ghRkpxcZzqDrnv zP&M>w_x`QQN!-B`-s-U^6kTVocpo^L86(C&j=SFNKk`L*Om?0luVX$2F%y;XNHi5s zM*6v%mD>(4xdq|vwUkF)Nhlv45Lh}GI-j`~NtsLxZ~CcaGn*hmc%mLqYY}sczW;}R ziPX*X*YW&#+fIhNTC2X4hJEEz8%?=+!=nFvKsMHoyh^Kkabx=U&mYg6Hzmd>-njPd zl_FEllq@74x-LwN@05&gzw|Y)JmUK7&W&pL-dim{Utd4_ElhTxD$_3_qIsP%KKV1= z>EbS5C^M$&K2}>*x1NXnsfdB@$`cV5;b(7>U_-?kd@Bn-gV6FZ?e=ceIhra4>UnaAeHpg+ zK=TnJE-jf?O*4a3{2i*-lhhL1Y@TYN1G0L`Dd=pwBQh5kq%Vqgq|+)thM>@yghUBH;F}Gmjo2A zhGK$tv?;Vyqt3RNGU_pc7{(dPnZAC3yT94y$E2hmNIQW6_DWqU1(GJ`3e-r0Z0`+$tJ=B9wS1rH1Tj5D=CK96}c$^`2`TKk3QxQ!FLRe*} zdh2LE$gJdPd9{QcG~fN~CK^DpWsh3(s{u3Xr6>c%6q=SZLM8XC-4yIP zJ;R{S-F%1JekuO4&pRcLZs>+Ip3s`<-^H+nkm*vT^&fQJ!KmQH$}6OZpT+&!_WP*I z=y8JPhUM-;ymYK-HS?QYgB-fv=Q_K!nDbrjT}lkpH1y?^KF>4aA%fdL?suT(dD1UE zI&fGl)6AN52Z5PhLuEDKpGg`c_8)&*>HJfzDB;nMpnV(mDFQyCW4QTYh#p~$$<1Bp zlP(dG0)NsUb>j-dZXnwl5{T{Rpkj^cIcYa}!4mk@7@)mtlA615)b<39y z#DB%xrC92RfCS!TP*aC1BVgVxjDL`QE01rRFtfp=@SgaVzTq|IBpRz4F4D3G4&hbY z@=Eh*jqcj|$tvm95c)oMfT5=ed;Ug{FF8uvs!Wrr$6tSHoP8U}^M8;9mn3W|&Ug>9 z7atri9$%I+dLEz4KVHH-?^*ZH69n22Q^0m!x>r%{S4;ZOz4>{sP<)ILMxVP{{xAh* z8ocu7w+($~Y+MCe(81PF9j@i5#8a9LV@^E^TBau~rH)wx3VP8Hw&vTJ!G8|h$@0{r zd?89-{*iq)k>uV{c_jLSwTyRa)Xhy#=Izt5kNYmTOL#R%xfG6ye}c}ds7B9epZF|i z05X$_Ag}DKJ*RW-$jt=&fg?VHI$VkhaZcpNHY`=O>8mhuS#14rs0?rEx7744<(oE- z`B?cG(p8)t;13!~rF&Z7nkYMzHn`HjhStO0jx__;mjlzOkB=;*tz%Hv=Zh40UrmKa zWfwuqNOE$YOSt9njy13O@)Cv3inK!E>PPg>9sQp5bJ_B6jxFYCPkFJ>LA0v3zPwK>CSj|Io=z(0dJKH`vr! zTgt{;f3$|mKK|G;aCPr#FH!J*AoOJu<~^kEv4O^FfpkSNQ)5qr_W1C!cJrEg@@#(^ znOIuJ;>F5nHc!?PGih4ex!=I|bsqDWF8Q;3Q|*9A)AjMaE)l`p-ja`)3}P_GM8nL1 z_?=!%-r{W<>v!YJ%yR#3R-;pqbD#Pa`v9ov#FN(p(nrF`F;Han2dv@DaKmhp{;77o zdb;uEP&d(f8D-@Dg$txJuC#<{`sxvzZVHT>#{tJE!%5F8V|h3Sa$M)%40ys8v|J2c z-LYp~Lr48|n&Xh03GrC^^w1YKBkt>)Pd5^rr<_3@=K@af3kP3vn9nOc{H(zkzGUd8 z`?-9J9_oYjblV?yY*F2E)IK|778L}q?TpLe-`CoVR|zA^8+d`qh%xr!wb8$eVB#T4 zj?;ju4zclUEEs>Dnr%G`_Aqq)WcZPT1w#)mGjO_%c=JRl5Gd=O4B~STJ{;;ZAe2_0oY1Nv(YCa9~$vxl1x3G(r$ni)t zCD;UIiJ+&Sw;D{tTTu7Na@1=~`=gsP9Rr&A)RmZg7VYz^!_MESuL+x{f#LnSff2QZmvgdTAXA1s4yNBWZQgagH{_T3Z}vz#BR^DE*s#-9UD z>-m3iISYn9f6>jx_&@y(4Shu$1pO!za;fUKC0DEB_{W2jhnF>9_z*wZ}HLKKF`Ycv6H#0 z^3uI0j?wR0RN+z4L|u|qrE7?C z9Mc_p%etN2wSPXR-Y=*gO2OeDHNFh#eDwcxQ z=g{mTIfQ&_g4vge4g^YxdZ^;LwbxmCT*PzAL#FQj%cntiUpOT%BKD`Rif# zp({~$^a5JozaTCxL58PYSKnh!L6(P2UQV;-RIK(liGBN>4Tkym;>Sh)%sdwOevV{_ z4NZ$+PRO@GM7XC_-L+l=0aV=1OMN{eez_iF)vL_6)oLa)?!96}mpm zY>b3GxV5glpP$kf%C)h&e{bibA~7fHt7-A!^GdeGVp~<#i*6+2*5$W)&*<2edx=KW?55<; zG>LL*`?>pI$RO>!B54(3d#+=4p~?te;1I6F%&k zGS$3HcKPnYcVD~R9s4PvzQBJQpxfW*nColXab7vKxEUZNl6ReHome^OEeJRDH6qqS z<1)h#>{!bkHh>@GAro|7YnW^D!-de!uW5)rRs1cEXmN`qkg!DiOS!P*Bp+CO{2=c- z>dHIHKP6i(kaO|vMCG>ex#cB-T-c)rl(zCRYCtP*KttZ%PAd{w@@TjiT6s0BF9yQ~ zjo#Y7(I8gPSlL{FFY%cTMdBOx)8oM?sLM-q9>hj4GFY?P+4*dX{AnqXM~@^&-_re< z`?)DB1>Lq~%I%8*TXG4kx#=cQa7GQTtXN3w1qXe+L%eOL*4D@E`ixX>CiUs_ko0ES zgU}B={!Ry?#%P#b$7b|a6%IPS`Lj{lWMnLFM}Ow4CR(F6&4HnL@M5;7%`Fw4MYsRLXLJ0VN6;RtN3uP!O~(yz-r)M92@N z+fDL7_wNwV-WA$cKC+#*b@}GN&ywfws&F;e>^Q;hl_BA&9((8bC`AmTQ!Dj{U206QH9n zUQBifFO$FF$onApc(HP8W2=WSasCC&>F{k?o#$a}LH`9py7VyiQpcVSn}=nLXO`p| z9_5kB*R%IBNsAC`At+;F=b)uHbehoKJK9hO5fXa`_MJWSN|jFSCdLHY%19RDg@f_5EA7 z1u@GtpNAfvl!^M`eQ2V~V65JRoUcVbGkh<4jEP}v8_cPKLShb6^OO#kob_DEw<~B- zkB4!m(i8S;?;1s(<|j1^MloY0Zz>~TWKHkcPD|1Rzm@HIyd(X2=4<3xwBSM_dO`kG z4xdwL)A^je%)F&T#%6^Vt_ulQ$Y|KncWQz@k%mwJuQ&^;WNiVPs#@j_`5s1aJ{i3#ISlRRr1 zkvmgy?{y*GiQsG>)qWOQ;mEMNKPZ)sFQu#dpRNX?oHx_bj~f@fGYO-hM-iiC>6iC- z-X8V7n+?+OUj*h-K>}Z8iEZIbNRi!Zb%IMRD9Sc|FAN`S;k>xblkLy%5ZV>^u753Y zDR6mUTt_`>iLz}(Px$jEQC{tPbo8hZ2698d^{NO@8N_Xp$4*dRn7k_ZezIyx9z^#W zPH=V7v@I``?;u9_BFHL{gzrNCak>300#>r7#lsdwZag8L>wnX}fwFu$`waEjoP3-w z^!7DHXScVjG-jhUh8LwXLQ6(1uE%N>V~1=Z&s}LCo)~EW_Fq+pTty0(f~{hc z=Hwi8;tHL53D$i-rAvM7FUawMEfgY^?Px$siVk0Javtqw=u#dPpt5;*?ceHjM zX|;EwXHz{)EE411p!;dK^NITWbiTIOQEy5Td-Rwm2;DD5BLk=8#1cm_ zi7;l#rtYqOG{$9yepjUy#nNuWRY)0L_HzF$nJ|>Xd+P#`chH%LAgS=H}f zW|ADNXcZ^$%d*rHGoRy%T$adU#Frz~Ign#>&ggYrZQ_Y%A?Uj`Ecqgk8=uG|>F;Q} zyCERJYB!KvN$l5sb}Wxwy+}_kxFPG;MvOIRM<^ND+G3K8wTX*H=>3(PpC4E<#+Mz> zwn3Wf-6cs*7pF@phT}7Kclr7)n$}bzeX`ON$MWw?%iJUtzbQX!uyiD2N4Na3c>1z(vv@YLgxi#TphHyBCTS4Gq7M-5B#wFxmPOSxe-^#0&zt6mN!{M)q&ZV z+gm7NYKj*S3ubG{e8p?p^PrtdS%2#XHy{!Z;@cHP^^P)onV?#~MGel<>h%A4?vbt0 zgs{Vw3SsmAM7+*+ioNgwnIDD2M3*r>kkZcC15`ftwM@hi=k5G}bgn;+|3{|maamwr#Z+|_cv-`8)#1h+>g-c!DJ{qy-NEQVdU{_4k;*QC9q>|4Ty z@bW_N%cJ+zLd+NY4R(aw+5!Qe{YE714?-5h04}6s6b_u2e%} z`YmHY6Yuua7=>rv=wf$;mATgodE2#TTu>|sOcH!NPqwbHBHOhLK1qK$e1<}pX+Z%O zV(f(1QuQ<`$9rD~+zI{A0s{~@Q%9eKsf_iWiqEO=x*NAtww&}vEoPacE$A0p2{O?x z8a#7bTt6*L6eo@zk1n5I*uKUv+so1SJ@GXJfAn<3Tv@Mfd`=G9H{+KeFR;>q5$^D{ zH(Etk;$|G|evT+YL;HsUJJi76vexdA)xF0C_KqFh4&#u?-uZe|rrrXL1c^KAv8(1= z?3-$*j#eE;JjNB2<(iH49o;+f(cL-n%)aj1w1`rw(~iL;Bs~QLQoi$OY0R4U{os)Y z)RqOk+@sTAW?YznrMX8G*vK$W!8jNfP-R!_5|7^HT z6oG1QE)*+~6}eR(!QQkEsLxLX!jmJYV6k9lJo#!(!U5&hS%;HUOJ}A7#`^F0p%=n@ zQT>Q*r{5#N1=I*IG$7nLabM(Hi9*1RH&&FmQo z?TOT!>0aZvsLuPK-%4@}sjAj0-}#m0b0ppDFpybm@e?i2%MQ4AkfFb;5uA1%gr*&G zn0~8f6aWGTcnz;;qoU;)?w}k50k7Z~nZLP1Z5Y(m+sX_>@Vb~#qM`)2%kkLqIB*56 zvrZ=53m_%9Swh8~r0OtReRLn!B-U*I&LOfMO=x5VFWgmou8Lrv5P3DC0Zu_%*JNG(h z@wmFl7rLIf9;$LYJ(eUMbISGu494-%6s%?oDz#T7kD0YTjA%*9tMP+(CKr1HLqegj z)ly=U$`7)dG+bROJ2431S-xU#eG4($61WfoNVf*3N=TZs)b=?{Zf9z{Mz4SHSSE~k zKSYQlG?LfkCY8SkfBuA07e*k(wg}=Ade&HOHeCk~X1kWS$IbRMm zG{yn+gqI8}4NQ$I_#EJu<*LDg=D^s^BQ>~75S#JAx=})a_L1}#nBATe*S+vxO;Lio z#}=Iv=?LSON~~kb!6|)EsZh2PxA{kWX;Ukn&cwm_jRx@QkTeT@4-iXffmcuq>6+4F z8jO^YG@&T;$TkSbf4X-?LEE!ty`20Z21Lz0FSmh~nKE7!-5kR@|q~i~R19 z*w?sLRc2ZWNLh0bvZHehQSDchnPSZNH5<|ZfidB+Kbn5BJBc^`$-q-)xZLpPW&FZO z0`V;XHWe}krlIY{5G`H`#PYvq{wm-d1NUS|v6s7r^h3B^8`thh-87-^dJXJp%s4w+ z`@+XBUij|2Uf35|K&;z+8$R&%*M98--SgivvGK>qbHvX7F#g2>up_{zM|mA`1dJI^ z&U_YUKM7OgDP0E3h1^x3%d^A32m6b+i zv9v#1r7joNm1I)F?G&wc#7l)@1qR}78*b0=Ut;5P1xN^=rhlCDv9HPF z5UXHbEV>no+n!`U!0Fw|xBTo|{=y%+?ZodPg7>Wh@Ebq)pFQ&M{xk2`)N6iuQ{G&L zf38_R-+_;N{$&Z;#=kfRf~$j#imbp8GlPs-WaO2!DE9$){v7wYh9FM`Jv~Z~95^%( zQ$Fw?aO=E*3-p5KGXI`e8R^W|;#9KNgi3lPpMpo3gX^`TE=7KL9d#VwA+qeXNb;XVF;^G8gkZqJzFxk;2KK)sz1>Ow<1N2NRLk_+lALO;c z$d2*X?A`{xZ2k+BQZH@`sGLoNI2XNcaVGSi=iu}~@=)&sdJ#aakGj0aU6Dv0eVY`R z!|?0;)RANKfD#o`P$g zKXdK7?l#%(lsw0`Xm?7YJHY3|Z~xpkot(m=&ncXW?@AnQPLMFWy>ro%TE8h-5 z_G;q#P_a-<(x@1sI7|zpq!1h3a;0IZDQviszQKzfTjUcZNB@TOftNKw-b`l~6cD zNc+)B(Ve0vbg%mOF+lMBUS8nDQ_JfSRR6K?;U_uf?Bd5jv;2;+r#&cgBdSPpyLqfi zl}VQIt^f)-I{;e3XCJ^mGHkDl9FB!>e_Wa_Iv; zeZH6mEtpGVhI92weh(=bOFb32dk;lbDs={pfRDmT_zHSe(mDlKj{)j?ifXl3hL2S+ z+6XK+{sDA%tv;9bQJvH!+<)9R`ocajUtcYoA0w=l-eZuUpMl4rI+sW6=Cq>@t|j!! z;m7!t7qE&HN|$lBpDsOy7!c70nI>M)XN6Pe$H#kyHmLFI#{lbm+TF)OW}FOAL=89ahh}E{iyeZBxiP zn(DLQ$!ig(hFWZ!EnVA}*T+Bm>=(ZJE57P&Z{y0pZvt1veHFgo(og>J^J|yhq!&Y# zkfS3;KYKhQmJGEA4bmRLN}Oq(R)K3Ox+Mux#|<_8f4B%W$ zaY5h6q6_qauW(#uWd#lExi3%5EYrEm$S-*sjpGGBoUaZn=M!=X zKU*Z^m5g?@7I9sMHmQpYeIDHJpRzwduK-2_8#bZx>E8WollX+Mow$J>^x+NsflYw} z*o~)XhvLL!3=w2*p=scy(!el2yi^k0Jl|cvDtPv$2j26MKYsI6?oG`5$^rNnf9>}^ zdS?3UcdbpQ_V2^+VDxk6e?I!BJfgZwDZZ5Qgu7F|q+KIs~_0GsS>sKI!Fibk0%om6qubX9Oci_94NW@>J&g z`WR#2_>rP4<4QSIj^Cg%jCKzv<38QhE11d(gB=~gK#xcHsIMRY6KV!#kvI_W;|YP= z7Q{0Rf3^#>k+p@UftNx9*$;hBA<`>6~ert>sd-U>5mQ#Hyr?nB@$E%Q=`~ z^ant=VR^ahFqDVhqMiqFOiJV|OIb-E?OO}hNzfT)Q z*Jeu%S;m#o_4D`;kaJ=x%A2#~3ko@Q9^f(T$VWKO*x5WNm=VUCgh&Y6oBlBBGiLR+ z=Qut%d^(kEEPvL)--syu;ENvn_BqOPsfnygi&nG^#F30;^KdfY;WjAhScQH&@DRLKlIpGBvmj2KEkI{g7JUN-!|jk{q48z_ZeG=EHAzyI#=h<*MM1=5phuSx?`d5qh34 zQ?i`LPRX?F#nF%_r<4))z6ek4z5km}kFUKVZVj`n&!|XTAE+Xi{3(}F(r*|z4f$BS z`#zutjx_wFQPSax30obr7gVT11a$th3<^_}ibbiZG_Mj@tUe`bqp{{_#(-%@xpv~udG3F{tvoI9_D=y&7PC@T2_mgo9~Ow2Dw~=y5`yvj`aSsbT3XI! z)kLJIEkMC$;AY+-09rZDYD;7=|m|}R1qdXf%cJM3Z z=hqaA7WwSxhyT>)%A(Hw&g%62N^F^c!2s*v=>p+`69@U!O@>oS^laVEQkE+gUd*U7 zuh8;p3u*mR4?6&)S+Wlu&X7y^qcI=iksBs!%u@;0uPw9c!^Taav$_v0&%`J2g(iU* zqb*pBdy$=w2ZiocEWUs?$HOn=qtbf zdpoE?pxQ}>=c&qp3ol>h)h07*na zR5-ewjhx}QFAXV~hyF@Je3ib zUppQp&j9|(=FYg{?{es4>KJ5#8gi44LwHz{3>K6MaR?rig{zNzLd_@h20G4RaMRyZ z58*h4J}{0L68_Ur_W364GP4(qB{rQXLsW~~>sk;>+cp&k&bWn?Wf#5;I(f3LV>u)^ zCHj$9B3;rhMnNJiXg$wwjq3e_ zJ<;HBdOmvM{FU7|-w(AOM&T1$+n;{>A9-MF|H3zR!_>djl&a?*fb!-)_5paddiY>! z&6(f-k^ytY0sVuFIhZ{KYYxqY=j#u z3$g%PV!_47bE-U-50WrjW&;}sGzZ+mv!|8kNh^8jyqB9i9a#rJ3COF+Gg>8>!RIsb z#MXO>6UBIk{P^{}yrS3l>?;gtR7ax2+q?D^CoZ!N4tQt%`Fh}3&cnL&thWK^lTrGL zpoAt~2oTIrvCph~DW?AEBj+ZpdrAH#Ytz7uXn?%{<7>O$_|xC^Z4cZC-n|IBG-}<8 zRXAGa|6u>!fB4MWwb%06_6Qn{F_1pT@SoB-jsT-{eE@B4Bdx>JP>e_zWo5dYn+XBuoP$3}((qZFnbbk}nsxRx%mMHr>lbd6p?D za+J-v zly7~DK+*x8V49Jve=T0Z5T~dK#O$B^C43t?U{q$ufn{Ym3pfE|oC()mnv%mc(sqEg zJ+-11kk)n->-L0M`bVVGSrHA_FM0U{CM5u$M-P*Qhr|hAcpU<@m<{cuzgTjtGwVk9 zy^fBBbsK?F{^dH{hz^b+POA18pofB_<9H~*9V~EtmiycttFM$%R$DTd;W1SBj><46H^wm^fWrlaXpB7Oe)67 zdjwbp_8(3_Ib=5MCoto8LMnGzI0Q*wnL|4$nfV7}@N$-)G$Id6=Q0ehjFil&jhAEN zz(NutC(Z$K2*!ERLF^siJn)nVnMq`@@ebO7EBLYk;p&z0`ZdRcLxldRzlS9o#vCkt zzu&qPRW<@DurnV+>JRT|1t;$7QzBU*0mJ2>EZ?Ip4xbIUjW7s8;ZJzOee56*rNSjq zRtUr;3jG_p#yR0la8upWz!2ZuDsR^tg-{2_%b%QE-%7Y=ekB=gLEi4+26 z`2jwavP31mMqn|(?%Bf0;{A`@=Z@^h4BXMrGTSB5GWf38H8#QxrC1- zJfqKjdpiI|x;;N^^g`W?l^8s4t|G0&F9Rx12HY|?(Zg|zP>lC26Kw>J(m?cSdXGZY z|01LP-cK6X*8sCI9lv4iQsz)`+SB)i0m% zz{+{O^l!7FJmNHtz-Y!L+?=6?u~asu2bw-OkC3)4=qzSJ&GgJeN#SC`_#>b6T$Uwd zg?7N0hmylNCj{^$D{u}`2V@92f}_t3q;s;6Ig#pg!hl~OhzT~eA{5^}&@z1jP{?3u zr$qXd-L+xPb$I0)H;5=LSkE*d5Ouc>5DkA!_uts<&t_ul9f9pY6j+^tnE`!4hs>NE z#z&Z>7q4`bav~rV53_&47@rbJVcC@hEeL|6nSIP!6jOg?P4zB%(9kQbW?0Sg{ltI( zdiwAJg1r+W$`b>qCFO)GZ2AB{0(sz;b77WD3Kdr2M9J`*D99+Dre`_>$W5nImwr>A zg@MUUQUec#JJdunuN2i$T~wpgFgKBQyCglTl<6f}y$`*V?@P%zwSWS0{oIL{FgRU; zT>)|YtXkmcH3R+NLtnm~9FH&CaUmC_%QLRAsGWZGPkvw9KB4?7eO!*K;Gn;~)@|8OFEH{j_XVdgJ9XVTz!Cl>l|8m%%E6q4yZWsyFi_ugna50oR zFQaunN@xSmh?9K$cpkW5vJ{Vj33|}if1#-o)ro5Hd6pD^agy1)9%-}# zpz@Js@@@kWvdW-Enzo>V9MVy&3cyU78R?}xYPS*k5`9P`02};RlFwC%IrX6oheLmz zHu=%cJf0OsTV*H=M*vbh^KTkTLq}z9bE?7YU;i2G2=~I3!^m4OdnCk+9s3>3IMMJRjpBhMyl>kvwdPciqr^;RU?H z0=~}C7kSPH&vXd?JP)hqLVFdNLbgSoi4T1q&r=sUaG-y; zv+I-fq82*(dRFwV3sR;p&UL0pH_9b$uSpwWkl zC3y=n_dt=~*IR>;UI6~Y*L>Ai{QV#Qxu3gIft{Enz2!uPS6%wmH=P>qKc(^fE(r9# zIQskS#rWTL;kYX5(DO2DEv{ zzhRD$-+03Hz`y{64mb`QY+N1HK=S#W2{<$vN?nphW*-^X^*ln(nuc$Epi{}4PgB(8 z*Gn^&Qr{br^Lj*EdDFl>qyZiY-0s$%dg!@JZ@P!Xz9CUhtOKyo?fucUId6_RZqO+E zq6LfrN6bdUsNQa7YD&As#NvesT`>OSE8EpfIcR=hiOYf8&B3|xf#o~`Gw+~-aZr*Y zUG)Syb_`wUg&g4k$oPl#5gYySIG|p&i;+$P9OT0|AwU}Fnr-IU;Io`zXC+`WZQ(%@ z`!#0vb+DcygEF4$XA>egyhw=SsE_>2^jcxyF~I#T0mz9><}KvcyqD5xgd<)mJmeJG z>FgXO=OO65u9Np+a4o-S;3R8cBW)5mYOkN(q({X5b!pz$%BqoLQ?5aPng>3F}w}(=%onIgBT^D3;T8UIR)< z8$QdK{3EWx4og$l8E@4yQR3l{sS?wWVX3Si(> zj-%2$9q`gpX!3(@UshxY8IxlbtNIRz4;>u@^MRMrb)F0P0T$W-a8RF;PCAZ(se*N( zJmW6si>B*dwOIw?WR|Cn08N_9uz3zFL0mKIw#1N4d6o%IHIUP8FJ^#x)_tWuSLk_J zp2P5aofR52++k^dkV*^!*A3(A!Lp%?6|`!3YPxm)ol0k0)^W&m;}US?)O3zZfwzLL z@9lr-Pk!fjK6zZ6dkOADIsh-f@*7`#dOUeK6SmtC;##Q($r!*GzkENoTu6uW0zMB^ zp6SCeZny*k?Vz|A>z_;<$60o$v>nx zZ1Cu92|82zgfmiN>oesLokG%ZWl7&3?I9g(dYC+Q44$hcw}Sii5aM43)HH<*h`9O} z=b<-}k_+SSy#kcdhmu(!#d+qL!oi>3F^JA{5MV_PIDV2qW$5^Zg5hrvr|`*v@rL*{ z;HH85Km&XWWP3Dv`9sfM{Nj^XT}H3FT@hHaLD+nTGgG^o(l0v_F zpp4CPTm5PR_03tcC=Qs3IOH5irLLlm2Z~o_0Nby4&J#7m)>JJ%9g1Jg!g4N9^pC$T|K++%?M;+=0fb?TW5Z)BqD$u0(Q zc1p&DC3r1W`(-~E8x6J?HCu6(vZV^|uve42PczFTA$&#s8OG4d){K^AM?A)XffOhP zWQ-Va<_!fo5b*K~b7WDOQ44O)3vU>;Et?_>9ENlaIs&Bb&qyiJ z0!&aGX@CVF8PL0EtH4zx&pY^|u3kk!r<&PA1es^jr$1&SQK6Of@h)apLA5q9F=MNT z^#x}I->auhkwpZA&k07E5bdfo0@2vYA8qJmKH6)*d;d>8wAo#H?wTM2o(kb~u$q9@ z_#l!U9CWU3Gv$c^Rt@}EsaY5mexu?T(J%VVc*sLA?_r-n8uKXg@4+A8`c_RLF#t$k z^>&R=Z4?VOdjT1#2hx6D0*NDn;gg@@(BBaVOGSpS<*CZ8iqbAs_JJh&wxfd1-N_Gt zsE^S8^zGw^VMUC703I}uSnVa@vfAVl@;uH~{8n{I93zQVcGo_C1^Pl(Tr4FEtWq}# zQMX}_)gRefkeR{q)re^`1|j zI0xXt=YHV})<^T-F)aVnTd5{JkT^#ob!ZzeWjUr?Q0}S3I#D`|vS1v-=qz^f=)Tdx zL>s*15t+Jfp#uS&j^OXErOeECKSz!Li@5w!mZV${k0|iv$odd-z9^kCjvZpwUaC-N zfovI50F5Y?RX-p}8E$)8WtmQl`3G{q(hN;}lUBhOVJ*L6zA29Z_n z*{)NfC!ln!(yEtwg-(JWMfQ7=Ffl5$g;l&1Jc}+m%%y7{MbCwbCGaCp?cJ+xY!qZ= z!J8Vze{xj>HJkQq06B^}yjf6b)=m8BR~0eqjy;glMJ2PHn^NDvFGP-%s1-I1G!5K6 z8i+xO_1jarIk&sB*BpSmr*T$GqUD;eU?vv^NHA){)k812#R2frJoGU57%-NsIRf7i zyXTb%Ls{rktW4)a%SI8Pa2$-U{CG+L`YOUmqyZ~&N@$~DsH1<(6AhQCt6pHJ#ptyz(r`nc>k(kZGku9~x~nBR zz3X|duk*QX&;YlDtRuR0J)KaYfLx|}*x{30xG~ZGYNv{4(h@)zA<<%R!AUrFKqkOf zp%mE;&;q9}Oa2D{OGbeQXW=*u{;2$AIcu6n;oYvX)K!V=!MxYwvW!RYc6%LB?jqMw zKki7^1HQM)>*gcf!f`ZEw5EQ>D!}Ic&a40T@Be;%KKA5<6LkyViT$7Zof?#1J>364 zx}j_+e7(yMc^mpL%V(k$h6K-4hQY=RdQ8<;T|$gT%z9;+Fi>Tb3a3KwWXsw?DXl$N+;-S1W+zFFqb^;7l>SAR`PvFY%@i^XWI*}37 z+@Fu`h%9fyjrDwL8F+BBYKzX&@biu)$_S}0qm!WoKEui&BySeftAca&A>brgnH|#v zhoLXh9H6{HV2O%UL__f++8sXgtqv)~4h(gBk>p~9A^L<{`Ht5=-q}!;OC@i_!Mek3 za<{O{FJ~eT*Sfal-YuftRdUz?tS%fo|JRd{@x<2}W2gqK(fHL*eBu+oQ)s_@J=A-p z{6skb=l9XPKr`at@093Ruy%gW$FDQU^n0#0zdkGYq|n z8_Fq(9d(OYx-G5i4?gk-4~&2AhBmzE=9;u%go7ANig}R`&Jct3R2d^?^P_bp_tFiB zqN}=%gs!jVx=uSADp5H+;Ki=mxt7q;DtU9>txgq%&WamPKx|Cl6M0?|KyK=T6Xn@C zEfKnatFZKlpy(F=EYwRq5gI;m02yVZ2!S1`!3}_39}u*_@ifAm_XZ#jsG;BhEz5;* zQvVL^=UmfM3Kn(IAWDtAlwJa=#9RVgJOynPY@L_sgw6Ae7bwWldNR)DgL5mJoplYN#O>wDr@^`U*yvK+3Re3IF zdvkFi2_N%%i6teMVNHL`#)L$jaRR1e0cj5bOZrpglN#$2ewa}1 zsz8FrYpB;00}qT&$pV&gI~IcX2bUFUoJ$95tW%<1o$_tYNIQU}YXf{7`;f&%PkE8;!FdV;aAO z%vC2;K;+C{$y&}H-1MWh$?NqBUN%w>JS4orRZdd}Q7`&Wt+}j^dNz(`hLR+*^JlP< zX_=(o;`Fi<#-zdfK1>_nfL&CNaFVV-7@jz|to0vzVUmAn*HnDj13<`r9B0X<6eSR+ zwXD*g8IVt+h9fV6uiugmT?x*h{1kene25Ooci#&r(<%r+&q}{^yHs=!p){~sky_|$ zRkg@pXy>ZTWJ(*-ZnPe1b^9ql&{W-(~RAs~xECdLiQDx+)If>UO_3l~el_0?LahtZ%Qhb4O1a!*s%tpxL}pno?_lZ4%vot_3A(#>p=NV*aW8*yKf87pVbo0DK9S7$jzt%lVu~2m=`9d8tFss{-Jb6^Des)!1Sd zc^78?jFTPPOB zS_P0An1PSwEsXS1oDFTt!vTQz`i>r(Q+jV}+U;(Sx+|xl%ZkUS+t9MZz67wtLc53- zs{|X@lqVWUf=nZA?3l}eARvR~7q2kdpVoJPxFp zOeI|8UjH|FWqf^%L5}2I;~YjO^K1EQJf-`(DDA$HI7@IZ+G!4B(NsjnHb^DAyv4Uiwz>=0bM8sYhP#2{?(C0iGRsW~Ij#(BfVHg(VFbE!O z2jfK&4!Wb2*^wx}b$^S#9kRyn0%$wASq(^;)^mkVn{4gwKmM+Fy=(pV{l4GF>&Nc- zaAF*QbHDPluac>G17$cwo@7dx~SOwIr?z((#3{n{v-FXRecQHTLBjJD;1s#z&7*AmIA900!6 zKJy5qp`$nc)G6peQ*&_}e6E04?hL~WZw52p$M^jL)!!ck(ybAFkY``j-q6ncO?d<3 zPIt`I784o%Yp2KEMBk9-&1yJBr*waRYhO--;CHk*AgY=$$>2|?N(%{9ZmVlz@N*`` zBYG@FSODauq6l5`gc-W0!jw?HI#{17DijQ4<5Okik}+-UfxM72ZeIGzvmC0|&?>8W z_PGM=4Hw3tFE~8mHc?x6(n-c>0XvBN`a23&@v9^Nngp3 zQ}hzf+Bt+?h*g*Zqona5RN*B36cd=5Z{{B-?rHUXD6oR>F(@FHuc^3(!V4_o#xS62 zs3IN)6pPPv%q;=osjp0Zi$K={brev%Cq(^QaOi62(nT+@GprH{y6SWu>8MM>={m}S zC|>!Sw0!*+h}*U5lC)PbPAONF-Adj9&!U%`rHA^UXEj2rqURTZjmNLJ_D}!mt5ov- zo8@sg;hh)<;B0sCiFvp6$PAZS4uEIxZarF%E0Ix?GNpYb(hRzkn05NRYUzXn!~s0; zfg!E9;4lMZ9C7C+Rz9;?jOd*WDN`*UG9c7Yx7hX1z-5V7ib#p7oQ}hS z80O&P&4*k~w^NA1*KLIkpzWVQrhF6;^$)pA0@rc^ct}|26E|58$fr&YVY84e|4QLT zm@zm5e{>&SU~)T8V2y?LMnv^S(s`-Z)sW+g!N4Fhlbw!ofmliWy&P$-ohJN{qPJF?+Jg9jr1xMGm$#RuC0lkWD ze;JccsLLYf_WUBJ4v!YAdAJh`fInOGUKWNFa3WTfOvj{T1X+|N4+p@gCa)g@kjKvb zNw=rl1)n~%*?sElsqW&L)7=Z(+ud(I_(1o zzg!0Y$;-RlXI{A6edebxc8@==*YWN!(6etqJlMj44C~rgpw)x59Y$1@>%alCqo8uW zA#lBbxOp4}i(xe!w;lx8+ubD=MPkq?^!ue_> z#dDsDp)1IeyYH&2&b9hA-JW*)+e&ZJj$3r+Zf9+$8*9AU+SNHut&X|BwGr1Ft9b0S z#n+%03RCo@F?G0rP??!oi()Tecq=dr6>J_a2x)eFGOdZ6d+2?<3#1s<#vc`!i{OurO|0TT_alvoDI!ZPD7gO_M-@Sz*Q z?QN-=n$bguWlOywTXonF?+q_&?n*hIIDe}9g;zb$z4yU~bW7k|_pwuFy7!(v(>=NW zq3#W9ztw%t*~{HzYto%w-_gZ%wVO;gx~tb7>3;Rf1KoSBp6*`rsqOCbJ~8S(_oI8= zI>w&C^VF={+L?CSmnXti9ZyNVOY0hAbxg9JB z&?e}>Z3IqWPNXzdF&2-|pB1QcGYq}>Gs5HxE*u65t3Zc)e{LFuUtKmmf+$`Jj6qSE9&lsB!FGuk>j3M|?CA-(WtU4abusJj!@ zPZ(nQrX?(~P^4SWf|GECL(;Sb6Zk`+vZCvYoWHXWtikXe0m^gPdcY#p11@H};X*bS z_~J)C^|8{Z(2P1p+q3`0^jz=2CCTk7^b)UwwuXepxd`aC6=TUH>*9dvHj znLM|fi(jN4B;tUVqi8?Lx+dA;Px-V#3BLYHdbxi9pP^1y=vmyy%QUi}%<1AZ9e-VX zv||e7ekzve`P6*+XyV@M@riK&_SPPHeK+R|Zgl$S2JRSIfAa7%F(Wl=wFVGMGv;u)6;c&eXw?+czx)pMkQB?biQ z&fpRB+0^Gp@dd}=HrFV!r)BwxjQvFWE3R#hx=Z?K&V{p^-9^P8Kl4!cTj!tbK6z%V zyL5W9ySzE;e*4sZH#+-V_n$xZe|BH_*spi5Kl@wVRJQ@1nLp88*?G9TIN$D`Kc(IN z>wDejXfwe4xyQTT{Pa`Z|9#=n?rVO1qkGeD?RMuc?04s0n9CX2=r%4t)NN_S;KIYZ z-P)E6lm^F<_7be^N=6Rxr1{t@5aWB)JS=I^o3pU21*S5<%3KVo>Wmjl51v(UI23#| z2%}C~YP+F87w$zbZ&^iF@bwN zf{z(JIe9%*O0Zs(uYq8HjKd8BcQ&<$;5f$n+PkvXO*huNi8i|KJ~Zm~9@*;lA6V}$ zp59g;-t3;$HT3Ip2A(~2R?hD$yAOBU-A6@lTi4yoq^HNOPP>OMTqB^Yynxmd`td{sc@W83=|9j+2_x^`Ycb_;j?yhR1 zlYHdv?Br~>b55VveRQvT-9u-(uX?=eF6=zi{mtp0>wfJMpVxh8^6GAH?@TwD=)D13 z7rTwKAM73)f4FB}#7V?9x@ zrKJfr7tZ94d(gJ-^|P+W23SJX7&x*ABOOU3>n9t+CwTWLjrP995X6HB?SXO?ygE=; z71sRT0QlWl=w~L-EOh($#z-9tx}G7?A8uIUO2(KYv)rxraR0kOYILR2JPf+EY{#n1 zF@byul?#>Sbox_V0Qz-(bWWZeS~&D4gj}as9BlR{LU3U%l47 z=J`wAXFhYe`}}7wcaL9wK`U`ry5}Z4-5ITPo!-+YYIi!V2uQy;!Rjom7|wJ}A+}Y= zxe{CotRfV3x(@B<<~5)eIx#N;Ix!A_o*#cSHvj2j@YG4&-B&j;U7yP$90Omiq@71c z04A2U!SVIZz^;FG%zFl+R>zX){dX#7Rl%c!%i@)^P+`_%K!7##Q(81)sZw&WyhN6a zFSN=Cf&d2s10Echk;le8@FE?TGKM^4b~Q~B-l~i04>mL?Qzipu5~X&~@iGyR}DV-3RwCb#HtAzw2JOfLt2z##+LpVjG&WZ(q{LwAt-V zUei4z0iKyW+jV;%>^^JvG?9)GI)yk{_C&2yN-^K#g*Mzc572`Ti{uPpB zLnQZaH*8&Y{WN@fe0q>*x%W$GSG5S>VS}`ceB=l14Q0)FVr8TmznspAR=VcfYu)}M z>)p=FHoF}e{tFLx-KWl6)!rAqE?Y-;_S4<_w;%6*eSN!oR4*(09n(*BFR0Hyx6L)7 zYiCWK!RV~kmv!wtBqw{k-u)Zt_}_f`a`&!hp6#A`?vvf?UwEN=&Be>zgV!$Ume|$q zlvWGQU!KV!ki#I~cT;_vKF^Z>x~>iSIF1;%iMZXhN>#vcTrbq^SLGp^yCt{5dwp|H>%HC_!@_stq+Azk{>kVoI5A4p$x;u4aU>Ca~E7CyWU$MxSLFwYq#Bx37(9ZHwJ6-URib0?^bAm%g`yjf&m>n=@m}rp3_%qvQ z=D(w5%!^x^!E9+G-|6$+htHkTX1?w2Up=_hee~R!Zs&oAWaJ<0x^o(^v?|c)p5+7k zSG)6CX~0|4>_;<} zZeIhW9tZ5iryGB+mPJ3iTVMNV_k?CV&!67ye(}jO-LE|G`tJ3gx!e8G-?-Sl?xPw6 zFI?>&`t*(-JB$HNlOS!Pa%D7LE0*)JrX_e@&gfns7P^tDG`9`JUOK3?I%w|(Qu`JQ8kX@#=poCi_TzBt&Z-M$)4D$y#- znH+3~z(Lbx1%EL0?TC#lNV;}84_dW0L{(tt~1I*Iv zyzl$pnS19>@4al9?bw1{v_%I5fP_eiMTMf(EX%SIl{mH)+j5-5Px3r|&p-XN7Pj35$KR&-%94tU^XwylZnKhl zED@P(NV{g6a++yM zX{C7qHNd#4YKBx>oJJ)T)0>rtHJ5%^Ynjbjfk5r8$fz}6t>H|+aw~|iObGK|86;z- z%Ck@csaami`lw^Awbq6YPZgW=ZgZ>N?VydtCDkv_=K1>-f4 zd^GR9U?S0ukIrMzfqW}tp3u%aQypk!V{Et9TAJ>#T6kL*W`s(z8^Ss zvPCeFJSI3w1d8)kxL&`6S2_>>!_T_;P$&7_P9w2UCr9VuR_RYv=vGrA$QbG>-E`Rm z5qBJ=KkxGNQmBI(@&L4|4E+V-pT~8t0L05OBc>awHC5AwsJ}`_I~#Sjt4&iaDDoi$ zl&nVOnbGD8XSL_}aou(7qy{hEfcwBmkiO66mV%u?@s4e3JamWF?%1OKifL^-`W1C7 zBCgC&Xa->R!lpx-tm**a(-2D#h!vnX1VJ@`F3AVdmSUn&#Y_V*Z4>XDP*3ZK7F(zF zO6RhM+UoUad!_EWwy0j1sJdl~Ju$$-OCeQVfS!Pe%fwW@SOf3|Sc6RLHDE{{hj2+Q z(;<w}!@2VtGX;dlxsfdYJ><6N z54KsJ8m_~6eIC7%i&FCaZ%NT;S=r&zktni^nS3jH?qcE8?ePUE5V%}hAo5KJ_*Rk7 z-jF#Ih~|ddSFR&GV}6*^4r(NSfxj)ZrF0s7)p{=)Sw>w=t4kA`8lo~T&RMzs(6;cB z@Hnce07*c$zw~fpEc$X5A1k05Ol(w8_lD1p`Zd(x&nmxRW69{;D?OwzaO5pFh(U6D zUDo?oWZ~T7tkJk9so!NqI*7lBs>^oZnU;c9TasGrh(Rzz^ma1$`Bh=vUt4irlZiS# zll!PDR$H}_ZBSiumzEIicj&SPD@Ro;7|F~#%#C>^R=TvRE>vxOnpZQPF*(iV=d`?x zv>{&$(Gb@Z61XDvH|V=S?k1dTwOn7RS1YS&XF?BZ3W7eR)`f%`@Hx!#eW}Vd)j(9l zC{wT{CZFy-NBByN61^t4_Vj;G3RCmFUFhr5z4tyU7Y}p0eBtaNBk$uFp7Fl2)W-6- z(4YJ4C=V+g9}CeZ#+j&pC2<2U$UC~oAM|YxbOCq z-c`!vJa%bvG_pv-;!R0a5B96Srdf4-)bjDS)%4m++FZ*s!Bwp`9?5z4B|qTD9|1QWq+{E=8ZM+od)WvknPv#N-aRYUQO*2 zxK`!#73>Ypb#$v^rds>QN7X%x2sD)@h5*C^u2yj-L!ohz@5>^5O}QoL%i!b;Obnh z5oi$0db0AooC4DFb0=3&o+zbBO(|Z)vkh$XxWD4ygocHoyEDr zCvmiXx?IdpEV=S){EZRO!})tsv^IH4|CH?nLKc5`)Cf>w z-j02r(rf#)D>kj!h5;>CkS~?gOtw~+)A#Fj=4cbfvh5X1>aV}49TnGf|JWHNj{FYJ zOnBEoVXUClOgFc|$R~tJj3yiEb$)xVPW7~DsiIN&Y!mIRf!M9qbNwy4+|;a(jSTC7 zTw1f(CamBakX$jb3t`T;*;UqqoJ9I%TBeeYHtceDm&d^Kqx=gGHuj-2`giLPU2OPU zu4nz0-{JA+hDXQceO;<1%J$yt=x!)eIt^vY=h4wM{`o14FWKC#Fy^Hq<@>&j2u-)n zbDD$m`d>IY$F1ah*bQx1wYQ$VAJ1+Z0sy@X3pD~Xp5%s|VHg>)hz#(xM>NV4ka#rG z5hn(uHeiX$MP)r@Zy|Qyb28d4Pd@5%9U+yErOUt2p;S$vypVrl?He3bw(9JZ)qR!9-$SsXE?a^5Y)Jt+g z;Gp0~Rsk2YRvZA0afx>g0 zCbB5O&z}~K^m*7U$=9V6kf!sw5&rQqt7XW_xrxc>K>%3m`3$PcDXPpwqVb!@KOCut z5Lte0RBfoxWn3fkp6f8he2cO^cd(m4*t!t+3aC%YoLfNT{!SAGM%2zjP2Ms+EFhQn zt?+)IMJl*-ueJTmn1_q#RD04M~0 z_?w;Le1E*KsszH`ih2x9Tk~3L$>aK(QLa9tS)@<1odvCROeot0x8As<3#hcFVp}zX z5o|x`ce;LB?_|D53(L6tc~C?EIa~hQD+C|@nY9E2K|vSR@_L@@-|Jk_?t3ok?pK~w z;y-;x#j&f9#aR6Fw?>U>!p?brU9UdU)2P`Ut-3JKp!3}=T5fCCba%I|boS}@w>9ZX z{SEC{7}aLnXE!0zOIKr%i)5g}gf9~@<8i~@Ac=&`|18n+SBLY!jkpKCo4#EScqDI^ zblVUBh`NDG05gDl8N7tGY{bsg4vXBtE+Bd)JWwIrH}dhhp#=FN!XScUT$Er}v^=6g zFW2Q~F6*D4M=5ZM>zCz)Ret*3BXv2i>oAmcJHvIkJ$~kUe&*zJeg-C?z0qCY5C8ZU z8}LPhj1hj6Edb2WMxOE9#6T8~Cx>71LK1hS+9Zg-PN%!O^k&Z%jbhawC;!2@toB}b zTbr+4RQ1WXRek+BKyg{C$yK!?dW>TWQ=5+?rc7%&M^KZgEN)5lT5Ijq^+S6!d1xP; zf4{CZwrbDfw7S=_dbMejhSjO&>N(AO$DlMH?C*2blf3nxAY_nUu^!_>{0S0x5ybFB z$XD0!Jpk)Q8{j6jf*ry6H4u1mn|jf`)~ptF6obK;no3=5>Qc+vygF8Hs(Eonwab9e zm1;G@$yE_YAc^Z#4v9t{nj??eAZ$8BQ=E)2ZAI;2xRu<-@|~!!eHDHl5+iz!P(&1_ z?LIe<_h*IYrSbB)k!?Vpi~}zVyoB?UHgYAVN`pFyq7lM!4!_ZSYJ|+ocYNDV!V7u1 ztMkt)fIt9HN5F`DLwE;mD7}($w8uX;k$<5*RK{mSMM6ca8`>PH0C)!R6{Jo)3)@7y zez%LG<4Oc!!FtVZOQ}z&H?+ssQG-MIvUJ6@SRLg@M$qL{p6ctjHnzW9Q8{zz!^&mK zw|OOrfi(q3Md5j94A*WC&z!|OP})|ch?h#NA+02FD=M(^&sMJ~jr2X%TBUsNgfcxd zn(t3)6t4V6Wus0d8`O^N%x3iYV`|qcvF}xGd6PQ?DHffVh7cw4pDNH-#kwGFp)8Wq z+~?UEE(7rBi_Gh{Fm^rh@jKM~z$dir)#r5Q+s9RXenj&Ni^{M*YL+glV=1NHi&7u9 z0Z%a&t1U?Dwsh*~!8>&3sYmrk2ezqWvQ0mH8DqlitlAPQs0COfNTTA{H|1FeY1A85 zL3+HRcps&iP#Pl|^exGAhyQfnt+Dx*Oz+zp(YJXO{CO!BZKm?)WqV&dzikKryXJXS zN5I4o$sAJzOu~E(E1r9S5-&jsv%vwmI)CZj`mlYwiQtLiRY1`I-6=rb<?+va$f2So6rC|!aLk1+UYtSfp5J&w}Ud_o2bi6*ciuk1?&D$ zA0kWV32n6hJUa43BE;rjuCCWeTazv})$1yv^tU_PHG$QCQ!cK%CYQAD?bp=x%GcC3 zbtCdSPBvlrTn7iSqLuuD*09pgrslK?QPAJCQx*NYG!76Ny7#c&?AxwmH9c^JK+Qz8 z&ecxh{xqd!h>aC&3uX(%(paNmh%2#n0wAIar~=u;IaL;Mamp>~5J>z^-jRSC8%M7{ zQb%AQq6lVc5skK{G#yLo!tx=l!u8**+^zok7VH;NL>{zD)D|qHyL89gr23FxG{x8W zcH#*bc|sWluLo2D1&9En0K@4(F>weTSRPPc(&5p#`Jo9Y5Td)}BpbVH3 zB<8s9rQrx=jLbbQ;l7a^k3$_Co$s5{`gr-S5t8SVvl&H~#(6^_&$*yA0c!_iFPEpJ z@f$@s@hmgpz-bAymyv1MpviBcfUVwx<-6Y%UP!&6BA$7vgsOPI&$(}884xxYKd3aw z;CW`_N~=`p9?rSx#sz|g=f3Bk=Qc;zwc%4XT$EnSosiK-Cs)TGKXnT2j86GB-zZ&d zJorUk1koF5E9W5}Bk9QyUPY}C2Qh;AB&ss1vXN_Z2YU1gyi>*yhRrD1wW@SSQOo_) z8f)FD7itD|gt?uIH>fe)q|5WYI#RKf23nfMq;S}{Y=$3o*~l*q(#JwyMd~z_R0YM@ z#>)#xxmW_4qf^Ea+-H|H*_2TFp+~jr_)Tr4$Vyv-#zEQ*v$INNkuFrAdduM!OZe@Y zkvR>ntf^%JL%<8uy7rMLb@AXXed<7;?z}mshet=W?`D;HX2`RQVJ#}TbciBIlux@3 z`ujcq87EHc-197tlJfwM_`|*M%=b&@0V1sziVAG}>oVoM24(Bz zeX@SBvBaNo?z#-MoD94TsGYbMAZ}D&)YfuyjR2hdmvhSaxxWd}8xWP77ym<_sh zT$L)Sw8BO_^R)?d{aE_f)$1D=_`SSkD*;Qol_ug?Gm*x=XXug!-g-ezr{7h_^qA^U zKvodYXEhBnUQEG7VCf$;j4J_dt?^!?@oxN=Zy+MQdj1`)z_oNG`-y_qgHLp?@~thJ zt!)DN(pW@~!Nl5JGJ6CBmq55EVIEaPT%RkXF(w3&7XgGscdW{%lu85e)@JmP)^|_= z%%M)G)rEyB)wUheQ^{*eA(o$6+^5;ay;@D}R0_$&)tUxPB^$LTIi|tt3FYw<$ynS8 zbn~u-NmK&y!15iQqJ#hdKmbWZK~%cmSlC!khh&5m5M^{3>AxeSV|$6Pn;S^nwpg~S zFah8IN?F_+Be6K&g@nO%c$AC;{quPQVT~|1&X({bdKrItgqp&0i~CjsaKrdG3PEeh zP5{yC71%J*0Lpos0`PWGenzy8dJroTY0-K7;&S5D_uL-=(fgIIg#Q*<=Qt1T>>B<>F*G(UHfEAWxGO0F`zk8hz3j$TKuQrn`wbo)#|tT(*eF2Bwd zg%Z(chPIc>yDjCsp}k%^zCpW6529h({Dow(ZMM%yR$HS%@dm9_X-n^#4k*^X?<5|tF zT~+fe1Z`q~m=_gFwKc0F)dsyhkHP-}?u5%KtfbYHp4L+%tGapohFc&7^`ChsI$nw5QW}Md3`XpJu^WBs8 z^uTRJ02q85P&k?6@gy#b!#NB0{lr;(Kf;`zFb1A}9-W80zAXLL=aH{N@*Q9JjRx{1 zfW)m0plQ8)C=&9QYjRn?-@kAcWhs^R(n0%Gitr`l5(k( z(zq&B+PV!<;!1uAQ@QXS=HdldHq;@h0eFE>Medli3RnT zqsev<2cQo_F@PI!{0XRyWB{>00ez^PP5=lJ%t!1Jh@4-kLZ`+dE5_qMNOlseubkFj zE4+;HU_$L{lbV_x)K9dX)wXm&zgs_uQPgp5Z9J>Sm3>OC9?<#K9a>)8p{1&JokX3m zfIoJk0ASCrsG6AS$!wnBDS4t6(r0vR#AW-O*Lxbci$gz_&H=neCPt8sGHCHV)hJbC zwTrVu0HA9%8ye;SM3)WRa{Ro1L?uK;K(gTOFnU~@DUDoFUBcr?{T@S}t>MUBKvbJV zr4{E{ihfTMY=&EEUN!;XA&&U^AeyJ)g|JaYXae<^o2O?XAQIG3g^Q%yZXz+L1{|~q z$x4oJa?yNZ3vF|)TqQu>SrAy(??bU4vk{{uY&MhwQCdBONnDs~jcP?T+7n_ zs6DisdJ?v|;9dsNGlshKL-Y+^ZovSJZ*Vt9U+=e<`k8s`KDNDvakE=qCTjC=y#Yzl z+ETB%J3@ZDJZ^ZAu=Jla3T)Ur{cyZ4;S&HGx;uvsL!eJ+vC$)sHZs?j&P3I}a z>WSaqn9~h7^;4Y<8F#X&9b$JbhlKD`c3 zNAlEoiaAQ$ifok{Fb3R(^r}9ws(GSb&iBN169Tt+h7ek-9jFlcRoJpgcnI#$zq`ct3L&a)Tv;wK-| z$o?(*2J3wpX+;)WkUJ(I&WJYHKxkpBJNWuAm*R|Z1#_YZagZk;!PP?lg^nu!@d9xt z|0PX9x>IVK=gfNAaJqi)FL3hSV(-2F3smu!I?HWC0GKN>;E6Z|105hG$pB@YV_KgO z8;8-enMJ^2a_+L8Gm5e8M_vRhX6iOjF$8w>xQMXewkb{kRS^!-XOp7?Gff=VoYc@yGDAc8Ry zNGb-!v3Sh5*S~w&E2&o!?iko2b z)JZI*U(sS_O5^pLw6$eP1FhG!X<=5sy7~+v%^B6BOTAjTq_eeeYI|~vzFP4Eny-2q z<_e=mR2vSNT2HW|wiRYYVhyCV!tQJ!1IFGS0~KHz_gS9Mt+5PV1JET#83iz09%IQY zUK6I-EH(D|N+SkUwT!ii0!CnsevUzqR5KThv`h>Hn+(Rg2*S<43a|0LR70hfz#G=R zCdr#|m}Q8ZR6RQ-;(|HHd|7I&Py@tJFS^^*)SS{?jhfiff)pgB&Kqgfr*djuNU41> zs}{JPW zF^wuvt03K|A)r$$QRW&a@upb82oAk-3y2|!F_|}EoW&q+x*?(ZwobTDjN;a?(?GRS z#dz0xu7J4hN@{Mrh7Ft=vA3uuUlAj;N(|oOh`?j~ysDU)q1{E+s@?7iNQ4mF&@bNT z3HMbSq#>gAnsUEq>1PvF)!Z|2;P-gVBO1?hv!mc0!uKdAuOn~?Pw z6&MdY`gvQa1YCd1rt50zJ&wc+jvd6WB>e?rZi&;4rtNyZ=Ahoney@rv{WKJg9(93| z%W5xJN1xPMevnRP4Q3!TYbI6OFr{wnRc=i8Xqxp@$=Zph7qp0LY$J%en+Sh(r~>Mm zH=`okuGZCY4K7{Kmb0&GZWcS2wnm*iaEJQttXBTWN%cIxthVt4F|9H<{Y|MNh5rq9 z1hHZRpPVER5azkihCLYPvG6mp*Y=mmYsy?+o;7sHGYS-%UL}Jk7jbRF=I4 zk{HWYL6{^EX;oM~fc--T5m^<)O$5_euJmPKu1c_pq4;xCSyUd%8Tc_WQQA;=!|8h~ zESG=p{Xgp($jsyDJ>OKG_it?T#`EZbVLEwAz}V-d)9|`7Og7Q(-|dpZQoTJs#xu#k zXmrX0R+XC6|1h6o{*N3vAb_ro*aFXwTMT>F*}Ja^p&Jh$p`z0UAwCBN7CZlzmq z;9jYCRJYr~e=ef;`JMiSmfWhKbnUwq*JcqrJDyI3*&}9Y2IRjA;&K?@N%Z_H7#_`4 zHS2{PTlDH3gCH>s?Iv?-99`1=r(f4yZ+%(u3rB%p3AJjsnt}MUaO%_O;n&~-t56^G zWct*Gzy2&-Pa}HGDa7pA#!e;YlDfQ@fye+U!$}p1X4Zfo^UtI%V~{t78&re(Dm!$~ z;+uM-drY6%ewS``Zlaxh7a-VC0rMcDVg*Rw22!^Ccf>&4`m{QO4M3xQDgTc^5H)xv zSR@Kk!)$76x>Y~2u&Rg9aW+q+wSW5w9U92#O=PQcb-VP9c!$^=h!Ka7GK=jMP7^9X*aidUdPPtpr04)fbinNHw%U+X5@+{X z`e=!!PS8(HtCR=XTji35gI=wUD?@yR1^l;%E%<1w);J>Mh291&0f?7*m#bcbfSZPs z&Qm7OSOJA;4Bu`a^wq5LAp01^fss{^l9(HZ(1@e5=uZ{ZYVOb84Cyrp02?_%`0)*+ z#GxM@XeO*eOq*$25#nYIsmslVJj4sC7JR`favfn8MMt8`Q>GFrNd@D20I_!S$b>qQ z7{WmeE#71oN2EL>jg`8xwNdpDkumb6Q&!R7ZpNIe=RH;4>Y0AbLa1aGiB^a7sE_e% zxOxLZC96SfNUBjI<)-IDw7N?6mx!}PbafR3S{k_m;ckFSk zm9#U9$B2)$e5d={OE%|j^R(w@qAQ0q$!ZzX+MZ#xaJyDX*ato!d-}GL0uJXJr+ozo zrt01^YPtjD-)yN9%ZzTG0{NfnlP=e{>Gj-C>aE!i!0!2J<(WBSpQ6A#B?$1bSzU#(wG!WzS5g?Z{14-=r^*1Z1$8vB7K%*O@$OhPr9y zE#KtzzH@j^J^%Uz{o(Zsy7bA9Yqoh%f3mv~NnoX(x*AtI3`0GHUdKX)b}(jy541!p z;k5xV7m%a-)N6ycxUavPZl4|i%G@r=$hCwxIWw~4Zvf!oen9-rQ_9Da=st&VZ{xB2 z%%Fo5s*37{f#5CF!J>#(LsZ|lUl%UN((%eM0T3!QSL1R~S=Z?1Mn9iN?=q1Qeq5KI z`xDD4y2m&A9^6~teEiT-3Xo8S(Y1uquZgFtHQmyq7kBK`Yr8f_@%u53>%Go4y{AuT z`!k%riuDK zt!*mm&Lw=3-&xY7c0!r7<<$X~{iQAa=u{hZN144KBhxMXSIM!;HL-R^mA}F zxcg05eC{A8mI~yq!m`nL8%Y&rxz`K_P7oqu00{s7ETMxx-uM+gk^H&_y5@Ccu2&cH zTlMp^XBEGe*H*SyZS1?O+PdFTZ(&HEjsLP%Vn{mLYjnAyRiCL`!RcU0-7E7T@MVV7 zVr#00{0|~|fNmZ{X}@-J_ty1i5XomCu)HO*8Ri&V`!WD_oIMZ5dYW{jJ*i2MNtQNe zLHrA-2Bvvdz;+_dvsv~^2(Dmdpc>`@V`B{E^Ds9Us4%aXF9CCb$ZB|@r8r@*$&2Sh zcxEKWHpxSEMGfJO7HkWg$H`QMIWANj=x{&;FsgE74EaVAL7syYkoJm~Y0lhoNtP2rXuE>X}|s*L({7HXJ)1 z8(G>JxPHE?8bln2P{@`fEBYD*4_x&2)&|IQLBK75Szj zzW8nJqiP!f`L_|9f~mH|xUbN+qj>F|XuebbHun>{lKvog)^ir=0E9_5?zOc@T_&(I zNoNO`BuKSVERy(&VkWqVi*bL=Gx|j0^AJ@OTW!z?ku2xfCc9&0FVciN^!3akWz+rA zfs}e$CiJs2X(`}L%S!m<5MrHZ)O5o z)w>1e7^?#00N;s6ja5!;wykN1&%(n4dhi6&y(9l#um1NxtW)bP^Sx`{(RgP(Ht+>Cy2;kqWb_GnAnWBUHPEA?Dmn;zX#sq`=+#EIAR`1xt|El+Eq zqZ$4Ec8HEP-8nz2V?En-D$}c%;XoTYEA-^#g1RqUQ9I+3Zza|M+}|ufF^z~=Yg33* zXF;wIBFTDvsOyqWCR%ki`!J9j4~6=OyKM)N z)Wdz&Q`h0dHT4xHvAyclXBK`Q?}sf?ProLJjCf&TSi=i9wP`jTrie`s;&4UYVmRpG z86R*6TpUJM*ie|8xA(+c6NCWBy%GZD-M)G~f8QnzZEE2jeQlAths@&!;t9#u2A=*I zIeM&pL|qW_IDE0MgF1&iNCE=aN3Pf7?&%xEs-?=qfsb&b%a+}U z(u1vq2~ZPME{up5i!qi2*5DxLf_GA~F2YJ|xKm*$8uG$bIxCvus7wrE(5fX{xW|}0 z5FjDB9k$(wd6v-#f1mv;U8i*pAnqVhiDN_x7Fji+feJyLJ>>VtxLuTK;MdvDX_rnG z;null_e}QqV3WtZ8bJ7MSh4rB2gJ6iDRr=AZXaB0BhrLq2E9ETn0pHFn7Sz2$3c^fRk1#P@G*T+`m6f@W4Lb#~97hMwA? z?lU*k`nAhiUR`3p0RneHOtlgpqa)i!UqR?pu7b!N_NP90*k;*_qxP)}x@XT_x(2a- z@2~y`RsHyfb>h*7^y0Q2T_WW7lNW|`5A&~k0lFq_n-GXwBs0OlmF8I><@sifrO!)X z(T)C=ME3upv{7vD*OpVn(5+vEB~rfHc-&Zi<9Srqtr|Dh=ey1u<@cQY|4;JjyDd`S zlv&(?3l8J#mG4jEXaMVD_?6DfNH$>Yjl_Y^oijSRhB8NP!0vw`8HtBFd9_gwo;HMcxINLL3Ye(e<^cKqr1U zd%E=e?mfEFK8Riu2!}!6wzrP!sTW^V>#=9lm6}p#Z#UxUc3ni=T-ns7{(bw@(c7mH zHr}Z^IS0pHtInssU!AxLb`DReZ|AI@f)n2R#%n4}+*HHbFb2*^jWuFmi2iyFqrIE# zWVX_BK-Glntv+!><2wfQ#mDyQ_dC&Tvps5?E&NALqv{8tVgcR2&Vb+Bd_fvVBZD~Y`3mof0mbfD`$pr2TMLmvb`EiLX*W6u>#2G+Cy zzykhj6MG2!gSLOIwzahW)#Bg8Qh%Ga;(B^ejmu>%-WD@hbFk$jZE-{RAq7w#4mW1x#sI@p224yy*{B`lrJS(z zCj&(&6EFntjU8lowIaYqsRkIGuyO{-6EvX(;*gdNy}2QX^bqtC^(i+}C@3 z7FSw`jj)3iL{%pQIg+W|F6Q^{fCmIPPM=IXewTKQT~d*d+-u_#4C^u*Y@*t+y$jNj2FBH947nSV5G%)|?>VgIxs(q5 z(My^yjOp@27zko}@P~KCk>WMz16QZD16RTneKX7Uf}If49!kSQ4>2PVrf7hJ+<$NK z^Y5njR}Zk-Zji0=Kbl*PR-#e(auf3^Fch0Eij+|b7_q1-_wL{1D-K&NJs~CFWKOXt#R;SVEvmYFRNZMT`ms5vteVrK7bi8-+phCl_7hO$VE`!&$M~u~Gp4Nz zN3|>Ys&+QMp$R_dTE)k8scJ6-3whW@jo7H@gEwoW$T_Nj%~dz_bNS!VeW}+pJCj83 z-G-=om{>x%8v>t`l?y~3L?QrI|2w^(Qet*mpBny~dSYx@56xfaE?f>C3px5M#ICn7 z6)VDfs#g@~O!U`TIO8kt6T)eu)e#U*w}yn-ITXnioA72m|g8_D|@#+pN! z0QESUjj~wPKhGs|>`q2>o?w;$1kRXio7IOo!S_44jLQc%N}h#Ra2?*~Bb>SG!LTqO zXg{|a#bFf#=T9DQI~!}z8ex^!*wrqmr-)f^q$}b%sA5j>THMMIAQy;VAM_tKFOSc2 zPN#aano5WW+h@e0KH>)N;ALTffP77W_%o=3XgJhM8@&6Yza zOI|CUEHxO!B_Ir{APn$rV9q1`s9R7~M^>4;P%G@i;0+vXkJM{OIeu^DDg9>UM~LOm z{KRrTScBr08r=8hVUtaCIieqKtHi(NoBaJ0wp$-WZEUR>qt7@#`mY5k!xZ)#!B`Mo zlEw86)eFJaA)C9YnVzjoRO;|Lpi8zcj%nxcE6N;uMFjFwk*+3&pBj*O9RBqgG;(aW z{m~EUjP6n#=dGG6CMi&##@juD#DctIbE=Pz?Y-(~6Ziu6P%L|MmBNP<{LF)FlF!ym90$ zM5CK@|6?D*8u*MpclD;$KJ=41H+BuNYqhQ?4{2%$Tk6yu2XFX~33U8lJBPF+EBG(3&*(%2M%RfZlW z-axk&tNQgrEmsJ;`VH-f-B1lN20E*kaKlRJMEXI!QN3Ry)zf-y;kbSrW5cdifO*r1 z)zjSX!aZt*{Rrp=FnbaGe+R)={#Mr)^=NKXXNDhyxlO4t(?XBd0?d%$r0|c?s%HryaS71Th~C5L2@wru z7}~%&2gJwaIxFRT`~dm^ntHy3;r5>}xj6cix{RDd-DEaW^h*q!ga4iL^U>x+O~tq# zOAOo*Zi~hGmZh5rK!p-i4=J7(L9*Ux!J>VF%7kPKI_l3Orj8{+aOTP#j=2itZ}o!_ zTw;!M=28gV>~~mNU<3~mv&i4galIx=+$Om5pt8W2Fj|zf!FB^~C&r~AIF7D{Z~4gV z1s!Sb(L`mBS{8=XkNReXI&*9gg|!BVunt^XPf-ltVth|fr_d(8HIISnHB>U!Kw_`b zf`&{AF(+F|w;t0{d#5JqTCmh7?nI$cFCle0QuT42 zFYd$Wbqn7>Ungf-f7c24rK2j@G;7^VqDq99BJoMiJ)1$qHFP5 z9mbfi{^XD*>t+bHb(qb9_M@Vx&;`_9@%SK~9P|w_`%SPVvs2prx!=;0ufD|esnF=w zgM1^Vv`FMe+BR!=%K?qteTRkzlbUa!4-?(A52+N=m@i<VoA>55IB-(S zZ=MQ0wwXmx#5i}kt37!9tWKQ6yQ4~btM4Yh%&x;J-SXG){?h7<8VXAq$Z4|rv*`JpJTw|?xd4xYct|uxk zrSQCb!6bX{_4=b|0*1?tV*Ws!-pce%XE7U@($^m0==ITGFFj9F)3tu)C2hk}PxIR) z0Wofu2~eH^MUbzj zN;F-#9}=^q^lp{|dHsvpNYkJ{?uUNn&uAXHYW%g@7?ly3Ts(#H{)Epd2{hbf%J`irrY(___2Xtchof?|0VRIcsx#k#Kh)vG7 zRyN?$1e2Gz*r6fZ(SDn-zhBuip#$j|-821J6;T�RaE>&Abjzq*T2#tKKJSb>!Id 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±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

        j@3(|Ni}H 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%0DO-v>Y2RyHw)bo2S&t`a2K_2C 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

        !A!2|Z^dxq>4;?e-H4}#q8 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%0AY- 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%0Agz2|FM1TU;bBi3?*G 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 H_j&R^#wIk> literal 0 HcmV?d00001 diff --git a/chat2db-client/src/assets/logo/logo.ico b/chat2db-client/src/assets/logo/logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..d8941bfcc60dd79c20282a76b3d85007b38356eb GIT binary patch literal 27690 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 Xn0_J%>OGzS;GaKsJK2`+q$m6zM)77r literal 0 HcmV?d00001 diff --git a/chat2db-client/src/assets/logo/logo.png b/chat2db-client/src/assets/logo/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..aa82a9822c7366ba37cdf742deb29360a44e1492 GIT binary patch literal 17421 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 z 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 UK+$$Yt!8=VDOtoo8me#b0FVvRw*UYD literal 0 HcmV?d00001

    1f4$EKy3$dhfW98oH2f?_HuF;T)Hr?fP-kc^%rn^dYCauxswQ`9oc{Unx@ zMcOaz(y7~!KARpc5t`c7aFCzT`2JrdpXGW;&QO7%_`-@hzpby~?UPl$D1X?JdA z`Q^F0C%r%{vrXdbmA2c1)V@htOD!Wf-^K92o-a3y8@8@`H_0X9)_%}uocjm^DWbkmm2UCgGVnQe|@P z{Kf0k0|LoYtBQ32(PPfxj*j8U1QeYYo=mI%#uQ`!DOL$q%p*y8OB#~`A#qM&;SLTv zIcdnFf_JkHz}{iV!5%n}bj(d-tkDG^e3Affz&8rYcF(P^Gr1O`(a1J*rrcCh*TV+0 zHjB~~DemqmiuA_kW>59jS<-*x?y-TFCbEJ!YoPf_e(pm2^PM#BtvotKU2DA3X znO^B09_e10FB9aFN%zR~DvoOR*ftP;d&5-U@?*NrJKqC&8$qi&vy#&-=*nKa%6}s) zP@sxf9CrSkg=5drb>6p`IS%9|K@-^_$NcY@;igh*>8vQ`ZvauthHnq22h(5BPi~o$ zImZxn^3=(b8uengkWU0I69>38nwY><%Am0e+G!WKT7eyY$$4Pfewx-jdU(O8P3xfu z9`33rknPyf+}8Ha{+;ii_9BD7bdE2bpuh1}wr}@QmnsFKd5YjOKdSauj=wSAJ2fp> z0ByU12H}v4zy{~@d>`EnD7l-N$2Ty0nH!D+l;W1T2YDB+7n#i#C~{8PDLjcq#v_Bw zBKcfh{hDBs@vC4IHe#N3bBT0vicE2uu-z#jpgB2Pu-W6@<5pf))PlV;ZQi^t7LVEe z>ZXZBW`XCux0JVbtN;?wq}Mk^#v$dmuX_E6on6?EMb`gu&$;UQ?Ee3I!P6P^8Ls^U z^xCCstm1k|6~57mS^rW+?uC=5+O;dD&5MfBnAtE@s`IRKYE`;lx-#7#a#iWIV=8}@ zQdLes0ZsRZarf!S{o9n=?f^5jAPo>D(kW;O$jp2gbz10DmZNpUz*I4)M4A{ar$w?Q&jJ`E9SJcg#8yaw8*6z1??TYU1ly;J+37r^3quMBKDD)sv zW34T@<3i{}7lPJW!{K3A#ja&noKzc)%^)l;psG1v9Lkv*D|I>eDdA7aAFm!+&dImg zdA#;h%x57GBr14XkD9`V9i{IcdvdiN3ku`t9l8HM@#F>Slu$}LTR>i*c)_9s3%Ksq z@i1xY(SBO{F ztrXy0<=O)=umlTvphB5t$J2ir^^*o|pkV2i`?H)^b?#Tr9J*kw^w1S?GoNVlc}j`j zK3}0Ll4Lyzlxw!ea8l#9El67?4V%YJU>2KWltxSc5#uGPgyQJZONwQ-e`$_ahNowS zSI*l6K0CuR$LlSzD2-EXS-g0Q%I?528I@6^U*N{Y?iNP)m(eI?uzQ07e4y)yoSweg z?4iAN4Yrr1VbyM6#9mWespM=2bz_bkC1)IcwG`l1n0D({6K)Wf2*FqTLv=nbgmvP? z$$umtoNtqwNVjRneRG6nB6t>qVyKI5fS^nWo|(`u+c(<{DS{PS3Zj?BG?2|r2)>z{ zEptJv$efQbA&B5(QweA)m{RjO|fr# z5M}0?k*2}Lq@~1CRXH(r>Z9chMR7N#s4ONrCMHKJh)qk8*-fkWGXskzKfMq-3F47tq>lKo}82=-I8;`h-od2*+c{aLX|(hX%_Doi?L3 zLyoXv@leZzq~0w5V)Rm;6%$$HtLXS9Mw1bzMFF(r=_geAJJhJ0m-&Q~{6%(&xjPW+{ z&>(YT2uP3pq@qxz^(~Es zhS?Mq`*%Y0(4d5pW2~-{1Q8S{Wu?={tW|tL2!AlPqvqLmXGPZ&xRwCshH)xG5KVA zdS&`#zgl!T-8cheAmwz-4j{sf>Rabh)kDi6dZ?S4!NTd7O9|jPS}x>rplH z{PTT%^Zn=j7BINMKF;sw^LR7w$hHFr@Sk1PM*=)c|A*r^9`2*+@k;T@xK{hEZuU8q z&uRG0ah^(4B;nO7LgEVt`j`J6I$HVvzpfC9eyIPS=G7p8*7sZ34*O%?(cfXhxhuFU z%PCE10mQ2V$Htg=izauKfnyZcuQ@ zQr8$Hj>pBnmz$4`FIq512+~NXBun6N}6wvv?Vff8oh!9{#Vc;k?Mr96Xmr zVwvhmf7Q6aXAMgE3$gmn(zaPV>Dr^a?|}>c!_4gC8RyyM)iC`p#Gi;>9RSPQv)r@K z}>I>1_lg5^t4?j*eAtEU3cf8Zla7) z)`*QPWCs}nZG~1ts-UaB+AfSyaU z-%)e7RBXURJ7Z;gkJ%0IzT;%z&X#_$UlSW@e}0k>M{kwT+Y7)`94Nb^eO0z_QKQ8N zi?T(FW)0k=d@-&eNo2OSH~KgN8P1O=O)|mlOdn!@VWRw{_I6osZ`o$;rZQl2OR#8B znZ>N-ivlKV&PA~Re?H7U7XCtESwyyvC|eVRR>KcKQw-M3%US6QLwq}iH~*~mC)f~;s5n}f}^ zN#$e)2UChXPWIe@uxLass@GGPNL_|mMinG__U`ukavCEwrS=wS4o=&Rvd(33Tu5uC zMLMH2Qd3^iOEF29B_RO~BvXWjl@X#UWk_{_CZ6V$X1^g;*3b}7WjO6BDdvWKWqn+k z|M(|*uTzL|Dm0QA8S0Oi5ca-L^wZ8|xlKoUmz$XGGY(~uwcAfFK+WDoTg=^h^6#QX zGPHs6qn5IzD6yfx&D0s{k?_|Z#lkjdz)XGgWDtflCl}|c&2Ij;a(zS zf^zprpKQ}Z?vdWI68m_8?4e$UY>&OAv(?4Y7vEds`!JauD=BExw-?(pwtu!!6eRu3 z#gUjK0iQQu$Xssg6_LEsu*k&XrvBrQJlMwD)N%Z>#Ix*V$J6 z7PHQt?z6GF?sNUk(`lb^HoKp+by8P`c*`VN%x=(IKg3U4`S371cP>cj3@XuTN>&Qk zDM(f5=bpZ=1Vbj8K@FN5FQYCwC@tA#@KBk7z za>WQ}div)EB{{Q@9J%5yoa0UpfIm+~Bqg(m9IfKNT&X5_9bVk!h6fHDT#>ohuxE7 z%@MGS44C(ZoliMJx@0PIGb}R`CJlUnfjmEC8OA#^U?XOh?ySm$p@zsqq!IGSEtFX| zzRRI>!(onRkFE%(WY|>}AN^WQ;>ocUKK-!)J}Zt+NrV>6Cz0l(q-2JbPg+EU{#c}v=x4_X9BG79UdrVp?Y$0 zLlh;mkP)VTpUZVKh_XOGs{~doDS@2q7Z{S}kvOhoTOqr2 z5#-7&O)Jo8Ppns*vPH$nYQ4a=X$fgee4 z#toPqHfzDePc7A-qbq3PQ-{|)gvt1Te@x||-*7R%5 zI=6pK3-xHS*ju)87|QPL)O=z9U@jM3`R@?i?eqXLp|_%=U&yz35{-)mZM=B!BMjA| z1Y657R8wSGUwI@(BeLFZe+Fu>%{*Wa4G%G~y^;MCKa*FPhh?s%$s^aX)UT|sEDHVL zeuv$GV*|)i+74@tC=wH|?+~QqI_3shny@fD8u8z~He;`emLw@D&`pu%?w+>LJvGfk zfrB!j<6@OisEQP@5Gmb@u^a{mr*Kb8a|axlzIv`-p0977-`rmW1oz{f>-S4pSCyO- zd&0`Z*2IE2_9Y^o$23ZufKW-v^+0@A`SLJdpK(g=&?(wG>j+*yv}l*Y)5g)EI+!xB0rI}4S9N+CSJ z4ay64notf1cDsIiPfDaGSdz%W|6a)+TJW#?Jk4EoR5Qqk)>*wH=WAf=!0dz~Mdlu3 z@<_bZf^mUp1LTr|Wo!-vA&lFS6s}rzPH&41%!Fc=6hw9%J9NzdKFhlR6b9UCb=+6)k z>n{A2pP>u@z>Z(r(UBP~(K}0YQUmO)Q;d@#Ql^(@z&>=;UhJdKOJ0a+KRc=+vqwT` zCkWwIKnW$^7mL(tk@ywN$01USW6oX_g4X)J^^oxqgyW`!daPLDYfF%7u~a-@4FpQnC~vLn27L;qg!(r zo}U|R1A?jApEv5Vv&Wy9WN;L4p48@#N3B^CHJ-oFDaRpmOGgg>a}Xbw{&($vtj#TU zh3%qz-xzf*Yl=N?4ptstym{-PQ z2&8L`r`9`A2Ihifa!YJ*;0F8Lz$8iYL?k%`+1Ul#bIx;B!rP}=sUUlY_x2N{fPkEuB+NQo(zoqXB1=);M zkM?suCMZ555)Wk7=D7i%1N&D8-rTvfbX)p+`Z)a^eGb|v>F+Tma3g#UJO@6G2*Ebl z@x8?W+=#G4p(aoOb%H#~DVKrY{x|srSXD{ER5hG8Z?1#CZA{80pHt53^bYcVWb@mI ztBXn{J3r=(UvSuMYH~H@IR?qNRP2Vrz|q z#}i`X9RxgjTbIvKMw4R6=LHOCwH*4`J~t?ynn1ltKtN0T#|^0&(L^0FO`%~#M*u$a zTt{GtIq>5b(-;}dcLWC4MYw66$%-e>&&)7?_A!cJ|%C9rNvv^7I4lsGR#Peqg?}l1@P9*bFniYLOmTcb2|y#gvx;zz;pOV zh6c=tXNKA-G>Rmi7GFLU91L-Mz!QFn0wGanwXolS0olmROnF@Lum7{L(s;_+`TqX% z>SCGgP}jsHH#$uZKu%T7XhKo;xov86-ah%*<`aoUriSHmY$-;B4ngK(N|8~Z6kBFH zZzmy!`1zPmLVljfcqb(%;V}ZBa4NDCQ-(+-7zqOe?e*xT4DP=oM{_kh{6e5c(bMlh zk;n(l%R%YjG}tAIbYF;AW@ZjSZ^}g}hE%`*^zmb@^sNswTp-mSU1M#&ERes@J@plG!)2~($VTnIEWmAP zEXPnE^75c-2TX;xgiz)Qm_djtVhZd{$CuTGR@>8b8q|wu`9^d`YY)Zfa4o5ovoG6L zH=T2=XUkS*4B3?o1?T64a1?FJ2kTz<(?W>*p?&hkJ+$TNZqn0G1q12r03@(;>moe& zYuZr99w*1TR_BwQ^yMaLW?eyTA1s!KonbeKZvg=N2mY7xjKVT7 zJPZ!8_eP?{kSVEK#xnZuM%P~Aequ0n+v}x+jaw9)kh$j}P_iq98MC!*XdvglUYxU! zyE6Hj(SfpIWjfl(-&1t)bssDSdhO#(pU35`i*Bxcf)YY3D{=j7@^s*^{wEu4oKwEy z5slCUFQtSM|F=_3ov7WK^+k}&Z!Z75PiwAoB2^#hD&gpnhtCw$xcN{+;0UM(z>mT( zsFMwdjOMdlIWl?gLHYN zc;xibN$|PhG-N-b-GVk($ct9#2=f5sCJF+D9R69<5{`1u7Z?}abn}+9gz<0Cwjr@j z2zh|oL|&Tosk6U>`QDXxP{K zj_w@X;t!JkG=T-J<9bflz4LdsAG>>O^1%7YD?6NlC@XdTrHHMoOsAQ*WH)>g;}~e% zjyt;66^@P2AT=oGmqYx`0_0T<#!H+s@`=T-H*1YZx>+Dt&R%m&m+5&dirUu>h_$8~ZJ?tm|#c{LXt{L(UTU(v;jLM#_+QFHy zZ|RVIbTPn7$anpc#hzM?2m5j#i@!3oo44cg_h`{77Il*lQuxr4cU~;G;%h;Y64+vD zwuVg!;3%p4{WBeU$keM9i}dzH9^ZP<`KFk+j7!;7@cDB~?{4Pqa}4J9()HV(;pZ^d zHG>t+Yd!WcbY9QXm<}?mn6WdzcO`?lX>mV2j2U*G;XAGk6EnEc)vNEoT)oj1ajZk`TsR-6GAb2Y-eC8W)2=cY{&UlIPXwp0KonANZ;s6A?nw zry23hfwYqUE%q??++om|SgoA4gt=sf;nvL0uVz;NZ>gn>GQKlU(!$pn<&XOFpo!WQ zY{{+$0cX`pm%wGvmdLqknv%MP^?t&?W=WNe1|LY+7WXpeJN>q&PfNiwi^@7rwx#fv zSJE7}jZvs8OSc@Sh`PuZZ}z@V`1V7fV#u!WZNjeZ8K_qxastY0vGfUWA=qg&foRg# zw$~lXt02-I4ARa5k-?0nan7E}i!#6TrlJfMOtrq~hM403^qqem7nbzPSclgrA}m+d z$3o1^)V}7%@0I=Mp4>S6W$;97+24Ae;|4&ortf?t>3{NX^8dyCjsiE1!y;F+dT^$* z=xZ+N{Hb9N7pf4n`>}j?-g8mf#&p@fwR~-!74+y0WuIS6f#UIP$C0;%)XWe|3omg- z;Em@2$bW-z2R$JH$T8QY7}KVkUnrc*=@D=0wu@>1y426Zr_z$)5qZmi>x}O#3R~+Kencbq=)j z`mrB|WUJr^mL5vWa9kVacaeYkLjvnf^N1OVF0yGdqbb%52lPB%TS2iO$F4(5t#fqj z6sYrPZRS80-o1VYf9hSL)$68}EeM);+sjh5XN`hp)-;T5_!s@c>U}S`r)d`>ubFW5 zvOG0)f!Gja=NjQ0oeX)f{PkWy2PzQjTPIGu!^T?LK!Qn(9am3LwjR9UFdphy_IC&; z@kLW0Gsb7NiGS5RPig0JLfZSYX&JJ154L9X2@Xu^n%Xa^mcnDy@hK2q|U? z{wP{SDcRg{N1iHo+yD5k=D|K>#js2Czeu;T7(gmgEFCF(Yf8%GJc@upp2+klnMQv7;BRQ435BD z`b{Ulv76Z62`_SGg(=ok1P%T+7K$(he`x)XYJCnz#@)wj2*4(xd+LI0AA>WeYiiPd zvg^iNWpek8%jMrEi+goAbJ$jxVc4)s)21VvQ958@S7fth!eGkmnAoUt;Ad z+v$eKOBQ;W<5&7GoY*payW{fW0K3o{e#vX@wxnaQV!9am_s^)7hbTKnL?baAzogvri+&W-7{S7TAX)KnJlYvv>oh%bMAPX+R?=>>$$E3Vp<8d~>D6 zy`?qq@SJU#&P$4G0Qf2i*LDq(PH@*cuL)e|yza#xO^m@F24M6IGMX=6%c*QRZZ!of zv6V@_9`&T6mdV5uj~uPP@g1l8(k)pOb0%W+w4=??0=ydk>r1L1Sii2RE6<0V430>1 zRbL_RRQ0s}=6Y-pir$`sxV>dgKwH;C+ zbSK}VAw;{aAmKkQqHNWQwC#o_U1Ni`Wcqi=Hn5~xz0l8;5L_AR7+P@PP5_r<$Ul&O z@b=fXzk%z(I{ms!>Z}-vLE@s9##{hE${>l6UO=y2B2fVSdK8dk=_+!)v%S5t>b^3$ zOt$Rvxn6>pO^A0^HYsm01l)SKF5+Zz&^slo#7@sPG14qkvGoSWJDrMh8CV@pTYFAg zFHSI?tvl3Ukg8oF0IJj7Al26&&}7C465!G`vW8cigW^NHLJJ7W+%{Pld9RAM<9-A40h(&~BlyZ$e>DK}psG>4HOvr#t@sA3ht-6W4+}ZbdT&O(9%To~)BCSArdTIkHu5od>5B^0jFS5(k{`2}j3~%nH zdWAS30E&`}wIq&{c^{%(U*Etga%G?~aQM?jjuXbxi=RqztjCa|Q1gn7|0z#NhRe-M zmJ7m8bk%1+r3iXTCX!X0xbp=wLy`_9()c9*if|Rysj#IbG5aziG2tOX(d%upH}bhJG?XLUDoRQ2?5xt+NN8;DgNs;d)zB;EM*aB6||7~uZ*mA&INK|KZwIT=MZM;S=f zRTC{U#I$j-tq=u;fS-h~`7+V*e|la2qBwPk$l!HR$HjEz0sIaa7f~2Ce?)_ZsePC?LA;>9b z5BZ@DHpI0!%ri5xuI8PG*CEfng%3pO-pJ{`GXhJ~+uqphBG_#en7T)Ah_EbE(m((UBi^LQ2Xb?Q0yQ!o(_FhQj{cfHtf zpv=LcZ0pw36cKhyQ&pw8^|!WaUd%pMxJ|8Jaj1|7057WoK3fWz$31jk{IBT#p?TH- zdX@7vjWfwf`#LLjWB{;Yoc*uWOoa*k+N=r5l*{ByWRuye!utZAU;enUlv_#vlC~<(#49=GUEREW*Fd7`(H4vVsZ1`DNjd5BEK>Uf%P@`9K3Zxe zTJK}d%4U(SZscX+;A(JF#ZZ)$cHP>9_2@sdd)PfPy-B9JN2T7|^aV9n;Ex5s2t7Ps z?HD~$favc>FFw9I4O!){y1in*(1i@KfL5R@j?2>;e z&E!yWGbMrCEx@pN7Dw(OA0~GTyVp}vy4@&|tR->eZb}1V8a{Dyi_kU1HB=tzDo6mX zVZjU9efw!|E$tx);QaZhaaC7~W2qVT`(x~x{4Mqe`nK?4u(tmNF_K7dDGBp7c2R4W_5iKP=$1h;5}S?O;ubad$Zr8*}kU8aD=OzCs^I}ZGMO)ep_i%O1D<H-EW`w0oAb$s@&sl-*-Z+)!D_1EXilhiq%jAp4iX)+D~N48qSL zhNsceAz@DDDkyTpzgGKOaj2ekcdykx(Jf$1FhDnK#RXx&A4cb zgz_5M8cwLak}JKW#-2f$IxZWElvB%psYm?a;mhyk15^u2}E@RydI9l^pT0AZoviDrx;I~TG zBN)Ld{YYW4QP*}ZsELMv!l04RC~}m?sb9yR@VDQ|tB5oYGY_PqYpb3I=r+*?LK9qX zcm1<>JQNvWb|C7%{UN{xVqO~zE2knBV#8Gp)Kq5ZzXX!a3?ixGPi7E09BUJ5sST7V zX&ofujBbW9w(M|RYq-Z-21cx*`6}=q=fiPl*3B)-24++uJ|SSY+{ST;CSEWQRlpH% zb-o&;HHX480-&JYZ(;7FTwV!RgK*^<+5?d=NpCdQh>Ybf3&`bpnBQztK)z&%f+<|d z!P*Pw5?w$zA+j(T0#Tl9-h}OLXs;&~E&ATSqcbjyR$hbvabs;%a<`zHJissN)8-R9 z?Oug9<8WRn+(oK$C$abxdx-p#v=wczrw1PVW$!$l^3~Fs^HP#3N!jIfLirW=6wAfMJs8=j1M5wcqYE{XI zWf(I6rA@R=&J$B!9f6W)`HFGU>)Na)Fvfbq`+14a1%z|iv9$50HieH4H zOq3seN&R`Ge1d|?3=|%#96(zDODLA)9N}q1EYuQ`uVv*0rJ%5Rvh3K;yFS)*l8UNV z3wK^_k_8M)v<4~8`Nhe0MBUG(9WT6|uW!7qBibbwyRlpicZ=>?_srwjy7jLXM#xl1 z1{7&d28XDg+!S*Fh$Lj;kCABXQ+d8mptht`+ghgei3R$&=0E)(DWuI;^g>O z)t_JQah1o#%kpcw$MWRy@aJy;Q-EOsi66@+35M7Yf`c(bU})_idI;@+8b%MJMo|vv z5!A1t!J(nDl`AKB6DwELo9GEMemTMwc>;KmYR#Z<7)@xXt?msR*b9f=BI@@A01cMcB6^w@p7u2S zOPwqaUxd>5VhU~6Y<9^(f_@R% zEFPwgP~K68sQV!I9c6@SU+om_(H@;vR5Yv<<)(i0(0FR%e=2?^%qnIp0PWO66%A}Z ztQV(?VujI-Rk7?FZWN3`>(ujYrAbV#YCl5JYLc)641VU3`mSt}!!v#O_d{`lVo1RG zIc%pTzJ8VcWKc|v?f)Kbc;=8pB>6Exp*dNAj$=1h38Tev=@sA9%~B`h<#^t=s3D)e zBDATo8*gI0K4mpzb?P;1SW-;5n0WaS)FjFb?;^*e`wbzM5G4)$S&XiJ!yd!3bynCh z_8ZN10uQOV)4vgS^(*=S7l- zk1Z{qT%nq0b{Q1$Cx3?j zQ_p$CM2XofvCg-8WR-6{BTlnmkT=eKzSXW!D?|h^>F2+t-O9SV=~nyIx;X!aBoVX0 zia~Ued1cvGH7uB1YUAbc66zb(Og>8k_ ze*l?moDQ)-GR|h6usA#{H{bQ)jv2hxyiy4}iYPY&Fv&ofCS0#?xM*QwOc{O!n)fWq z&_80cX`wApmpe9SA7}Blt?oOEA&n9Oxit%BjInV8TYq?FC=?l<+EOpCCKu`WggJAX zn%WK&6f}p1%B+x(q~zwnJpMw#wwqkGHoz^53(=o0>tWf7)oQU00D?|B19`^IaA=xT z?-HeVHn{u|?ZE4iY(w})z5c5pD`|^S7V7a0+^zwE-ti;ZbDK|xUMlk`COU)nFEZS- zR*|Y-lAax0*Lm>j^=Hd*T=SMKMmT&_sr)XyQ+Ij$n)IZZy&+zTkk`IZCyQTL1J&K* zcu3IqSL(22_o#fIu`@?)?V+HcL+)<*TLI2}{PnAeNn$kp^UvFVniwQz)30CC7W_;9 z8J4{PPi^ocIQKpy!>DP*jU5Drbf{YX-~YN;)0?-&Qt*U1E!?vLRaPnZMBLv3J{FZ# zcR*~kQ*3i6OUX$k#Np@9(Jjp&mR|3W7~377KmVm}tx<111C$L{VD?dsqbphw|NjJ!`Rnv-q=L z?xFnP2+I)n0E>xi47M#c1RX?Gle4+$yM8pMSG4pM>CHcOrE`0;jx75bnSSR1LvhxOA{Yqwq*e^H?ZgVWD)P(8SH3 z-^JuHn|Hhvi&QF6Bmu*bWRw3Y91nND1VFZF|Ah-N{dX&t4Dd$$b)G1|9hkGv)5SF_ z(iE7aGlYiKW+G#6uZa|&?o!ewH*2XE*DpGF3Jhup-IO;Mi9w%6zTMdTX!v8X%kZ(^ zyFty6v6^CHhMR6i|CRgq&A)&=IXNX|bbmD%!D^QTtR2fRsiu7*ZLFu6J1@G`IpysP4FkyuboU9bF0w;LA|pj`-~6x6L^;XmnY0e&E;LK%aGW^4}beMvQG;xD)?`HcvE3P z7uSC!vAc`7(x2O1P?(UhDZakVe(ZqmEQ5MhcW}(Ets$n9!I%be5_6)me287hE;1VZ z2z%&J$;9G{qIT|T@tis0)!cTE6_mKP^ngC@&}7A8Js!!BDvC80SvMJ~s8CO5Q@%Zp z5<2~Rc4BONa&~qCxPDrilhYL3b?erw1)`hrmeGlzf?Kx-K+p*Rtg@tJc+Hx|rZsCr zLS|>1cTJW}1^GEqpU+~63=;M=pTr<;mtrBfCDM;Y5hBRFU=Gv3%q5N#9K5DVOeHG1 z;2*d1F~?xxV~IuuFwZjsP$%|@J9BWn_MjscvoFBw8nG$D6p7t!BIyefBfTO(aea!( z0~)~NpGx4qXw24@sd3dX*=Yc78Qa{QumY9{@8oo**eU?)=;Y_d5@4lLoU3KQ60m*9 z?9H_8sO|K#i_iX+#4<74!T_&c!qMYJc1vs^bG;J^I#WXmwh-32wRSjFi`B>=v-0rp zax=`Swyi#csB>S1d{o2^(1{a&9;+&{xLlood&!(mSrqb*1(Mr`kH*pj(<%dSYx{r& zm?DG*k&S8*23W*YE0a|=K#&&u{w9_@ad~f$yKdcKEZ0(nJ$#nmWXWYT0e97=n^O~l zcoFXOLBb`{tZz737g#&pu%;&}$3<+h#X+!Y^Qv3KTUF+2ETk4odSY5{jv4T%wf0TjWQkHv*mYC3UsNP+1W6~R0fHh#ke!Xe-4~6Rx z4N+0`&`20)qvDcN?Dhpos?2qPU?rHefAZH*I2ZxG`a3WHOCnkzL?$sIYf!ysk&;8h z5P$W|hOe@&4xb&lPsGwHR4LB~Aj?Rn*K~vhG0be4f4x}}kq(QW1bp#M`us)y5;%r@ zZ-b1*zzxNYyyb=zD36hZY0n3IQW`aKqeR!|^@cRRHG{=pZT35FGzo=cdDLbcbUB>)*ne)LlB%`Z`!Dd^` zT;am4Vpu-xX0N6O9#u4|Uy_Ld-`}iubpE=H&c3;lb$65drn@X3agMFfEtbwTTe#@s zO&q`-Qf+W}gJ2Kgz#pC)D>n|F7I|A%Y^(_Nj=#I?>k1qL1()>6yk$3JUb5cY_dW`x ziSpX(e&wUmq<4qKYO>C1H)ju)z9(%DkoRn}$a?2)1-uk;%o+Ou@|5)s0I-vOBENXV z_JHhD?o`O50f#`=`rnn?vyD>33PU+l1Z#XpwC z*+j8dgu`%}CZNHL+hXO~9T%4!wwrjP%KoC&ZI_HrIAm9t0%lI<84F17ZjoV~o471x zS37vTz5=Dt7?H zSN$xNEVay=Mjkkm7hMYtpWF~;9eok9$tBkTI6&tuFHyJB8q^0Md+o@=PrQsa=f*Mc z4SfZXlX?MJd!eq);>E0Rdn=&^wb{GPepY8qnr90T#cvdY20p!lkITd)v^&el=U%)%9c7ALEYc9}>fvF4aCDFP6ajnx~X3`PMfZMxu z${DL@#Tr-uVohd#XoC6?Hav6_W77OfKxQ?P?@UZUo=!5L{*a2fb<+(`_A1|amu6_ zRL@ooN1a&bfmdiP4Vw<0bvJ4JVi8t_BrN|E;9)82$ay37axQ^8jL0uEFwE<28X{%h zTWS*Eo$&yq&BRT}yLMOvep0AU03FtL$m@>UDU3_?+!-!Z4e5+c_=6{PWLDabAf5;0 zVXI99o)oQC%aX0y{ppQH{^u_L$Ir_BFm3tlY)k>*SK_(t`1Imp^*g>CXn~?Q0RM5z zW#o1pa%BG7-P4xvwgC7zEN8EK4fYAluMEh_*>Wy_gM)CGw(Ha(W=8H8R%St2njN4p z-TTDa-wEE_D@2UI{l|u6{anj>P@wpCA#tvdn>fFKg9u|yCM1{si_rl<06O0`K!Vq` zgFTS-;1IM10a_QgZ53$1RI5P?W_+docxwf^!1>l1v|zrq6P!ppcNZ9HAqD~O01j8b zY7NleV5>kcc-Lys-vwW(ztviS(O$IHpuhiHJHf5Mx$6RljbWgGkgz^f(}9d|qfl~Q z*1yrJhY*zdA44Q;IF!y3@7X^fV?f~$AM7!u=FkM`58vCzhu+I!2-2cSw=SjO>lZGj zFweI}`kQY8Lc;n`O<#9QMz|R}xj*y%MynnIr0@TM5DC2Ohw}DQ2;UDPL+zFlb9}JJ zWRd3ZfrEwnbMX5og8pql4nugc7EQW!8MNW+7uHw}tawX4*lSr)v)kkAOutA2rps-4 z?EgEP^crcSf47zH2M}ShV0>lfLMm;n^FbKJNt)$FS=CM3^}{&L%ew8ydEL+Z{QwBT ze_rh08w@8%ie^}j7eq-`R82Qb%XVDP55g!;(kw5^s&3k@AI523)@?t|>wezv2O$_i zF`OVNnqfI!AQXuuQkh(#RH-#uo!($HnJreE-Qje(Jzk$bF?Y8h3fuhZzNR=+o@gYt zN2mU7&N7b<*|xOl6ey}|__#SMaI2@Cw6dF)NiWj3DR*R~!c{0dKn$fUYbB>}rU7Kr z5=g|f^BM_wHUxYiyp+#skf1Y4ie4RMNy#{Usk@CTe8CH@H|oqdOh=?#tW~zreMXiB z-w_>fVPu$|w;CWuGAFDl6*VMy$y*>n<#AVmRCJW;v{kP`^GX_E``HSLGqEKq-Hm0Z z0~@C=GhS*j%Gx~BMM4)HVp=rhvB1%wUtfmy5V~3}JU>7fWBv5Ue}DV+*=Nb+lc8>w z76bAmY@M#0XaeHel^&kl9AmgvkoXa%IJtzR7*TYA#V=)S|y7Pvj#R&F}%1SjS9Re z3xB&}55?qs)$*=FYLM?%Hn+w3=CX>#atTFrq6XNOTdT=wm02#=w6x!b=E!>PcWCSG zzl6Kvz4b*SzQ*tyvBs?@@yCF?S+z+nCS}BZo#xFby09E+RFU%jaspCU&;G`m;?p00 z`v1GxFT}Qji1Udc=Nf<)L%^8i6wm%0JhDfX<13-1Q@Og9VlD;SXgHd_iDm#1nAfJ#a3rfbtte7;ZEuE%pBB~T~Re|kstoMv3(>c+I1t--v0nU%# zTZTHGdI$t{^-4>?(SbP4vwrzEIOm#V1hexxC45EFA53_X#VgAT(goNAR_CWyvG@jY zC6#$gX9~y*SBZ+gzPu$Pd?Ie7heX0pJDkdA{Y2uaOad5>i$P+t0e#OucE`CfMKlh3 zSdYZC$);Qi*L^gdPg)=koNFgOW)&6CD3@R)hS&zGfrHk>Vvb&A`8LjUdrcFa zWK-+9$iQ3KNNRzy8a*bQ8KQ1ElPr8<;}hq(XMwCsc|-`tm2-^_6?R{_HIRDSH$YIJ z6^#iJV;`h*8pEs?4c|9*Cq_!b_KNo0WqeW*!yMbaxZjFr-x5fLydRaRvZU-ZZvX%Q DyOgyl literal 0 HcmV?d00001 diff --git a/chat2db-client/src/assets/img/add.svg b/chat2db-client/src/assets/img/add.svg new file mode 100644 index 000000000..edc2a864d --- /dev/null +++ b/chat2db-client/src/assets/img/add.svg @@ -0,0 +1,15 @@ + + + 编组 + + + + + + + + + + + + \ No newline at end of file diff --git a/chat2db-client/src/assets/img/ai.svg b/chat2db-client/src/assets/img/ai.svg new file mode 100644 index 000000000..c8b0593d3 --- /dev/null +++ b/chat2db-client/src/assets/img/ai.svg @@ -0,0 +1,30 @@ + + + 编组备份 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/chat2db-client/src/assets/img/connection.svg b/chat2db-client/src/assets/img/connection.svg new file mode 100644 index 000000000..83892ea97 --- /dev/null +++ b/chat2db-client/src/assets/img/connection.svg @@ -0,0 +1,17 @@ + + + 编组 4 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/chat2db-client/src/assets/img/databaseImg/h2.png b/chat2db-client/src/assets/img/databaseImg/h2.png new file mode 100644 index 0000000000000000000000000000000000000000..764bfdceb40ef2de60cef8e8dd2b0903b5bca796 GIT binary patch literal 12968 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 Ef>WL}Q2+n{ literal 0 HcmV?d00001 diff --git a/chat2db-client/src/assets/img/databaseImg/other.png b/chat2db-client/src/assets/img/databaseImg/other.png new file mode 100644 index 0000000000000000000000000000000000000000..8ffddd4648a8bb2246726a5baf60e0a1090456b9 GIT binary patch literal 844 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 a)c*rcJo8_(`HGtW0000 + + 编组 27备份 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/chat2db-client/src/assets/img/login-bg.svg b/chat2db-client/src/assets/img/login-bg.svg new file mode 100644 index 000000000..35d74bda8 --- /dev/null +++ b/chat2db-client/src/assets/img/login-bg.svg @@ -0,0 +1,3701 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chat2db-client/src/assets/img/theme-auto.png b/chat2db-client/src/assets/img/theme-auto.png new file mode 100644 index 0000000000000000000000000000000000000000..7ad4721655bdd212020de0f933bb99f98ff01449 GIT binary patch literal 3484 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