diff --git a/config/routes.js b/config/routes.js index 647847a7d..24fbe6a4e 100644 --- a/config/routes.js +++ b/config/routes.js @@ -25,9 +25,12 @@ module.exports = [ { path: '/console', component: '@/page/Console' }, { path: '/project', component: '@/page/Project/Project', spmBPos: 'b64002' }, { path: '/project/:id/:page', component: '@/page/Project', spmBPos: 'b64003' }, + { path: '/project/:id/schedule/create', component: '@/page/ScheduleCreatePage' }, { path: '/datasource', component: '@/page/Datasource/Datasource', spmBPos: 'b64004' }, { path: '/datasource/:id/:page', component: '@/page/Datasource', spmBPos: 'b64005' }, { path: '/task', component: '@/page/Task', spmBPos: 'b64006' }, + { path: '/schedule', component: '@/page/Schedule' }, + { path: '/schedule/create', component: '@/page/ScheduleCreatePage' }, { path: '/auth/:page', component: '@/page/Auth', spmBPos: 'b64007' }, { path: '/secure/:page', component: '@/page/Secure', spmBPos: 'b64008' }, { path: '/externalIntegration/:page', component: '@/page/ExternalIntegration', spmBPos: 'b64009' }, diff --git a/hidden.yaml b/hidden.yaml index ce274d70f..4453080c5 100644 --- a/hidden.yaml +++ b/hidden.yaml @@ -29,6 +29,9 @@ importers: electron-log: specifier: ~4.2.4 version: 4.2.4 + myers-diff: + specifier: ^2.1.0 + version: 2.1.0 qs: specifier: ^6.10.1 version: 6.14.0 @@ -52,17 +55,17 @@ importers: specifier: ^3.2.2 version: 3.2.2(react@17.0.2) '@oceanbase-odc/monaco-plugin-ob': - specifier: ~1.5.3 - version: 1.5.3(monaco-editor@0.36.1) + specifier: ~1.6.4 + version: 1.6.4(monaco-editor@0.36.1) '@oceanbase-odc/ob-intl-cli': - specifier: ^2.2.0 - version: 2.2.0(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2) + specifier: ^2.2.1 + version: 2.2.1(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2) '@oceanbase-odc/ob-parser-js': - specifier: ^3.1.2 - version: 3.1.2 + specifier: ^3.2.1 + version: 3.2.1 '@oceanbase-odc/ob-react-data-grid': - specifier: ^4.0.0 - version: 4.0.0(antd@5.26.1(date-fns@2.30.0)(luxon@3.6.1)(moment@2.30.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lodash@4.17.21)(react-dnd-html5-backend@11.1.3)(react-dnd@11.1.3(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) + specifier: ^4.1.0 + version: 4.1.1(antd@5.26.1(date-fns@2.30.0)(luxon@3.6.1)(moment@2.30.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lodash@4.17.21)(react-dnd-html5-backend@11.1.3)(react-dnd@11.1.3(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@sentry/react': specifier: ^7.88.0 version: 7.120.3(react@17.0.2) @@ -210,6 +213,9 @@ importers: loglevel: specifier: ^1.8.0 version: 1.9.2 + lottie-react: + specifier: ^2.4.0 + version: 2.4.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2) markdown-it: specifier: ^13.0.1 version: 13.0.2 @@ -2046,20 +2052,20 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This functionality has been moved to @npmcli/fs - '@oceanbase-odc/monaco-plugin-ob@1.5.3': - resolution: {integrity: sha512-Wbc/K7rMp3AWxEwgSgKhd5ix3+vCqCfXTuBLx1IbyzzGmwLRASVymEtzSlso7y0PfdgHVlI51gPdR775nUurAw==} + '@oceanbase-odc/monaco-plugin-ob@1.6.4': + resolution: {integrity: sha512-p+dS4B1zvdRCuNpoC6VfYGW/SOFhKHvn+Mu9+oJH46CswZpgkazBDQDYGEXz5SGLGxLKLRCi0EFLNlzVfTJApw==} peerDependencies: monaco-editor: ~0.38.0 - '@oceanbase-odc/ob-intl-cli@2.2.0': - resolution: {integrity: sha512-qCJriKatOEwHICu9sGM5b7PYYtWxtVtB1V+P/ZBTwACXQHhQEgFlBCnt9CDAoMIOM9iDEeFvV13VSWt/kGW8ig==} + '@oceanbase-odc/ob-intl-cli@2.2.1': + resolution: {integrity: sha512-x0TxoxQVC7xk5w1Syu5I1RscNiwoPv/uIMLbCIAxhDFqB2lyc8uzb+6MNMij61wh/DisASrpLbEwwFZqSTfb4w==} hasBin: true - '@oceanbase-odc/ob-parser-js@3.1.2': - resolution: {integrity: sha512-BWtccGg7zR4q0Rc7J+byT4kyOVKtK1993oMW2myGH8pBSDRiagAZMjm5Af2tZUVbJs67UxV52SVDDnJ097NToA==} + '@oceanbase-odc/ob-parser-js@3.2.1': + resolution: {integrity: sha512-GFfI2SQ8CYl0REtfeDb4/qBePkLIjiflPIF52+r3vPmSj5Kkc/uebuwFyUQLCcmBq7pyeoZAVnrAOYO7p535Mg==} - '@oceanbase-odc/ob-react-data-grid@4.0.0': - resolution: {integrity: sha512-CYmeqa+Ocpwt8PpzEiihgT0NS3bDz7futJT5QPUczZJG/rtfOLKpnbZ/CacMDAKRQob3Sdo6dSB0yAk0nfa3BA==} + '@oceanbase-odc/ob-react-data-grid@4.1.1': + resolution: {integrity: sha512-2FvEVJu8Y5OgRv68Pj+DziaKSVReI/SsU7TtQXeF27H8d7ViAe/u07f/cdHkHFhnzVXt9sEG4hg+zVzn8ZKZlQ==} engines: {node: '>=12.0.0'} peerDependencies: antd: ^5.0.0 @@ -6755,6 +6761,15 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lottie-react@2.4.1: + resolution: {integrity: sha512-LQrH7jlkigIIv++wIyrOYFLHSKQpEY4zehPicL9bQsrt1rnoKRYCYgpCUe5maqylNtacy58/sQDZTkwMcTRxZw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lottie-web@5.13.0: + resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==} + lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} @@ -7090,6 +7105,10 @@ packages: multimap@1.1.0: resolution: {integrity: sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==} + myers-diff@2.1.0: + resolution: {integrity: sha512-6s/caiGUb5vNH9kq5HPw9t9OzAq6hTJ5V5N5Damd/npcp+stLg2LxQIcgJa9o51qbfTVgfth6/yVCoJvZY85BQ==} + engines: {node: '>=10'} + mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -10879,7 +10898,7 @@ snapshots: '@babel/helper-annotate-as-pure@7.27.3': dependencies: - '@babel/types': 7.27.6 + '@babel/types': 7.28.2 '@babel/helper-compilation-targets@7.27.2': dependencies: @@ -10937,15 +10956,15 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1': dependencies: - '@babel/traverse': 7.27.4 - '@babel/types': 7.27.6 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.27.4 - '@babel/types': 7.27.6 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -10987,7 +11006,7 @@ snapshots: '@babel/helper-optimise-call-expression@7.27.1': dependencies: - '@babel/types': 7.27.6 + '@babel/types': 7.28.2 '@babel/helper-plugin-utils@7.27.1': {} @@ -10996,7 +11015,7 @@ snapshots: '@babel/core': 7.27.4 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-wrap-function': 7.27.1 - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color @@ -11005,7 +11024,7 @@ snapshots: '@babel/core': 7.27.4 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color @@ -11014,7 +11033,7 @@ snapshots: '@babel/core': 7.28.3 '@babel/helper-member-expression-to-functions': 7.27.1 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.27.4 + '@babel/traverse': 7.28.3 transitivePeerDependencies: - supports-color @@ -11027,8 +11046,8 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.27.1': dependencies: - '@babel/traverse': 7.27.4 - '@babel/types': 7.27.6 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -11041,8 +11060,8 @@ snapshots: '@babel/helper-wrap-function@7.27.1': dependencies: '@babel/template': 7.27.2 - '@babel/traverse': 7.27.4 - '@babel/types': 7.27.6 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 transitivePeerDependencies: - supports-color @@ -12640,14 +12659,14 @@ snapshots: mkdirp: 1.0.4 rimraf: 3.0.2 - '@oceanbase-odc/monaco-plugin-ob@1.5.3(monaco-editor@0.36.1)': + '@oceanbase-odc/monaco-plugin-ob@1.6.4(monaco-editor@0.36.1)': dependencies: - '@oceanbase-odc/ob-parser-js': 3.1.2 + '@oceanbase-odc/ob-parser-js': 3.2.1 antlr4: 4.8.0 comlink: 4.4.2 monaco-editor: 0.36.1 - '@oceanbase-odc/ob-intl-cli@2.2.0(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2)': + '@oceanbase-odc/ob-intl-cli@2.2.1(chokidar@3.6.0)(encoding@0.1.13)(prettier@2.8.8)(typescript@5.9.2)': dependencies: '@babel/core': 7.28.3 '@babel/generator': 7.28.3 @@ -12688,12 +12707,12 @@ snapshots: - svelte-eslint-parser - typescript - '@oceanbase-odc/ob-parser-js@3.1.2': + '@oceanbase-odc/ob-parser-js@3.2.1': dependencies: antlr4: 4.8.0 lodash: 4.17.21 - '@oceanbase-odc/ob-react-data-grid@4.0.0(antd@5.26.1(date-fns@2.30.0)(luxon@3.6.1)(moment@2.30.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lodash@4.17.21)(react-dnd-html5-backend@11.1.3)(react-dnd@11.1.3(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': + '@oceanbase-odc/ob-react-data-grid@4.1.1(antd@5.26.1(date-fns@2.30.0)(luxon@3.6.1)(moment@2.30.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(lodash@4.17.21)(react-dnd-html5-backend@11.1.3)(react-dnd@11.1.3(react-dom@17.0.2(react@17.0.2))(react@17.0.2))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: '@juggle/resize-observer': 3.4.0 antd: 5.26.1(date-fns@2.30.0)(luxon@3.6.1)(moment@2.30.1)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -18903,6 +18922,14 @@ snapshots: dependencies: js-tokens: 4.0.0 + lottie-react@2.4.1(react-dom@17.0.2(react@17.0.2))(react@17.0.2): + dependencies: + lottie-web: 5.13.0 + react: 17.0.2 + react-dom: 17.0.2(react@17.0.2) + + lottie-web@5.13.0: {} + lower-case@2.0.2: dependencies: tslib: 2.8.1 @@ -19273,6 +19300,8 @@ snapshots: multimap@1.1.0: {} + myers-diff@2.1.0: {} + mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -20137,7 +20166,7 @@ snapshots: lodash: 4.17.21 postcss: 8.5.6 - postcss-syntax@0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.6))(postcss@7.0.39))(postcss-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39): + postcss-syntax@0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39): dependencies: postcss: 7.0.39 optionalDependencies: @@ -22326,7 +22355,7 @@ snapshots: postcss-sass: 0.4.4 postcss-scss: 2.1.1 postcss-selector-parser: 6.1.2 - postcss-syntax: 0.36.2(postcss-html@0.36.0(postcss-syntax@0.36.2(postcss@8.5.6))(postcss@7.0.39))(postcss-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39) + postcss-syntax: 0.36.2(postcss-html@0.36.0)(postcss-less@3.1.4)(postcss-scss@2.1.1)(postcss@7.0.39) postcss-value-parser: 4.2.0 resolve-from: 5.0.0 slash: 3.0.0 diff --git a/package.json b/package.json index eeca0d74f..13dad867a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "odc", - "version": "4.4.0", + "version": "4.4.1", "private": true, "description": "企业级数据开发平台", "homepage": "https://www.oceanbase.com/", @@ -67,6 +67,7 @@ "compare-versions": "^3.6.0", "detect-port": "~1.3.0", "electron-log": "~4.2.4", + "myers-diff": "^2.1.0", "qs": "^6.10.1", "request": "^2.88.0", "tree-kill": "^1.2.1" @@ -76,10 +77,10 @@ "@dnd-kit/core": "^6.1.0", "@dnd-kit/sortable": "^8.0.0", "@dnd-kit/utilities": "^3.2.2", - "@oceanbase-odc/monaco-plugin-ob": "~1.5.3", - "@oceanbase-odc/ob-intl-cli": "^2.2.0", - "@oceanbase-odc/ob-parser-js": "^3.1.2", - "@oceanbase-odc/ob-react-data-grid": "^4.0.0", + "@oceanbase-odc/monaco-plugin-ob": "~1.6.4", + "@oceanbase-odc/ob-intl-cli": "^2.2.1", + "@oceanbase-odc/ob-parser-js": "^3.2.1", + "@oceanbase-odc/ob-react-data-grid": "^4.1.0", "@sentry/react": "^7.88.0", "@testing-library/react": "^11.2.2", "@types/antlr4": "~4.7.2", @@ -129,6 +130,7 @@ "lint-staged": "^10.0.7", "lodash": "^4.17.10", "loglevel": "^1.8.0", + "lottie-react": "^2.4.0", "markdown-it": "^13.0.1", "memoize-one": "^4.0.0", "mobx": "^5.9.4", diff --git a/scripts/client/winsign.js b/scripts/client/winsign.js index 0039f3b3d..559ce71db 100644 --- a/scripts/client/winsign.js +++ b/scripts/client/winsign.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + exports.default = async function(configuration) { // do not include passwords or other sensitive data in the file // rather create environment variables with sensitive data diff --git a/scripts/must.js b/scripts/must.js index 0354ea8ba..dc00d9d2c 100644 --- a/scripts/must.js +++ b/scripts/must.js @@ -26,7 +26,7 @@ const outputPath = path.join(localePath, './must/strings'); const exclude = 'src/main'; function matchText(text, path) { - const isConsoleLog = /^console\.log\(/gi.test(path?.parentPath?.toString()); + const isConsoleLog = /^console\.\w+\(/gi.test(path?.parentPath?.toString()); let isFormattedMessage = false; // 识别 标签的文字层级 try { diff --git a/scripts/rename.js b/scripts/rename.js index 1570439ce..51e9f5e42 100644 --- a/scripts/rename.js +++ b/scripts/rename.js @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + /** * 把假的文件先删除,然后创建一个link到hidden.yaml文件 */ diff --git a/src/common/datasource/doris/index.tsx b/src/common/datasource/doris/index.tsx index e46222f00..341df595e 100644 --- a/src/common/datasource/doris/index.tsx +++ b/src/common/datasource/doris/index.tsx @@ -18,6 +18,7 @@ import { ConnectType, TaskType } from '@/d.ts'; import { IDataSourceModeConfig } from '../interface'; import MySQLColumnExtra from '../oceanbase/MySQLColumnExtra'; import { haveOCP } from '@/util/env'; +import { ScheduleType } from '@/d.ts/schedule'; const tableConfig = { enableTableCharsetsAndCollations: true, @@ -69,12 +70,12 @@ const items: Record = { features: { task: [ TaskType.ASYNC, - TaskType.SQL_PLAN, TaskType.IMPORT, TaskType.EXPORT, TaskType.EXPORT_RESULT_SET, TaskType.MULTIPLE_ASYNC, ], + schedule: [ScheduleType.SQL_PLAN], obclient: true, recycleBin: false, sessionManage: true, diff --git a/src/common/datasource/fileSystem/index.tsx b/src/common/datasource/fileSystem/index.tsx index 76163a827..abfc671b8 100644 --- a/src/common/datasource/fileSystem/index.tsx +++ b/src/common/datasource/fileSystem/index.tsx @@ -1,6 +1,23 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { ConnectType, TaskType } from '@/d.ts'; import { IDataSourceModeConfig } from '../interface'; import { haveOCP } from '@/util/env'; +import { ScheduleType } from '@/d.ts/schedule'; const CloudStorageConfig: IDataSourceModeConfig = { isFileSystem: true, @@ -16,7 +33,8 @@ const CloudStorageConfig: IDataSourceModeConfig = { disableExtraConfig: true, }, features: { - task: [TaskType.DATA_ARCHIVE], + task: [], + schedule: [ScheduleType.DATA_ARCHIVE], sqlconsole: false, obclient: false, recycleBin: false, diff --git a/src/common/datasource/interface.ts b/src/common/datasource/interface.ts index e4445bbe4..74629b5de 100644 --- a/src/common/datasource/interface.ts +++ b/src/common/datasource/interface.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { ConnectionMode, TaskType } from '@/d.ts'; +import { ConnectionMode, ConnectType, TaskType } from '@/d.ts'; import { TableForeignConstraintOnDeleteType } from '@/d.ts/table'; import { TableColumn } from '@/page/Workspace/components/CreateTable/interface'; +import { ScheduleType } from '@/d.ts/schedule'; export type columnExtraComponent = React.FC<{ column: TableColumn; @@ -132,7 +133,12 @@ export interface IDataSourceModeConfig { disableExtraConfig?: boolean; }; features: { + scheduleConfig?: { + // 归档时支持的目标端类型 + allowTargetConnectTypeByDataArchive?: ConnectType[]; + }; task: TaskType[]; + schedule: ScheduleType[]; allTask?: boolean; obclient?: boolean; recycleBin?: boolean; diff --git a/src/common/datasource/mysql/index.tsx b/src/common/datasource/mysql/index.tsx index 87735b71e..948dc33e3 100644 --- a/src/common/datasource/mysql/index.tsx +++ b/src/common/datasource/mysql/index.tsx @@ -18,6 +18,7 @@ import { ConnectType, TaskType } from '@/d.ts'; import { haveOCP } from '@/util/env'; import { IDataSourceModeConfig } from '../interface'; import MySQLColumnExtra from '../oceanbase/MySQLColumnExtra'; +import { ScheduleType } from '@/d.ts/schedule'; const tableConfig = { enableTableCharsetsAndCollations: true, @@ -54,6 +55,18 @@ const procedureConfig: IDataSourceModeConfig['schema']['proc'] = { deterministic: true, }; +const scheduleConfig: IDataSourceModeConfig['features']['scheduleConfig'] = { + allowTargetConnectTypeByDataArchive: [ + ConnectType.COS, + ConnectType.OBS, + ConnectType.S3A, + ConnectType.OSS, + ConnectType.OB_MYSQL, + ConnectType.CLOUD_OB_MYSQL, + ConnectType.MYSQL, + ], +}; + const items: Record = { [ConnectType.MYSQL]: { connection: { @@ -70,9 +83,6 @@ const items: Record = { task: [ TaskType.ASYNC, TaskType.DATAMOCK, - TaskType.SQL_PLAN, - TaskType.DATA_ARCHIVE, - TaskType.DATA_DELETE, TaskType.IMPORT, TaskType.EXPORT, TaskType.EXPORT_RESULT_SET, @@ -80,6 +90,8 @@ const items: Record = { TaskType.MULTIPLE_ASYNC, TaskType.LOGICAL_DATABASE_CHANGE, ], + schedule: [ScheduleType.SQL_PLAN, ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE], + scheduleConfig, obclient: true, recycleBin: false, plRun: true, diff --git a/src/common/datasource/oceanbase/MySQLColumnExtra.tsx b/src/common/datasource/oceanbase/MySQLColumnExtra.tsx index e8c8529ac..813104534 100644 --- a/src/common/datasource/oceanbase/MySQLColumnExtra.tsx +++ b/src/common/datasource/oceanbase/MySQLColumnExtra.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { dataTypesIns } from '@/util/dataType'; +import { dataTypesIns } from '@/util/database/dataType'; import { columnExtraComponent } from '../interface'; import Character from '@/page/Workspace/components/CreateTable/Columns/ColumnExtraInfo/Character'; import DataSync from '@/page/Workspace/components/CreateTable/Columns/ColumnExtraInfo/DateSync'; diff --git a/src/common/datasource/oceanbase/OracleColumnExtra.tsx b/src/common/datasource/oceanbase/OracleColumnExtra.tsx index e8ded7417..81e3fa1b3 100644 --- a/src/common/datasource/oceanbase/OracleColumnExtra.tsx +++ b/src/common/datasource/oceanbase/OracleColumnExtra.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { dataTypesIns } from '@/util/dataType'; +import { dataTypesIns } from '@/util/database/dataType'; import { columnExtraComponent } from '../interface'; import DefaultValue from '@/page/Workspace/components/CreateTable/Columns/ColumnExtraInfo/DefaultValue'; import Precision from '@/page/Workspace/components/CreateTable/Columns/ColumnExtraInfo/Precision'; diff --git a/src/common/datasource/oceanbase/obmysql.ts b/src/common/datasource/oceanbase/obmysql.ts index 8c3338386..3197e59c8 100644 --- a/src/common/datasource/oceanbase/obmysql.ts +++ b/src/common/datasource/oceanbase/obmysql.ts @@ -18,6 +18,7 @@ import { ConnectType, TaskType } from '@/d.ts'; import { IDataSourceModeConfig } from '../interface'; import MySQLColumnExtra from './MySQLColumnExtra'; import { haveOCP } from '@/util/env'; +import { ScheduleType } from '@/d.ts/schedule'; const tableConfig = { enableTableCharsetsAndCollations: true, @@ -56,6 +57,18 @@ const procedureConfig: IDataSourceModeConfig['schema']['proc'] = { deterministic: true, }; +const scheduleConfig: IDataSourceModeConfig['features']['scheduleConfig'] = { + allowTargetConnectTypeByDataArchive: [ + ConnectType.OB_MYSQL, + ConnectType.CLOUD_OB_MYSQL, + ConnectType.MYSQL, + ConnectType.COS, + ConnectType.OBS, + ConnectType.S3A, + ConnectType.OSS, + ], +}; + const items: Record< ConnectType.OB_MYSQL | ConnectType.CLOUD_OB_MYSQL | ConnectType.ODP_SHARDING_OB_MYSQL, IDataSourceModeConfig @@ -71,7 +84,9 @@ const items: Record< ssl: true, }, features: { + scheduleConfig, task: Object.values(TaskType), + schedule: Object.values(ScheduleType), obclient: true, recycleBin: true, sqlExplain: true, @@ -111,7 +126,9 @@ const items: Record< unionUser: true, }, features: { + scheduleConfig, task: Object.values(TaskType)?.filter((i) => ![TaskType.ONLINE_SCHEMA_CHANGE]?.includes?.(i)), + schedule: Object.values(ScheduleType), obclient: true, recycleBin: true, sessionManage: true, @@ -152,7 +169,8 @@ const items: Record< unionUser: true, }, features: { - task: [TaskType.ASYNC, TaskType.SQL_PLAN, TaskType.MULTIPLE_ASYNC], + task: [TaskType.ASYNC, TaskType.MULTIPLE_ASYNC], + schedule: [ScheduleType.SQL_PLAN], obclient: false, recycleBin: false, sessionManage: true, diff --git a/src/common/datasource/oceanbase/oboracle.ts b/src/common/datasource/oceanbase/oboracle.ts index 3ec8a91ed..a1d9d8084 100644 --- a/src/common/datasource/oceanbase/oboracle.ts +++ b/src/common/datasource/oceanbase/oboracle.ts @@ -19,6 +19,7 @@ import { TableForeignConstraintOnDeleteType } from '@/d.ts/table'; import { haveOCP } from '@/util/env'; import { IDataSourceModeConfig } from '../interface'; import OracleColumnExtra from './OracleColumnExtra'; +import { ScheduleType } from '@/d.ts/schedule'; const oracleTableConfig = { constraintEnableConfigurable: true, @@ -48,6 +49,18 @@ const functionConfig: IDataSourceModeConfig['schema']['func'] = { params: ['paramName', 'paramMode', 'dataType', 'defaultValue'], }; +const scheduleConfig: IDataSourceModeConfig['features']['scheduleConfig'] = { + allowTargetConnectTypeByDataArchive: [ + ConnectType.COS, + ConnectType.OBS, + ConnectType.S3A, + ConnectType.OSS, + ConnectType.OB_ORACLE, + ConnectType.CLOUD_OB_ORACLE, + ConnectType.ORACLE, + ], +}; + const items: Record = { [ConnectType.OB_ORACLE]: { priority: 99, @@ -60,6 +73,7 @@ const items: Record ![ @@ -68,6 +82,7 @@ const items: Record ![TaskType.SHADOW, TaskType.LOGICAL_DATABASE_CHANGE].includes(type), ), + schedule: Object.values(ScheduleType), + scheduleConfig, obclient: true, recycleBin: true, sqlExplain: true, diff --git a/src/common/datasource/oracle/index.tsx b/src/common/datasource/oracle/index.tsx index d286db645..3c8e84e9b 100644 --- a/src/common/datasource/oracle/index.tsx +++ b/src/common/datasource/oracle/index.tsx @@ -19,6 +19,7 @@ import { TableForeignConstraintOnDeleteType } from '@/d.ts/table'; import { haveOCP } from '@/util/env'; import { IDataSourceModeConfig } from '../interface'; import OracleColumnExtra from '../oceanbase/OracleColumnExtra'; +import { ScheduleType } from '@/d.ts/schedule'; const oracleTableConfig = { constraintEnableConfigurable: true, @@ -48,6 +49,18 @@ const functionConfig: IDataSourceModeConfig['schema']['func'] = { params: ['paramName', 'paramMode', 'dataType', 'defaultValue'], }; +const scheduleConfig: IDataSourceModeConfig['features']['scheduleConfig'] = { + allowTargetConnectTypeByDataArchive: [ + ConnectType.COS, + ConnectType.OBS, + ConnectType.S3A, + ConnectType.OSS, + ConnectType.OB_ORACLE, + ConnectType.CLOUD_OB_ORACLE, + ConnectType.ORACLE, + ], +}; + const items: Record = { [ConnectType.ORACLE]: { priority: 2, @@ -64,16 +77,15 @@ const items: Record = { disableURLParse: true, }, features: { + scheduleConfig, task: [ TaskType.IMPORT, TaskType.EXPORT, TaskType.EXPORT_RESULT_SET, - TaskType.SQL_PLAN, TaskType.ASYNC, - TaskType.DATA_DELETE, - TaskType.DATA_ARCHIVE, TaskType.MULTIPLE_ASYNC, ], + schedule: [ScheduleType.SQL_PLAN, ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE], obclient: false, recycleBin: false, sqlExplain: false, diff --git a/src/common/datasource/pg/index.tsx b/src/common/datasource/pg/index.tsx index a68a36009..76ca00241 100644 --- a/src/common/datasource/pg/index.tsx +++ b/src/common/datasource/pg/index.tsx @@ -18,6 +18,7 @@ import { ConnectType, TaskType } from '@/d.ts'; import { IDataSourceModeConfig } from '../interface'; import MySQLColumnExtra from '../oceanbase/MySQLColumnExtra'; import { haveOCP } from '@/util/env'; +import { ScheduleType } from '@/d.ts/schedule'; const tableConfig = { enableTableCharsetsAndCollations: true, @@ -53,7 +54,16 @@ const procedureConfig: IDataSourceModeConfig['schema']['proc'] = { sqlSecurity: true, deterministic: true, }; - +const scheduleConfig: IDataSourceModeConfig['features']['scheduleConfig'] = { + allowTargetConnectTypeByDataArchive: [ + ConnectType.OB_MYSQL, + ConnectType.CLOUD_OB_MYSQL, + ConnectType.COS, + ConnectType.OBS, + ConnectType.S3A, + ConnectType.OSS, + ], +}; const items: Record = { [ConnectType.PG]: { connection: { @@ -67,7 +77,9 @@ const items: Record = { disableURLParse: true, }, features: { - task: [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE], + scheduleConfig, + task: [], + schedule: [ScheduleType.DATA_ARCHIVE, ScheduleType.DATA_DELETE], obclient: false, recycleBin: false, sessionManage: false, diff --git a/src/common/network/ai.ts b/src/common/network/ai.ts new file mode 100644 index 000000000..c6f6a9d2e --- /dev/null +++ b/src/common/network/ai.ts @@ -0,0 +1,181 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { AIQuestionType, ESseEventStatus } from '@/d.ts/ai'; +import login from '@/store/login'; +import notification from '@/util/ui/notification'; +import request from '@/util/request/service'; + +interface IModifySyncProps { + input: string; + fileName: string; + fileContent: string; + databaseId: number; + startPosition?: number; + endPosition?: number; + cursorPosition?: number; + questionType: AIQuestionType; + model: string; + stream?: boolean; + sid: string; +} +/** + * SSE 返回对象 + */ +interface ISSEResult { + close: () => void; + getAccumulatedContent: () => string; +} + +/** + * SSE 选项 + */ +interface ISSEOptions { + headers?: Record; +} + +/** + * @param url - 请求地址(相对路径,会自动拼接 baseURL) + * @param data - POST 请求数据 + * @param options - 请求选项 + * @returns 包含关闭连接和获取累积内容方法的对象 + */ +async function postSSE( + url: string, + data: Record, + options: ISSEOptions = {}, +): Promise { + let accumulatedContent = ''; + let hasShownNotification = false; + let buffer = ''; + + const abortController = new AbortController(); + + try { + await request.post(url, data, { + headers: { + Accept: 'text/event-stream', + ...options.headers, + }, + params: { + ignoreError: true, // 禁用拦截器的自动错误通知,我们手动处理 + }, + signal: abortController.signal, + onDownloadProgress: (progressEvent) => { + const { responseText } = progressEvent.event.target; + const newData = responseText.slice(buffer.length); + buffer += newData; + + // 按照 SSE 格式分割事件(事件之间用 \n\n 分隔) + const events = buffer.split('\n\n').map((item) => { + const data = item.replace(/^data:\s*/, '').trim(); + try { + return data ? JSON.parse(data) : ''; + } catch (error) { + return ''; + } + }); + + // 保留剩余部分到下一次 + buffer = events.pop() || ''; + + // 处理完整的事件 + events.forEach((event) => { + if (event && typeof event === 'object') { + // 根据事件状态处理 + if (event.status === ESseEventStatus.FAILED) { + // 错误处理 + const errMsg = event.errorMessage || event.content || 'SSE processing error'; + if (!hasShownNotification) { + notification.error({ + track: errMsg, + supportRepeat: false, + requestId: event.requestId, + }); + hasShownNotification = true; + } + } else if (event.status === ESseEventStatus.COMPLETED) { + // 任务完成 + if (event.content) { + accumulatedContent += event.content; + } + } else if (event.status === ESseEventStatus.IN_PROGRESS && event.content) { + // 处理中,累积内容 + accumulatedContent += event.content; + } + } + }); + }, + }); + } catch (error) { + // 检查是否为用户主动取消 + if (error?.name === 'AbortError' || error?.name === 'CanceledError') { + // 用户主动取消,不显示错误 + console.log('SSE request cancelled'); + } else { + console.error('SSE Error:', error); + // 如果还没有显示过通知,则显示 + if (!hasShownNotification) { + const errMsg = error?.error?.message || 'Network error occurred'; + notification.error({ + track: errMsg, + supportRepeat: false, + requestId: error?.response?.data?.requestId, + }); + } + } + } + + return { + close: () => abortController.abort(), + getAccumulatedContent: () => accumulatedContent, + }; +} + +export async function modifySync({ + input, + fileName, + fileContent, + databaseId, + startPosition, + endPosition, + questionType, + model, + cursorPosition, + stream = true, + sid, +}: IModifySyncProps): Promise { + if (!model) return; + const connection = await postSSE( + `/api/v2/copilot/chat/completions?currentOrganizationId=${login.organizationId}`, + { + input, + fileName, + fileContent, + databaseId, + startPosition, + endPosition, + cursorPosition, + questionType, + model, + stream, + sid, + }, + ); + + // 返回累积的完整内容 + return connection.getAccumulatedContent(); +} diff --git a/src/util/aliyun.ts b/src/common/network/aliyun.ts similarity index 90% rename from src/util/aliyun.ts rename to src/common/network/aliyun.ts index 8034c3244..f0eea3a0d 100644 --- a/src/util/aliyun.ts +++ b/src/common/network/aliyun.ts @@ -15,11 +15,53 @@ */ import { generateDatabaseSid } from '@/common/network/pathUtil'; -import request from './request'; -import logger from './logger'; +import request from '@/util/request'; +import logger from '@/util/logger'; -// 上传文件到 OSS -// @see https://help.aliyun.com/document_detail/64047.html +/** + * 下载传输任务文件 + * @param taskId 任务ID + * @returns 下载文件坐标 + */ +export async function downloadTransferTaskFile(taskId) { + // 获取下载文件坐标 + const fileInfo = await request.post('/api/v2/cloud/specific/DownloadTransferFile', { + data: { + taskId: taskId, + sid: generateDatabaseSid(), + }, + }); + if (fileInfo.data) { + window.open(fileInfo.data, '_blank'); + } +} + +/** + * 下载异步任务文件 + * @param fileName + */ +export async function downloadAsyncTaskFile(fileName) { + // 获取下载文件坐标 + const fileInfo = await request.post('/api/v2/cloud/specific/DownloadFile', { + data: { + fileName, + sid: generateDatabaseSid(), + }, + }); + if (fileInfo.data) { + window.open(fileInfo.data, '_blank'); + } +} + +/** + * 上传文件到 OSS + * @see https://help.aliyun.com/document_detail/64047.html + * @param file + * @param uploadFileOpenAPIName + * @param sessionId + * @param onProgress + * @returns + */ export async function uploadFileToOSS( file, uploadFileOpenAPIName, @@ -139,31 +181,3 @@ export async function uploadFileToOSS( } return await getResult(); } - -// 下载传输任务文件 -export async function downloadTransferTaskFile(taskId) { - // 获取下载文件坐标 - const fileInfo = await request.post('/api/v2/cloud/specific/DownloadTransferFile', { - data: { - taskId: taskId, - sid: generateDatabaseSid(), - }, - }); - if (fileInfo.data) { - window.open(fileInfo.data, '_blank'); - } -} - -// 下载异步任务文件 -export async function downloadAsyncTaskFile(fileName) { - // 获取下载文件坐标 - const fileInfo = await request.post('/api/v2/cloud/specific/DownloadFile', { - data: { - fileName, - sid: generateDatabaseSid(), - }, - }); - if (fileInfo.data) { - window.open(fileInfo.data, '_blank'); - } -} diff --git a/src/common/network/chat.ts b/src/common/network/chat.ts new file mode 100644 index 000000000..cc5131c35 --- /dev/null +++ b/src/common/network/chat.ts @@ -0,0 +1,99 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import request from '@/util/request'; +import { Chat, ChatConversation, ChatReq } from '@/d.ts/chat'; + +export interface ChatFeedbackReq { + chatId: number; + feedbackResult: 'SATISFIED' | 'UNSATISFIED'; + feedbackContent?: string; +} + +/** + * 创建聊天会话 + */ +export async function createChatConversation(chatReq: ChatReq): Promise { + const ret = await request.post('/api/v2/chat/conversations', { + data: chatReq, + }); + return ret?.data; +} + +/** + * 获取聊天会话列表 + */ +export async function listChatConversations(limit = 10): Promise { + const ret = await request.get(`/api/v2/copilot/chats/conversations?limit=${limit}`); + return ret?.data?.contents?.reverse(); +} + +/** + * 获取指定会话的所有聊天记录 + * @param conversationId + * @returns 聊天记录数组 + */ +export async function getConversationMessages( + conversationId: string, + page = 1, + size = 9999, +): Promise { + const ret = await request.get( + `/api/v2/copilot/chats?conversationId=${conversationId}&page=${page}&size=${size}`, + ); + return ret?.data?.contents?.reverse(); +} + +/** + * 发送聊天消息 + * @param chatReq + */ +export async function sendChatMessage(chatReq: ChatReq): Promise { + const ret = await request.post(`/api/v2/copilot/chats/`, { + data: chatReq, + }); + return ret?.data; +} + +/** + * 提交聊天反馈 + */ +export async function submitChatFeedback( + chatId: number, + feedbackReq: ChatFeedbackReq, +): Promise { + const ret = await request.patch(`/api/v2/copilot/chats/${chatId}/feedback`, { + data: feedbackReq, + }); + return ret?.data; +} + +/** + * 获取聊天输出 + * @param chatId + */ +export async function getChatOutput(chatId: number): Promise { + const ret = await request.get(`/api/v2/copilot/chats/${chatId}/output`); + return ret?.data; +} + +/** + * 终止聊天 + */ +export async function terminateChat(chatId: number): Promise { + const ret = await request.post(`/api/v2/copilot/chats/${chatId}/terminate`); + return ret?.data; +} diff --git a/src/common/network/connection.ts b/src/common/network/connection.ts index 757d015cd..799e59f71 100644 --- a/src/common/network/connection.ts +++ b/src/common/network/connection.ts @@ -380,8 +380,15 @@ export async function getClusterAndTenantList(visibleScope: IConnectionType): Pr return results?.data; } -export async function deleteConnection(cid: string): Promise { - const res = await request.delete(`/api/v2/datasource/datasources/${cid}`); +export async function deleteConnection( + cid: string, + ignoreError: boolean = false, +): Promise { + const res = await request.delete(`/api/v2/datasource/datasources/${cid}`, { + params: { + ignoreError, + }, + }); return res?.data; } diff --git a/src/common/network/database.ts b/src/common/network/database.ts index c2b3f07da..f1496555d 100644 --- a/src/common/network/database.ts +++ b/src/common/network/database.ts @@ -17,17 +17,17 @@ import { DbObjectType, IResponseData, IManagerResourceType, ConnectType } from '@/d.ts'; import { DBType, IDatabase, IDatabaseObject } from '@/d.ts/database'; import sessionManager from '@/store/sessionManager'; -import notification from '@/util/notification'; +import notification from '@/util/ui/notification'; import request from '@/util/request'; -import { getDropSQL } from '@/util/sql'; +import { getDropSQL } from '@/util/data/sql'; import { executeSQL } from './sql'; +import { DatabaseSearchType } from '@/d.ts/database'; -interface listDatabasesParams { +export interface listDatabasesParams { projectId?: number; dataSourceId?: number; page?: number; size?: number; - name?: string; environmentId?: number[]; /** 是否包含未分配项目的数据库 */ containsUnassigned?: boolean; @@ -37,9 +37,8 @@ interface listDatabasesParams { includesDbOwner?: boolean; type?: DBType[]; connectType?: ConnectType[]; - dataSourceName?: string; - clusterName?: string; - tenantName?: string; + fuzzyKeyword?: string; + searchType?: DatabaseSearchType; } export async function listDatabases( @@ -71,6 +70,7 @@ export async function updateDataBase( databaseIds: number[], projectId: number, ownerIds: number[], + ignoreError: boolean = false, ): Promise { const res = await request.post(`/api/v2/database/databases/transfer`, { data: { @@ -78,6 +78,9 @@ export async function updateDataBase( projectId, ownerIds, }, + params: { + ignoreError, + }, }); return res?.data; } diff --git a/src/common/network/databaseChange.ts b/src/common/network/databaseChange.ts index 62feb3576..fced6cc4b 100644 --- a/src/common/network/databaseChange.ts +++ b/src/common/network/databaseChange.ts @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { IResponse, IResponseData } from '@/d.ts'; import request from '@/util/request'; diff --git a/src/common/network/exportAndImport.ts b/src/common/network/exportAndImport.ts index 404c6c644..5da2ee493 100644 --- a/src/common/network/exportAndImport.ts +++ b/src/common/network/exportAndImport.ts @@ -29,7 +29,8 @@ import { } from '@/d.ts'; import odc from '@/plugins/odc'; import request from '@/util/request'; -import { encrypt, stringSeparatorToCRLF } from '@/util/utils'; +import { encrypt } from '@/util/utils'; +import { stringSeparatorToCRLF } from '@/util/data/string'; import { isNil } from 'lodash'; export async function getExportObjects( diff --git a/src/common/network/externalResource.ts b/src/common/network/externalResource.ts new file mode 100644 index 000000000..f4ae403f0 --- /dev/null +++ b/src/common/network/externalResource.ts @@ -0,0 +1,153 @@ +import { formatMessage } from '@/util/intl'; +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import request from '@/util/request'; +import { generateDatabaseSid } from './pathUtil'; +import { ICreateExternalResourceParams, IExternalResource } from '@/d.ts/externalResoruce'; + +/** + * 获取外部资源列表 + */ +export async function getExternalResourceList( + dbName: string, + sessionId: string, +): Promise { + const sid = generateDatabaseSid(dbName, sessionId); + const res = await request.get( + `/api/v2/connect/sessions/sid:${sessionId}/databases/${dbName}/externalResources`, + ); + + return ( + res?.data?.contents?.map((resource: any) => ({ + id: resource.id || resource.name, + name: resource.name, + type: resource.type, + url: resource.url || '', + description: resource.comment || '', + createTime: resource.createTime, + modifyTime: resource.modifyTime, + schemaName: resource.schemaName, + })) || [] + ); +} + +/** + * 加载外部资源详情 + */ +export async function loadExternalResourceDetail( + resourceName: string, + dbName: string, + sessionId: string, +): Promise { + const res = await request.get( + `/api/v2/connect/sessions/sid:${sessionId}/databases/${dbName}/externalResources/${resourceName}`, + { + params: { + schemaName: dbName, + name: resourceName, + }, + }, + ); + + if (res?.data) { + return { + name: res.data.name, + type: res.data.type, + size: res.data.size, + description: res.data.comment, + comment: res.data.comment, + createTime: res.data.createTime, + updateTime: res.data.updateTime, + owner: res.data.owner, + status: res.data.status, + content: res.data.context, + schemaName: res.data.schemaName, + }; + } + + return null; +} + +/** + * 下载外部资源 + */ +export async function downloadExternalResourceFile( + resourceName: string, + dbName: string, + sessionId: string, +): Promise { + try { + await request.get( + `/api/v2/connect/sessions/sid:${sessionId}/databases/${dbName}/externalResources/${resourceName}/download`, + { + params: { + download: true, + }, + }, + ); + return true; + } catch (error) { + console.error('下载外部资源失败:', error); + return false; + } +} + +/** + * 删除外部资源 + */ +export async function removeExternalResource( + resourceName: string, + dbName: string, + sessionId: string, + type: string, +): Promise { + try { + const res = await request.delete( + `/api/v2/connect/sessions/sid:${sessionId}/databases/${dbName}/externalResources/${resourceName}`, + { + params: { + type, + }, + }, + ); + + return res?.successful; + } catch (error) { + console.error('删除外部资源失败:', error); + return false; + } +} + +export async function createExternalResource({ + formData, + sessionId, + databaseName, + resourceName, +}: ICreateExternalResourceParams) { + const response = await request.post( + `/api/v2/connect/sessions/sid:${sessionId}/databases/${databaseName}/externalResources/${resourceName}/upload`, + { + data: formData, + }, + ); + if (!response.data) { + throw new Error( + response?.errMsg || + formatMessage({ id: 'src.common.network.9A0D437C', defaultMessage: '创建失败' }), + ); + } +} diff --git a/src/common/network/index.ts b/src/common/network/index.ts index 0b0ad5772..578dc32b2 100644 --- a/src/common/network/index.ts +++ b/src/common/network/index.ts @@ -15,6 +15,7 @@ */ export * from './exportAndImport'; +export * from './externalResource'; export * from './function'; export * from './procedure'; export * from './script'; diff --git a/src/common/network/largeModel.ts b/src/common/network/largeModel.ts new file mode 100644 index 000000000..c657260ea --- /dev/null +++ b/src/common/network/largeModel.ts @@ -0,0 +1,161 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { + IAIConfig, + IAIConfigPayload, + IModel, + IModelProvider, + IProviderCredential, +} from '@/d.ts/llm'; +import request from '@/util/request'; + +/** + * 获取AI配置 + */ +export async function getAIConfig(): Promise { + const result = await request.get('/api/v2/integration/ai/config'); + return result; +} + +/** + * 更新AI配置 + */ +export async function updateAIConfig(data: IAIConfigPayload): Promise { + const result = await request.post('/api/v2/integration/ai/config', { + data, + }); + return result; +} + +/** + * 获取所有模型供应商列表 + */ +export async function getModelProviders(): Promise { + const result = await request.get('/api/v2/integration/llm/providers'); + return result?.data?.contents; +} + +/** + * 获取指定供应商的模型列表 + */ +export async function getProviderModels(provider: string): Promise { + const result = await request.get(`/api/v2/integration/llm/providers/${provider}/models`); + return result?.data?.contents; +} + +/** + * 创建/配置模型供应商 + */ +export async function postAPIKey(data: { + provider: string; + credential: Record; +}): Promise { + const result = await request.post('/api/v2/integration/llm/providers', { + data, + }); + return result?.data; +} + +/** + * 获取指定供应商的指定模型详情 + */ +export async function getModelDetail(provider: string, modelName: string): Promise { + const result = await request.get( + `/api/v2/integration/llm/providers/${provider}/models/${modelName}`, + ); + return result?.data; +} + +/** + * 为指定供应商创建模型 + */ +export async function createProviderModel( + provider: string, + data: { + model: string; + type: string; + provider: string; + credential: { + dashscope_api_key: string; + }; + }, +): Promise { + const result = await request.post(`/api/v2/integration/llm/providers/${provider}/models`, { + data, + }); + return result?.data; +} + +/** + * 获取指定供应商的凭证信息 + */ +export async function getProviderCredential(provider: string): Promise { + const result = await request.get(`/api/v2/integration/llm/providers/${provider}`); + return result?.data; +} + +/** + * 删除指定供应商的模型 + */ +export async function deleteProviderModel( + provider: string, + data: { model: string; type: string }, +): Promise { + const result = await request.delete(`/api/v2/integration/llm/providers/${provider}/models`, { + data, + }); + return result?.data; +} + +/** + * 启用/禁用指定供应商的模型 + */ +export async function toggleProviderModel( + provider: string, + data: { + model: string; + enabled: boolean; + }, +): Promise { + const { model } = data || {}; + const result = await request.post( + `/api/v2/integration/llm/providers/${provider}/models/${model}/setEnabled`, + { + data, + }, + ); + return result?.data; +} + +/** + * 设置指定供应商的描述/备注 + */ +export async function updateProviderDescription( + provider: string, + data: { + description: string; + }, +): Promise { + const result = await request.post( + `/api/v2/integration/llm/providers/${provider}/setDescription`, + { + data, + }, + ); + + return result?.data; +} diff --git a/src/common/network/logicalDatabase.ts b/src/common/network/logicalDatabase.ts index 8e87b7850..c3b501b65 100644 --- a/src/common/network/logicalDatabase.ts +++ b/src/common/network/logicalDatabase.ts @@ -1,4 +1,26 @@ -import { IResponse, IResponseData } from '@/d.ts'; +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { + ILogicDatabaseChangeExecuteRecord, + IResponse, + IResponseData, + ISqlExecuteResultStatus, + LogicDatabaseChangeExecuteRecordStats, +} from '@/d.ts'; import { PreviewLogicalTableTopologiesErrorEnum } from '@/d.ts/database'; import { ILogicalDatabase, @@ -8,6 +30,8 @@ import { ITopology, } from '@/d.ts/logicalDatabase'; import request from '@/util/request'; +import { omit } from 'lodash'; +import type { IResponseDataWithStats } from '@/common/network/task'; export async function extractLogicalTables(logicalDatabaseId: number) { const res = await request.post( @@ -121,35 +145,55 @@ export async function previewSqls( } /* 查看某个物理库sql执行详情 */ -/* schedule->task(仅有一个task)->physicalDatabases(逻辑库特殊的资源) */ export async function getPhysicalExecuteDetails( - scheduleTaskId: number, + flowInstanceId: number, physicalDatabaseId: number, + statuses?: ISqlExecuteResultStatus[], ): Promise { const res = await request.get( - `/api/v2/logicaldatabase/scheduleTasks/${scheduleTaskId}/physicalDatabases/${physicalDatabaseId}`, + `/api/v2/logicaldatabase/flowTasks/${flowInstanceId}/physicalDatabases/${physicalDatabaseId}`, + { + params: { statuses }, + }, ); return res?.data; } /* 终止某个物理库 SQL 执行 */ export async function stopPhysicalSqlExecute( - scheduleTaskId: number, + flowInstanceId: number, physicalDatabaseId: number, ): Promise { const res = await request.post( - `/api/v2/logicaldatabase/scheduleTasks/${scheduleTaskId}/physicalDatabases/${physicalDatabaseId}/terminateCurrentStatement`, + `/api/v2/logicaldatabase/flowTasks/${flowInstanceId}/physicalDatabases/${physicalDatabaseId}/terminateCurrentStatement`, ); return res?.data; } /* 跳过某个物理库 SQL 执行 */ export async function skipPhysicalSqlExecute( - scheduleTaskId: number, + flowInstanceId: number, physicalDatabaseId: number, ): Promise { const res = await request.post( - `/api/v2/logicaldatabase/scheduleTasks/${scheduleTaskId}/physicalDatabases/${physicalDatabaseId}/skipCurrentStatement`, + `/api/v2/logicaldatabase/flowTasks/${flowInstanceId}/physicalDatabases/${physicalDatabaseId}/skipCurrentStatement`, ); return res?.data; } + +export async function getLogicDatabaseChangeExecuteRecordList(params: { + id: number; + size: number; + page: number; + statuses?: string[]; + databaseKeyword?: string; + datasourceKeyword?: string; +}): Promise< + IResponseDataWithStats +> { + const { id } = params; + const res = await request.get(`api/v2/logicaldatabase/${id}`, { + params: omit(params, 'id'), + }); + return res?.data; +} diff --git a/src/common/network/manager.ts b/src/common/network/manager.ts index ca7d97f61..f817e8a43 100644 --- a/src/common/network/manager.ts +++ b/src/common/network/manager.ts @@ -41,6 +41,7 @@ import { ISSOConfig, ISSOType, } from '@/d.ts'; +import { IAccessKey } from '@/d.ts/openAPI'; import request from '@/util/request'; import { encrypt } from '@/util/utils'; interface IRoleForUpdate extends IManagerRole { @@ -61,9 +62,13 @@ export async function createUser(data: Partial[]): Promise { - const result = await request.delete(`/api/v2/iam/users/${id}`); - return result?.data; +export async function deleteUser(id: number, ignoreError: boolean = false): Promise { + const result = await request.delete(`/api/v2/iam/users/${id}`, { + params: { + ignoreError, + }, + }); + return !!result?.data; } /** @@ -811,3 +816,41 @@ export async function querySecretKey(): Promise { const result = await request.get(`/api/v2/sso/credential`); return result?.data?.certificate || ''; } + +/** + * 获取用户的 AccessKey 列表 + */ +export async function getUserAccessKeys(userId: number): Promise { + const result = await request.get(`/api/v2/iam/users/${userId}/accessKeys`); + return result?.data?.contents || []; +} + +/** + * 创建新的 AccessKey + */ +export async function createAccessKey(userId: number): Promise { + const result = await request.post(`/api/v2/iam/users/${userId}/accessKeys`); + return result?.data; +} + +/** + * 删除 AccessKey + */ +export async function deleteAccessKey(userId: number, accessKey: string): Promise { + const result = await request.delete(`/api/v2/iam/users/${userId}/accessKeys/${accessKey}`); + return result?.data; +} + +/** + * 设置 AccessKey 启用状态 + */ +export async function setAccessKeyEnabled( + userId: number, + accessKey: string, + status: string, +): Promise { + const result = await request.put(`/api/v2/iam/users/${userId}/accessKeys/${accessKey}`, { + data: { status }, + }); + return result?.data; +} diff --git a/src/common/network/materializedView/helper.ts b/src/common/network/materializedView/helper.ts index 53b0a27b9..8d83ca92c 100644 --- a/src/common/network/materializedView/helper.ts +++ b/src/common/network/materializedView/helper.ts @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { TableIndexScope, TableIndexType } from '@/page/Workspace/components/CreateTable/interface'; import { ColumnStoreType } from '@/d.ts/table'; import { convertServerTablePartitionToTablePartition, PartitionLevelEnum } from '../table/helper'; diff --git a/src/common/network/materializedView/index.ts b/src/common/network/materializedView/index.ts index ae610aafb..3120773c6 100644 --- a/src/common/network/materializedView/index.ts +++ b/src/common/network/materializedView/index.ts @@ -1,10 +1,26 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import request from '@/util/request'; -import { encodeObjName } from '@/util/utils'; +import { encodeObjName } from '@/util/data/string'; import { Base64 } from 'js-base64'; import sessionManager from '@/store/sessionManager'; import { convertMaterializedViewToTable, convertCreateMaterializedViewData } from './helper'; import { IMaterializedView, MaterializedViewRecord, RefreshMethod } from '@/d.ts'; -import notification from '@/util/notification'; +import notification from '@/util/ui/notification'; import { formatMessage } from '@/util/intl'; export async function getMaterializedView(params: { diff --git a/src/common/network/pathUtil.ts b/src/common/network/pathUtil.ts index 684aee8f6..4b6d7db14 100644 --- a/src/common/network/pathUtil.ts +++ b/src/common/network/pathUtil.ts @@ -18,7 +18,7 @@ * 后端的API需要的path */ import { ConnectionPropertyType } from '@/d.ts/datasource'; -import { encodeObjName } from '@/util/utils'; +import { encodeObjName } from '@/util/data/string'; export function generateDatabaseSid(databaseName: string = '', sessionId?: string): string { return `sid:${sessionId}:d:${encodeObjName(databaseName)}`; diff --git a/src/common/network/relativeResource.ts b/src/common/network/relativeResource.ts new file mode 100644 index 000000000..eed9d987e --- /dev/null +++ b/src/common/network/relativeResource.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ +import { IResourceDependencyParams, IResourceDependency } from '@/d.ts/relativeResource'; +import request from '@/util/request'; + +/** + * 获取资源依赖信息 + * @param params 查询参数 + * @returns Promise + */ +export async function getResourceDependencies( + params: IResourceDependencyParams, +): Promise { + const result = await request.get('/api/v2/resourceDependency/', { + params, + }); + const data = result?.data || { + scheduleDependencies: [], + scheduleTaskDependencies: [], + flowDependencies: [], + }; + if (result.successful) { + return { + successful: result.successful, + data, + }; + } + return result; +} diff --git a/src/common/network/schedule.ts b/src/common/network/schedule.ts new file mode 100644 index 000000000..f14695648 --- /dev/null +++ b/src/common/network/schedule.ts @@ -0,0 +1,403 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { + ScheduleType, + IScheduleRecord, + ScheduleRecordParameters, + createScheduleRecord, + createSchedueleParameters, + ScheduleStatus, + createDataArchiveParameters, + createDataDeleteParameters, + dmlPreCheckResult, +} from '@/d.ts/schedule'; +import request from '@/util/request'; +import { + IScheduleTaskExecutionDetail, + ScheduleTaskStatus, + SubTaskParameters, +} from '@/d.ts/scheduleTask'; +import { Operation, IResponseData, CommonTaskLogType, ITaskStatParam, IStat } from '@/d.ts'; +import { scheduleTask, IScheduleTaskRecord } from '@/d.ts/scheduleTask'; +import { ScheduleApprovalStatus } from '@/component/Schedule/interface'; +import { omit } from 'lodash'; +import { IScheduleTerminateCmd, IScheduleTerminateResult } from '@/d.ts/importTask'; +export interface ScheduleListParams { + dataSourceName?: string; + dataSourceId?: number[]; + databaseName?: string; + tenantId?: string; + clusterId?: string; + id?: number; + name?: string; + status?: ScheduleStatus[]; + type?: ScheduleType[]; + startTime?: string; + endTime?: string; + creator?: string; + approveStatus?: ScheduleApprovalStatus[]; + projectUniqueIdentifier?: string; + projectIds?: number[]; + triggerStrategy?: 'DAY' | 'WEEK' | 'MONTH' | 'CRON' | 'START_NOW' | 'START_AT'; + page?: number; + size?: number; + sort?: string; + approveByCurrentUser?: boolean; +} + +export interface SubTaskListParams { + dataSourceName?: string; + dataSourceId?: number[]; + databaseName: string; + tenantId?: string; + clusterId?: string; + id: number; + scheduleId: number; + scheduleName: string; + status: ScheduleTaskStatus[]; + scheduleType: ScheduleType[]; + startTime?: string; + creator?: string; + endTime?: string; + projectIds?: number[]; + page?: number; + size?: number; + sort?: string; +} + +/** + * 查询作业列表 + * @param params + * @returns + */ +export const getScheduleList = async ( + params: ScheduleListParams, +): Promise>> => { + const res = await request.get('/api/v2/schedule/schedules', { + params, + }); + return res?.data; +}; + +/** + * 新建作业 + */ +export const createSchedule = async (data: createScheduleRecord) => { + const res = await request.post(`/api/v2/schedule/schedules`, { + data, + }); + return res; +}; + +/** + * 查询作业详情 + */ +export const getScheduleDetail = async ( + id: number, + ignoreError: boolean = false, +): Promise> => { + const res = await request.get(`/api/v2/schedule/schedules/${id}`); + return res?.data; +}; + +/** + * 删除作业 + * @param id + * @returns + */ +export const deleteSchedule = async (id: number, projectId: number) => { + const res = await request.delete(`/api/v2/schedule/schedules/${id}`, { + params: { + projectId, + }, + }); + return res; +}; + +/** + * 更新作业 + * @param data + * @returns + */ +export const updateSchedule = async (data: createScheduleRecord) => { + const { id } = data; + const res = await request.put(`/api/v2/schedule/schedules/${id}`, { data }); + return res; +}; + +/** + * 禁用作业 + */ +export const pauseSchedule = async (id: number) => { + const res = await request.post(`/api/v2/schedule/schedules/${id}/pause`); + return res; +}; + +/** + * 启用作业 + */ +export const resumeSchedule = async (id: number) => { + const res = await request.post(`/api/v2/schedule/schedules/${id}/resume`); + return res; +}; + +/** + * 终止作业 + */ +export const terminateSchedule = async (id: number) => { + const res = await request.post(`/api/v2/schedule/schedules/${id}/terminate`); + return res; +}; + +/** + * 操作详情 + */ +export async function getOperationDetail( + scheduleId: number, + scheduleChangeLogId: number, +): Promise { + const res = await request.get( + `/api/v2/schedule/schedules/${scheduleId}/changes/${scheduleChangeLogId}`, + ); + return res?.data; +} + +/** + * 更新限流配置 + */ +export async function updateLimiterConfig( + taskId: number, + data: { + rowLimit?: number; + dataSizeLimit?: number; + }, +): Promise { + const res = await request.put(`/api/v2/schedule/schedules/${taskId}/dlmRateLimitConfiguration`, { + data, + }); + return !!res?.data; +} + +/** + * 计划任务终止-发起 + */ +export async function batchTerminateScheduleAndTask(data: IScheduleTerminateCmd): Promise { + const res = await request.post(`/api/v2/schedule/schedules/asyncTerminate`, { + data, + }); + return res?.data; +} + +/** + * 作业终止-查看 + */ +export async function getTerminateScheduleResult( + terminateId: string, +): Promise { + const res = await request.get( + `/api/v2/schedule/schedules/asyncTerminateResult?terminateId=${terminateId}`, + ); + return res?.data; +} + +/** + * 作业终止-查看日志 + */ +export async function getTerminateScheduleLog(terminateId: string): Promise { + const res = await request.get( + `/api/v2/schedule/schedules/asyncTerminateLog?terminateId=${terminateId}`, + ); + return res?.data; +} +/** + * 查询周期任务状态 + */ +export async function getScheduleStat( + params: ITaskStatParam, +): Promise> { + const res = await request.get('/api/v2/collaboration/landingPage/scheduleStat', { + params, + }); + return res?.data; +} + +/** + * 查询作业子任务日志 + */ +export async function getCycleTaskLog( + scheduleId: number, + taskId: number, + logType: CommonTaskLogType, +): Promise { + const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/log`, { + params: { + logType, + }, + }); + return res?.data; +} + +/** + * 获取作业子任务全量日志下载URL + */ +export async function getDownloadUrl(scheduleId: number, taskId: number) { + const res = await request.post( + `/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/log/getDownloadUrl`, + ); + return res?.data; +} + +/** + * 获取执行视角下的子任务列表 + */ +export const getSubTaskList = async ( + params, +): Promise>> => { + const res = await request.get(`api/v2/schedule/tasks`, { + params, + }); + return res?.data; +}; + +/** + * 获取子任务列表 + */ +export const listScheduleTasks = async (params: { + scheduleId: number; + size: number; + page: number; +}): Promise>> => { + const { scheduleId, size, page } = params; + const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/tasks`, { + params: omit(params, 'scheduleId'), + }); + return res?.data; +}; + +/** + * 获取子任务详情 + */ +export const detailScheduleTask = async ( + scheduleId: number, + taskId: number, +): Promise> => { + const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}`); + return res?.data; +}; + +/** + * 获取操作记录 + */ +export const listChangeLog = async (id: number): Promise> => { + const res = await request.get(`/api/v2/schedule/schedules/${id}/changes`); + return res?.data; +}; + +/** + * 执行作业子任务 + * @param scheduleId + * @param taskId + * @returns + */ +export const startScheduleTask = async (scheduleId: number, taskId: number) => { + const res = await request.put(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/start`); + return res; +}; + +/** + * 回滚作业子任务 + * @param scheduleId + * @param taskId + * @returns + */ +export const rollbackScheduleTask = async (scheduleId: number, taskId: number) => { + const res = await request.post( + `/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/rollback`, + ); + return res; +}; + +/** + * 获取作业子任务日志 + * @param scheduleId + * @param taskId + * @param logType + * @returns + */ +export const getScheduleTaskLog = async (scheduleId: number, taskId: number, logType: string) => { + const res = await request.get( + `/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/executions/latest/log`, + { + params: { + logType, + }, + }, + ); + return res?.data; +}; + +/** + * 终止作业子任务(数据归档) + * @param scheduleId + * @param taskId + * @returns + */ +export const stopScheduleTask = async (scheduleId: number, taskId: number) => { + const res = await request.post( + `/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/executions/latest/stop`, + ); + return res; +}; + +/** + * 恢复作业子任务 + * @param scheduleId + * @param taskId + * @returns + */ +export const resumeScheduleTask = async (scheduleId: number, taskId: number) => { + const res = await request.post(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/resume`); + return res; +}; + +/** + * 暂停作业子任务 + * @param scheduleId + * @param taskId + * @returns + */ +export const pauseScheduleTask = async (scheduleId: number, taskId: number) => { + const res = await request.post(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/pause`); + return res; +}; + +/** + * dml预检查 + * createScheduleReq 和 updateScheduleReq 必须且只能填写其中一个 + * @param params + * @returns + */ +export const DmlPreCheck = async (params: { + scheduleId: number; + createScheduleReq: createScheduleRecord; + updateScheduleReq: createScheduleRecord; +}): Promise => { + const res = await request.post(`/api/v2/schedule/schedules/check`, { + data: params, + }); + return res?.data; +}; diff --git a/src/common/network/script.ts b/src/common/network/script.ts index 0957e6d29..bd7623ec2 100644 --- a/src/common/network/script.ts +++ b/src/common/network/script.ts @@ -16,10 +16,10 @@ import { IScript, IScriptMeta } from '@/d.ts'; import setting from '@/store/setting'; -import { uploadFileToOSS } from '@/util/aliyun'; +import { uploadFileToOSS } from '@/common/network/aliyun'; import { formatMessage } from '@/util/intl'; import request from '@/util/request'; -import { downloadFile } from '@/util/utils'; +import { downloadFile } from '@/util/data/file'; import { message, Modal } from 'antd'; import { isArray } from 'lodash'; diff --git a/src/common/network/sql/executePLForMysql.tsx b/src/common/network/sql/executePLForMysql.tsx index d8101ab77..20805ee0d 100644 --- a/src/common/network/sql/executePLForMysql.tsx +++ b/src/common/network/sql/executePLForMysql.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import type { IExecutingInfo } from '@/d.ts'; import request from '@/util/request'; import { generateDatabaseSid } from '../pathUtil'; diff --git a/src/common/network/sql/index.ts b/src/common/network/sql/index.ts index f6fef0c34..68bada67e 100644 --- a/src/common/network/sql/index.ts +++ b/src/common/network/sql/index.ts @@ -25,8 +25,8 @@ import { TraceSpan, } from '@/d.ts'; import setting from '@/store/setting'; -import { uploadFileToOSS } from '@/util/aliyun'; -import notification from '@/util/notification'; +import { uploadFileToOSS } from '@/common/network/aliyun'; +import notification from '@/util/ui/notification'; import request from '@/util/request'; import { generateDatabaseSid, generateSessionSid } from '../pathUtil'; import _executeSQL from './executeSQL'; diff --git a/src/common/network/sql/preHandle.tsx b/src/common/network/sql/preHandle.tsx index 1a023afa2..dc411eb06 100644 --- a/src/common/network/sql/preHandle.tsx +++ b/src/common/network/sql/preHandle.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { ISQLLintReuslt } from '@/component/SQLLintResult/type'; import modal from '@/store/modal'; import sessionManager from '@/store/sessionManager'; diff --git a/src/common/network/table/index.ts b/src/common/network/table/index.ts index ca4b6ad16..99a5e9fea 100644 --- a/src/common/network/table/index.ts +++ b/src/common/network/table/index.ts @@ -18,11 +18,13 @@ import { DbObjectType, INlsObject, ITable, ITableColumn, LobExt, RSModifyDataTyp import { ITableModel } from '@/page/Workspace/components/CreateTable/interface'; import sessionManager from '@/store/sessionManager'; import setting from '@/store/setting'; -import { getNlsValueKey } from '@/util/column'; +import { getNlsValueKey } from '@/util/database/column'; import { formatMessage } from '@/util/intl'; -import notification from '@/util/notification'; +import notification from '@/util/ui/notification'; import request from '@/util/request'; -import { downloadFile, encodeObjName, getBlobValueKey } from '@/util/utils'; +import { getBlobValueKey } from '@/util/utils'; +import { downloadFile } from '@/util/data/file'; +import { encodeObjName } from '@/util/data/string'; import { message } from 'antd'; import { Base64 } from 'js-base64'; import { isNil, toInteger } from 'lodash'; diff --git a/src/common/network/task.ts b/src/common/network/task.ts index 235063982..a03c98942 100644 --- a/src/common/network/task.ts +++ b/src/common/network/task.ts @@ -20,15 +20,8 @@ import { CommonTaskLogType, CreateStructureComparisonTaskRecord, CreateTaskRecord, - CycleTaskDetail, - IAsyncTaskResultSet, - ICycleSubTaskDetailRecord, - ICycleSubTaskRecord, - ICycleTaskJobRecord, - ICycleTaskRecord, IDatasourceUser, IFunction, - IPartitionPlan, IPartitionPlanKeyType, IPartitionPlanTable, IPartitionTablePreviewConfig, @@ -43,15 +36,20 @@ import { TaskRecordParameters, TaskStatus, TaskType, - ICycleTaskStatRecord, - ICycleTaskStatParam, + ITaskStatParam, + ITodos, + IGetFlowScheduleTodoParams, + IStat, + IAsyncTaskResultSet, + IMultipleAsyncExecuteRecord, + MultipleAsyncExecuteRecordStats, + ICycleTaskRecord, } from '@/d.ts'; -import { ISchemaChangeRecord } from '@/d.ts/logicalDatabase'; import { IProject } from '@/d.ts/project'; import { EOperationType, IComparisonResultData, IStructrueComparisonDetail } from '@/d.ts/task'; import setting from '@/store/setting'; import request from '@/util/request'; -import { downloadFile } from '@/util/utils'; +import { downloadFile } from '@/util/data/file'; import { generateFunctionSid } from './pathUtil'; import { IDatabase } from '@/d.ts/database'; import { FileExportResponse, ScheduleExportListView } from '@/d.ts/migrateTask'; @@ -65,6 +63,9 @@ import { IScheduleTerminateResult, } from '@/d.ts/importTask'; import odc from '@/plugins/odc'; +import { TaskSearchType } from '@/component/Task/interface'; +import { ScheduleType } from '@/d.ts/schedule'; +import { omit } from 'lodash'; /** * 根据函数获取ddl sql @@ -79,7 +80,7 @@ export async function getFunctionCreateSQL(funName: string, func: Partial): Promise { const res = await request.post(`/api/v2/flow/flowInstances/`, { @@ -149,7 +150,8 @@ export async function getTaskList(params: { connection?: number; fuzzySearchKeyword?: string; status?: string[]; - taskType?: TaskPageType | TaskType; + taskTypes?: TaskPageType[] | TaskType[]; + searchType?: TaskSearchType; flowInstanceId?: number; startTime?: number; endTime?: number; @@ -203,15 +205,23 @@ export async function getCycleTaskList(params: { } /** - * 查询周期任务状态 + * 查询工单任务状态 */ -export async function getScheduleStat( - params: ICycleTaskStatParam, -): Promise { - const res = await request.get('/api/v2/schedule/schedules/stats', { +export async function getTaskStat(params: ITaskStatParam): Promise> { + const res = await request.get('/api/v2/collaboration/landingPage/flowInstanceStat', { params, }); - return res?.data?.contents; + return res?.data; +} + +/** + * 查询工单与作业 TODO 统计信息 + */ +export async function getFlowScheduleTodo(params: IGetFlowScheduleTodoParams): Promise { + const res = await request.get('/api/v2/collaboration/landingPage/flowScheduleTodoStat', { + params, + }); + return res?.data; } export async function getDatabasesHistories(params: { @@ -236,27 +246,6 @@ export async function getTaskStatus(ids: number[]): Promise(id: number): Promise> { - const res = await request.get(`/api/v2/schedule/scheduleConfigs/${id}`); - return res?.data; -} - -/** - * 查询周期任务详情的子任务详情 - */ -export async function getCycleSubTaskDetail( - scheduleId: number, - scheduleTaskId: number, -): Promise { - const res = await request.get( - `/api/v2/schedule/scheduleConfigs/${scheduleId}/scheduleTask/${scheduleTaskId}`, - ); - return res?.data; -} - /** * 查询任务详情 */ @@ -290,53 +279,6 @@ export async function getTaskLog(id: number, logType: CommonTaskLogType): Promis return res?.data; } -/** - * 查询周期任务日志 - */ -export async function getCycleTaskLog( - scheduleId: number, - taskId: number, - logType: CommonTaskLogType, -): Promise { - const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/log`, { - params: { - logType, - }, - }); - return res?.data; -} - -/** - * 获取全量日志下载URL - */ -export async function getDownloadUrl(scheduleId: number, taskId: number) { - const res = await request.post( - `/api/v2/schedule/schedules/${scheduleId}/tasks/${taskId}/log/getDownloadUrl`, - ); - return res?.data; -} - -/** - * 操作列表 - */ -export async function getOperationList(scheduleId: number): Promise> { - const res = await request.get(`/api/v2/schedule/schedules/${scheduleId}/changes`); - return res?.data; -} - -/** - * 操作详情 - */ -export async function getOperationDetail( - scheduleId: number, - scheduleChangeLogId: number, -): Promise { - const res = await request.get( - `/api/v2/schedule/schedules/${scheduleId}/changes/${scheduleChangeLogId}`, - ); - return res?.data; -} - /** * 回滚任务 */ @@ -374,6 +316,7 @@ export async function stopTask(id: number): Promise { const res = await request.post(`/api/v2/flow/flowInstances/${id}/cancel`); return !!res?.data; } + /** * 执行任务 */ @@ -407,9 +350,10 @@ export async function getTaskFlowList(): Promise { * 获取待我审批的任务流程信息 */ export async function getTaskMetaInfo(): Promise<{ - pendingApprovalInstanceIds: number[]; + approvingFlowIds: number[]; + approvingFlowScheduleIds: number[]; }> { - const result = await request.get('/api/v2/flow/flowInstances/getMetaInfo'); + const result = await request.get('/api/v2/schedule/getMetaInfo'); return result?.data; } @@ -472,11 +416,14 @@ export async function getTaskFile(taskId: number, objectId: string[]): Promise { +export async function getScheduleTaskFile( + scheduleId: number, + objectId: string[], +): Promise { const downloadInfo = await request.post( - `/api/v2/schedule/${taskId}/jobs/async/batchGetDownloadUrl`, + `/api/v2/schedule/schedules/${scheduleId}/batchGetDownloadUrl`, { data: objectId, }, @@ -543,21 +490,6 @@ export async function getPartitionPlanKeyDataTypes( return res?.data; } -/** - * 查询分区策略详情 - */ -export async function getPartitionPlan(taskId: number): Promise { - const res = await request.get( - `/api/v2/flow/flowInstances/${taskId}/tasks/partitionPlans/getDetail`, - { - params: { - id: taskId, - }, - }, - ); - return res?.data; -} - /* * 发起结构分析任务 */ @@ -622,53 +554,6 @@ export async function getFlowSQLLintResult(flowId: number, nodeId: number) { return res?.data?.contents?.[0]; } -/** - * 获取调度任务的task列表 - */ -export async function getDataArchiveSubTask( - taskId: number, - params?: { - page?: number; - size?: number; - }, -): Promise> { - const res = await request.get(`/api/v2/schedule/schedules/${taskId}/tasks`, { - params, - }); - return res?.data; -} - -/** - * 获取调度任务的task详情 - */ -export async function getScheduleTaskDetail( - taskId: number, - jobId: number, -): Promise> { - const res = await request.get(`/api/v2/schedule/schedules/${taskId}/tasks/${jobId}`); - return res?.data; -} - -/** - * 更新分区计划 - */ -export async function rollbackDataArchiveSubTask(taskId: number, subTaskId): Promise { - const res = await request.put(`/api/v2/schedule/schedules/${taskId}/tasks/${subTaskId}/rollback`); - return !!res?.data; -} - -export async function startDataArchiveSubTask(taskId: number, subTaskId): Promise { - const res = await request.put(`/api/v2/schedule/schedules/${taskId}/tasks/${subTaskId}/start`); - return !!res?.data; -} - -export async function stopDataArchiveSubTask(taskId: number, subTaskId): Promise { - const res = await request.put( - `/api/v2/schedule/schedules/${taskId}/tasks/${subTaskId}/interrupt`, - ); - return !!res?.data; -} - /** * 查询无锁结构变更的子任务 */ @@ -718,21 +603,6 @@ export async function getLockDatabaseUserRequired(databaseId: number): Promise<{ const res = await request.get(`/api/v2/osc/lockDatabaseUserRequired/${databaseId}`); return res?.data; } -/** - * 更新限流配置 - */ -export async function updateLimiterConfig( - taskId: number, - data: { - rowLimit?: number; - dataSizeLimit?: number; - }, -): Promise { - const res = await request.put(`/api/v2/schedule/schedules/${taskId}/dlmRateLimitConfiguration`, { - data, - }); - return !!res?.data; -} /** * 更新无锁结构变更限流配置 @@ -836,7 +706,7 @@ export async function exportSchedulesTask(params: { */ export async function getExportListView(params: { ids: number[]; - scheduleType: TaskType; + scheduleType: ScheduleType; }): Promise { const res = await request.post(`/api/v2/export/getExportListView`, { data: params, @@ -951,34 +821,31 @@ export async function getBatchCancelLog(terminateId: string): Promise { return res?.data; } -/** - * 计划任务终止-发起 - */ -export async function batchTerminateScheduleAndTask(data: IScheduleTerminateCmd): Promise { - const res = await request.post(`/api/v2/schedule/schedules/asyncTerminate`, { - data, - }); - return res?.data; +export interface IResponseDataWithStats extends IResponseData { + stats: S; } -/** - * 工单任务终止-查看 - */ -export async function getTerminateScheduleResult( - terminateId: string, -): Promise { - const res = await request.get( - `/api/v2/schedule/schedules/asyncTerminateResult?terminateId=${terminateId}`, - ); +/** 多库变更-执行记录列表 */ +export async function getMultipleAsyncExecuteRecordList(params: { + id: number; + size: number; + page: number; + statuses?: string[]; + keyword?: string; +}): Promise> { + const { id } = params; + const res = await request.get(`/api/v2/flow/flowInstances/${id}/tasks/multiAsyncResults`, { + params: omit(params, 'id'), + }); return res?.data; } -/** - * 工单任务终止-查看日志 - */ -export async function getTerminateScheduleLog(terminateId: string): Promise { - const res = await request.get( - `/api/v2/schedule/schedules/asyncTerminateLog?terminateId=${terminateId}`, - ); +export async function downLoadRollbackPlanFile(id: number, databaseId: number): Promise { + const res = await request.get(`/api/v2/flow/flowInstances/${id}/tasks/rollbackPlan/download`, { + params: { + databaseId, + download: true, + }, + }); return res?.data; } diff --git a/src/common/task/index.ts b/src/common/task/index.ts new file mode 100644 index 000000000..9ce1af618 --- /dev/null +++ b/src/common/task/index.ts @@ -0,0 +1,165 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { formatMessage } from '@/util/intl'; +import { TaskPageType, TaskType } from '@/d.ts'; +import { TaskActionsEnum, TaskGroup } from '@/d.ts/task'; +import { isClient } from '@/util/env'; +import login from '@/store/login'; +import { TaskPageTextMap } from '@/constant/task'; +import settingStore from '@/store/setting'; + +export interface ITaskConfig { + pageType: TaskPageType; + label: string; + groupBy: TaskGroup; + enabled: () => boolean; + allowActions?: TaskActionsEnum[]; +} +export type PartialTaskConfig = { [K in TaskType]?: ITaskConfig }; + +export const allTaskPageConfig: Omit = { + pageType: TaskPageType.ALL, + label: TaskPageTextMap[TaskPageType.ALL], + groupBy: TaskGroup.Other, + enabled: () => { + return !isClient(); + }, +}; + +export const TaskConfig: PartialTaskConfig = { + [TaskType.EXPORT]: { + pageType: TaskPageType.EXPORT, + label: TaskPageTextMap[TaskPageType.EXPORT], + groupBy: TaskGroup.DataExport, + enabled: () => { + return settingStore?.enableDBExport; + }, + // allowActions: [ + // TaskActionsEnum.ROLLBACK, + // TaskActionsEnum.STOP, + // TaskActionsEnum.EXECUTE, + // TaskActionsEnum.PASS, + // TaskActionsEnum.REJECT, + // TaskActionsEnum.AGAIN, + // TaskActionsEnum.DOWNLOAD, + // TaskActionsEnum.DOWNLOAD_SQL, + // TaskActionsEnum.STRUCTURE_COMPARISON, + // TaskActionsEnum.OPEN_LOCAL_FOLDER, + // TaskActionsEnum.DOWNLOAD_VIEW_RESULT, + // TaskActionsEnum.VIEW_RESULT, + // TaskActionsEnum.CLONE, + // TaskActionsEnum.VIEW, + // TaskActionsEnum.SHARE, + // ], + }, + [TaskType.EXPORT_RESULT_SET]: { + pageType: TaskPageType.EXPORT_RESULT_SET, + label: TaskPageTextMap[TaskPageType.EXPORT_RESULT_SET], + groupBy: TaskGroup.DataExport, + enabled: () => { + return settingStore?.enableDBExport; + }, + }, + [TaskType.IMPORT]: { + pageType: TaskPageType.IMPORT, + label: TaskPageTextMap[TaskPageType.IMPORT], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableDBImport; + }, + }, + [TaskType.DATAMOCK]: { + pageType: TaskPageType.DATAMOCK, + label: TaskPageTextMap[TaskPageType.DATAMOCK], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableMockdata; + }, + }, + [TaskType.ASYNC]: { + pageType: TaskPageType.ASYNC, + label: TaskPageTextMap[TaskPageType.ASYNC], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableAsyncTask; + }, + }, + [TaskType.MULTIPLE_ASYNC]: { + pageType: TaskPageType.MULTIPLE_ASYNC, + label: TaskPageTextMap[TaskPageType.MULTIPLE_ASYNC], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableMultipleAsyncTask; + }, + }, + [TaskType.LOGICAL_DATABASE_CHANGE]: { + pageType: TaskPageType.LOGICAL_DATABASE_CHANGE, + label: TaskPageTextMap[TaskPageType.LOGICAL_DATABASE_CHANGE], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return !login?.isPrivateSpace?.() && settingStore?.enableLogicaldatabase; + }, + }, + [TaskType.SHADOW]: { + pageType: TaskPageType.SHADOW, + label: TaskPageTextMap[TaskPageType.SHADOW], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableShadowTableSync; + }, + }, + [TaskType.STRUCTURE_COMPARISON]: { + pageType: TaskPageType.STRUCTURE_COMPARISON, + label: TaskPageTextMap[TaskPageType.STRUCTURE_COMPARISON], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableStructureCompare; + }, + }, + [TaskType.ONLINE_SCHEMA_CHANGE]: { + pageType: TaskPageType.ONLINE_SCHEMA_CHANGE, + label: TaskPageTextMap[TaskPageType.ONLINE_SCHEMA_CHANGE], + groupBy: TaskGroup.DataChanges, + enabled: () => { + return settingStore?.enableOSC; + }, + }, + [TaskType.APPLY_PROJECT_PERMISSION]: { + pageType: TaskPageType.APPLY_PROJECT_PERMISSION, + label: TaskPageTextMap[TaskPageType.APPLY_PROJECT_PERMISSION], + groupBy: TaskGroup.AccessRequest, + enabled: () => { + return settingStore?.enableApplyDBAuth; + }, + }, + [TaskType.APPLY_DATABASE_PERMISSION]: { + pageType: TaskPageType.APPLY_DATABASE_PERMISSION, + label: TaskPageTextMap[TaskPageType.APPLY_DATABASE_PERMISSION], + groupBy: TaskGroup.AccessRequest, + enabled: () => { + return settingStore?.enableApplyProjectAuth; + }, + }, + [TaskType.APPLY_TABLE_PERMISSION]: { + pageType: TaskPageType.APPLY_TABLE_PERMISSION, + label: TaskPageTextMap[TaskPageType.APPLY_TABLE_PERMISSION], + groupBy: TaskGroup.AccessRequest, + enabled: () => { + return settingStore?.enableApplyTableAuth; + }, + }, +}; diff --git a/src/component/AICompletionState/index.less b/src/component/AICompletionState/index.less new file mode 100644 index 000000000..71da4aeb8 --- /dev/null +++ b/src/component/AICompletionState/index.less @@ -0,0 +1,26 @@ +.btn { + // margin-left: 8px; + padding: 5px; + font-size: 16px; + display: flex; + align-items: center; + width: 26px; + justify-content: center; + + &.disabled { + filter: grayscale(100%); + cursor: not-allowed; + } + + &:hover:not(.disabled) { + background: var(--hover-color); + cursor: pointer; + } + :global { + .ant-spin-dot { + display: flex; + align-items: center; + justify-content: center; + } + } +} diff --git a/src/component/AICompletionState/index.tsx b/src/component/AICompletionState/index.tsx new file mode 100644 index 000000000..3f2292d74 --- /dev/null +++ b/src/component/AICompletionState/index.tsx @@ -0,0 +1,76 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { formatMessage } from '@/util/intl'; +import Icon from '@ant-design/icons'; + +import setting from '@/store/setting'; +import { ReactComponent as AIDisableSvg } from '@/svgr/inlinecomplete_disabled.svg'; +import { ReactComponent as AIEnableSvg } from '@/svgr/inlinecomplete_enabled.svg'; +import { Tooltip } from 'antd'; +import classNames from 'classnames'; +import { observer } from 'mobx-react'; +import styles from './index.less'; + +export default observer(function AIState() { + const workspaceAIEnabled = setting.AIConfig.completionEnabled; + const userAIEnabled = setting.enableAIInlineCompletion; + if (!workspaceAIEnabled) { + return null; + } else if (!userAIEnabled) { + return ( + + { + setting.enableAI(); + }} + component={AIDisableSvg} + className={styles.btn} + /> + + ); + } else { + return ( + + + { + setting.disableAI(); + }} + component={AIEnableSvg} + className={styles.btn} + /> + + + ); + } +}); diff --git a/src/component/AIState/AI_Loading.json b/src/component/AIState/AI_Loading.json new file mode 100644 index 000000000..474eaa568 --- /dev/null +++ b/src/component/AIState/AI_Loading.json @@ -0,0 +1,685 @@ +{ + "nm": "Main Scene", + "ddd": 0, + "h": 1024, + "w": 1024, + "meta": { "g": "@lottiefiles/creator 1.30.0" }, + "layers": [ + { + "ty": 4, + "nm": "页面-1", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ln": "页面-1", + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [74.5, 75] }, + "s": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [50, 50], + "t": 15 + }, + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [100, 100], + "t": 25 + }, + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [95, 95], + "t": 27 + }, + { "s": [100, 100], "t": 30 } + ] + }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [380, 742] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [0], + "t": 15 + }, + { "s": [100], "t": 25 } + ] + } + }, + "shapes": [ + { + "ty": "gr", + "bm": 0, + "ln": "路径", + "hd": false, + "nm": "路径", + "it": [ + { + "ty": "sh", + "bm": 0, + "ln": "路径", + "hd": false, + "nm": "路径", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "i": [ + [0, 0], + [0, 0], + [-2.7959049999999976, -9.086687999999995], + [0, 0], + [-2.0969279999999912, 6.640275000000003], + [0, 0], + [-9.08668999999999, 2.795903999999993], + [0, 0], + [6.640273000000008, 2.0969279999999912], + [0, 0], + [2.7959050000000047, 9.086688000000002], + [0, 0], + [2.096929000000003, -6.640268000000001], + [0, 0], + [9.086689, -2.795904], + [0, 0], + [-6.640274, -2.0969280000000055], + [0, 0] + ], + "o": [ + [0, 0], + [9.086689, 2.795903999999993], + [0, 0], + [2.096929000000003, 6.640275000000003], + [0, 0], + [2.7959049999999905, -9.086687999999995], + [0, 0], + [6.640273000000008, -2.0969279999999912], + [0, 0], + [-9.08668999999999, -2.795904], + [0, 0], + [-2.0969289999999887, -6.640268000000001], + [0, 0], + [-2.7959049999999976, 9.086688000000002], + [0, 0], + [-6.640274, 2.0969280000000055], + [0, 0], + [0, 0] + ], + "v": [ + [5.044369, 81.253926], + [38.245734, 91.738566], + [57.118089, 110.96041], + [67.253242, 144.161773], + [80.883276, 144.161773], + [91.367918, 110.96041], + [110.589761, 92.088058], + [143.791126, 81.952902], + [143.791126, 68.322867], + [110.589761, 57.838227], + [91.717406, 38.616384], + [81.582253, 5.415014], + [67.952218, 5.415014], + [57.467577, 38.616384], + [38.245734, 57.488736], + [5.044369, 67.623891], + [5.044369, 81.253926], + [5.044369, 81.253926] + ] + } + } + }, + { + "ty": "gf", + "bm": 0, + "hd": false, + "nm": "Fill", + "e": { "a": 0, "k": [-138.06908493612457, 291.90989510324977] }, + "g": { + "p": 4, + "k": { + "a": 0, + "k": [ + 0, 0.7803921568627451, 0.4, 1, 0.2, 0.6470588235294118, 0.41568627450980394, + 0.996078431372549, 0.62, 0.3137254901960784, 0.4627450980392157, + 0.9921568627450981, 1, 0.00392156862745098, 0.5058823529411764, + 0.9921568627450981 + ] + } + }, + "t": 1, + "a": { "a": 0, "k": 0 }, + "h": { "a": 0, "k": 0 }, + "s": { "a": 0, "k": [602.6905561601985, -474.1679156715744] }, + "r": 1, + "o": { "a": 0, "k": 100 } + }, + { + "ty": "tr", + "a": { "a": 0, "k": [0, 0] }, + "s": { "a": 0, "k": [100, 100] }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [0, 0] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100 } + } + ] + } + ], + "ind": 1 + }, + { + "ty": 4, + "nm": "页面-1", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ln": "页面-1", + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [121.5, 121.5] }, + "s": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [50, 50], + "t": 12 + }, + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [100, 100], + "t": 24 + }, + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [95, 95], + "t": 26 + }, + { "s": [100, 100], "t": 29 } + ] + }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [381.2962, 328.5558] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [0], + "t": 12 + }, + { "s": [100], "t": 24 } + ] + } + }, + "shapes": [ + { + "ty": "gr", + "bm": 0, + "ln": "路径", + "hd": false, + "nm": "路径", + "it": [ + { + "ty": "sh", + "bm": 0, + "ln": "路径", + "hd": false, + "nm": "路径", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "i": [ + [0, 0], + [0, 0], + [-4.543345000000002, -14.678498999999988], + [0, 0], + [-3.4948800000000233, 10.83412899999999], + [0, 0], + [-14.678496999999993, 4.543344000000019], + [0, 0], + [10.834130000000016, 3.494880000000009], + [0, 0], + [4.543344999999988, 14.678497999999998], + [0, 0], + [3.494879999999995, -10.83413], + [0, 0], + [14.678497, -4.543345000000002], + [0, 0], + [-10.83413, -3.4948809999999924] + ], + "o": [ + [0, 0], + [14.678497, 4.5433439999999905], + [0, 0], + [3.145392000000001, 10.83412899999999], + [0, 0], + [4.543344999999988, -14.678498999999988], + [0, 0], + [10.834130000000016, -3.1453930000000128], + [0, 0], + [-14.678496999999993, -4.543345000000002], + [0, 0], + [-3.145391999999987, -10.83413], + [0, 0], + [-4.543345000000002, 14.678497999999998], + [0, 0], + [-10.83413, 3.1453929999999986], + [0, 0] + ], + "v": [ + [8.70785, 132.003413], + [62.878499, 149.128328], + [93.633447, 180.232765], + [110.408874, 234.403413], + [132.426621, 234.403413], + [149.551536, 180.232765], + [180.655972, 149.477816], + [234.826621, 132.702389], + [234.826621, 110.684642], + [180.655972, 93.559727], + [149.901024, 62.45529], + [133.125597, 8.284642], + [111.10785, 8.284642], + [93.982935, 62.45529], + [62.878499, 93.210239], + [8.70785, 109.985665], + [8.70785, 132.003413] + ] + } + } + }, + { + "ty": "gf", + "bm": 0, + "hd": false, + "nm": "Fill", + "e": { "a": 0, "k": [-282.60770584152726, 545.6305403936136] }, + "g": { + "p": 4, + "k": { + "a": 0, + "k": [ + 0, 0.7803921568627451, 0.4, 1, 0.2, 0.6470588235294118, 0.41568627450980394, + 0.996078431372549, 0.62, 0.3137254901960784, 0.4627450980392157, + 0.9921568627450981, 1, 0.00392156862745098, 0.5058823529411764, + 0.9921568627450981 + ] + } + }, + "t": 1, + "a": { "a": 0, "k": 0 }, + "h": { "a": 0, "k": 0 }, + "s": { "a": 0, "k": [432.10367789951187, -220.7859684416852] }, + "r": 1, + "o": { "a": 0, "k": 100 } + }, + { + "ty": "tr", + "a": { "a": 0, "k": [0, 0] }, + "s": { "a": 0, "k": [100, 100] }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [0, 0] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100 } + } + ] + } + ], + "ind": 2 + }, + { + "ty": 4, + "nm": "页面-1", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ln": "页面-1", + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [235, 235.5] }, + "s": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [50, 50], + "t": 0 + }, + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [100, 100], + "t": 15 + }, + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [95, 95], + "t": 17 + }, + { "s": [100, 100], "t": 21 } + ] + }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [624, 540] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { + "a": 1, + "k": [ + { + "o": { "x": 0.167, "y": 0.167 }, + "i": { "x": 0.833, "y": 0.833 }, + "s": [0], + "t": 0 + }, + { "s": [100], "t": 15 } + ] + } + }, + "shapes": [ + { + "ty": "gr", + "bm": 0, + "ln": "路径", + "hd": false, + "nm": "路径", + "it": [ + { + "ty": "sh", + "bm": 0, + "ln": "路径", + "hd": false, + "nm": "路径", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "i": [ + [0, 0], + [0, 0], + [-8.737200999999999, -28.65802100000002], + [0, 0], + [-6.640275000000003, 20.96928700000001], + [0, 0], + [-28.658016000000032, 8.73720099999997], + [0, 0], + [20.969280000000026, 6.640272999999979], + [0, 0], + [8.737203000000022, 28.658020000000008], + [0, 0], + [6.640273000000008, -20.969283], + [0, 0], + [28.65802099999999, -8.737201999999996], + [0, 0], + [-20.969283, -6.640273000000008], + [0, 0] + ], + "o": [ + [0, 0], + [28.658021000000005, 9.086689000000035], + [0, 0], + [6.290785, 20.96928700000001], + [0, 0], + [9.086687999999981, -28.658018000000027], + [0, 0], + [20.969280000000026, -6.290785999999969], + [0, 0], + [-28.658022000000017, -9.086689999999976], + [0, 0], + [-6.290783000000005, -20.969283], + [0, 0], + [-9.086688999999978, 28.658020000000008], + [0, 0], + [-20.969283, 6.290785], + [0, 0], + [0, 0] + ], + "v": [ + [15.658703, 255.831399], + [120.505119, 289.382253], + [180.267577, 349.843686], + [212.420478, 455.03959], + [255.058022, 455.03959], + [288.608877, 350.193171], + [349.070304, 290.430717], + [454.266214, 258.277816], + [454.266214, 215.640273], + [349.419795, 182.08942], + [289.657338, 121.627987], + [257.504435, 16.432082], + [214.866894, 16.432082], + [181.665529, 121.278499], + [121.204095, 181.040956], + [16.008191, 213.193857], + [16.008191, 255.831399], + [15.658703, 255.831399] + ] + } + } + }, + { + "ty": "gf", + "bm": 0, + "hd": false, + "nm": "Fill", + "e": { "a": 0, "k": [-191.27957158326612, 675.2083717333229] }, + "g": { + "p": 4, + "k": { + "a": 0, + "k": [ + 0, 0.7803921568627451, 0.4, 1, 0.2, 0.6470588235294118, 0.41568627450980394, + 0.996078431372549, 0.62, 0.3137254901960784, 0.4627450980392157, + 0.9921568627450981, 1, 0.00392156862745098, 0.5058823529411764, + 0.9921568627450981 + ] + } + }, + "t": 1, + "a": { "a": 0, "k": 0 }, + "h": { "a": 0, "k": 0 }, + "s": { "a": 0, "k": [523.3309334889351, -90.86655076945775] }, + "r": 1, + "o": { "a": 0, "k": 100 } + }, + { + "ty": "tr", + "a": { "a": 0, "k": [235, 235.5] }, + "s": { "a": 0, "k": [100, 100] }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [235, 235.5] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100 } + } + ] + } + ], + "ind": 3 + }, + { + "ty": 4, + "nm": "页面-1", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ln": "页面-1", + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { "a": 0, "k": [512, 512] }, + "s": { "a": 0, "k": [100, 100] }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [512, 512] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100 } + }, + "shapes": [ + { + "ty": "gr", + "bm": 0, + "ln": "形状", + "hd": false, + "nm": "形状", + "it": [ + { + "ty": "sh", + "bm": 0, + "ln": "形状", + "hd": false, + "nm": "形状", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "i": [ + [0, 0], + [0, 282.386349], + [-282.386348, 0], + [0, -282.386348], + [282.386349, 0] + ], + "o": [ + [-282.386348, 0], + [0, -282.386348], + [282.386349, 0], + [0, 282.386349], + [0, 0] + ], + "v": [ + [512, 1024], + [0, 512], + [512, 0], + [1024, 512], + [512, 1024] + ] + } + } + }, + { + "ty": "sh", + "bm": 0, + "ln": "形状", + "hd": false, + "nm": "形状", + "d": 1, + "ks": { + "a": 0, + "k": { + "c": true, + "i": [ + [0, 0], + [0, -244.641638], + [-244.641638, 0], + [0, 244.64163799999994], + [244.64163799999994, 0] + ], + "o": [ + [-244.641638, 0], + [0, 244.64163799999994], + [244.64163799999994, 0], + [0, -244.641638], + [0, 0] + ], + "v": [ + [512, 68.1501709], + [68.1501709, 512], + [512, 955.84983], + [955.84983, 512], + [512, 68.1501709] + ] + } + } + }, + { + "ty": "gf", + "bm": 0, + "hd": false, + "nm": "Fill", + "e": { "a": 0, "k": [113.933106176, 910.066893824] }, + "g": { + "p": 4, + "k": { + "a": 0, + "k": [ + 0, 0.7803921568627451, 0.4, 1, 0.2, 0.6470588235294118, 0.41568627450980394, + 0.996078431372549, 0.62, 0.3137254901960784, 0.4627450980392157, + 0.9921568627450981, 1, 0.00392156862745098, 0.5058823529411764, + 0.9921568627450981 + ] + } + }, + "t": 1, + "a": { "a": 0, "k": 0 }, + "h": { "a": 0, "k": 0 }, + "s": { "a": 0, "k": [880.360409088, 143.63959091200002] }, + "r": 1, + "o": { "a": 0, "k": 100 } + }, + { + "ty": "tr", + "a": { "a": 0, "k": [0, 0] }, + "s": { "a": 0, "k": [100, 100] }, + "sk": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [0, 0] }, + "r": { "a": 0, "k": 0 }, + "sa": { "a": 0, "k": 0 }, + "o": { "a": 0, "k": 100 } + } + ] + } + ], + "ind": 4 + } + ], + "v": "5.7.0", + "fr": 30, + "op": 40, + "ip": 0, + "assets": [] +} diff --git a/src/component/AIState/index.less b/src/component/AIState/index.less new file mode 100644 index 000000000..71da4aeb8 --- /dev/null +++ b/src/component/AIState/index.less @@ -0,0 +1,26 @@ +.btn { + // margin-left: 8px; + padding: 5px; + font-size: 16px; + display: flex; + align-items: center; + width: 26px; + justify-content: center; + + &.disabled { + filter: grayscale(100%); + cursor: not-allowed; + } + + &:hover:not(.disabled) { + background: var(--hover-color); + cursor: pointer; + } + :global { + .ant-spin-dot { + display: flex; + align-items: center; + justify-content: center; + } + } +} diff --git a/src/component/AIState/index.tsx b/src/component/AIState/index.tsx new file mode 100644 index 000000000..d86c633af --- /dev/null +++ b/src/component/AIState/index.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import { formatMessage } from '@/util/intl'; +import Icon from '@ant-design/icons'; + +import setting from '@/store/setting'; +import { ReactComponent as AIDisableSvg } from '@/svgr/ai_disable.svg'; +import { ReactComponent as AIEnableSvg } from '@/svgr/ai_enable.svg'; +import { Spin, Tooltip } from 'antd'; +import classNames from 'classnames'; +import Lottie from 'lottie-react'; +import { observer } from 'mobx-react'; +import aiLoading from './AI_Loading.json'; +import styles from './index.less'; + +export default observer(function AIState() { + const workspaceAIEnabled = setting.AIEnabled; + const userAIEnabled = setting.enableAIInlineCompletion; + if (setting.isAIThinking) { + return ( + + } + className={classNames(styles.btn)} + /> + + ); + } + if (!workspaceAIEnabled) { + return ( + + + + ); + } else if (!userAIEnabled) { + return ( + + { + setting.enableAI(); + }} + component={AIDisableSvg} + className={styles.btn} + /> + + ); + } else { + return ( + + + { + setting.disableAI(); + }} + component={AIEnableSvg} + className={styles.btn} + /> + + + ); + } +}); diff --git a/src/component/Action/Group.tsx b/src/component/Action/Group.tsx index da3d05364..dbdf4c76a 100644 --- a/src/component/Action/Group.tsx +++ b/src/component/Action/Group.tsx @@ -36,6 +36,7 @@ export interface GroupProps { /** 更多操作的自定义展示 */ moreText?: string | React.ReactElement; ellipsisIcon?: 'horizontal' | 'vertical'; + destroyOnHidden?: boolean; } type ellipsisType = 'default' | 'link'; @@ -61,6 +62,7 @@ export default ({ enableLoading, moreText, ellipsisIcon = 'horizontal', + destroyOnHidden = false, }: GroupProps) => { const EllipsisIcon = ellipsisIcon === 'vertical' ? MoreOutlined : EllipsisOutlined; const visibleActions = Array.isArray(children) @@ -141,6 +143,7 @@ export default ({ {ellipsisActions.length > 0 && ( { const actionKey = action.key; diff --git a/src/component/AnchorContainer/index.less b/src/component/AnchorContainer/index.less new file mode 100644 index 000000000..912f64c31 --- /dev/null +++ b/src/component/AnchorContainer/index.less @@ -0,0 +1,22 @@ +.AnchorContainer { + flex: 1; + overflow: auto; + display: flex; + + .content { + flex: 1; + overflow-x: auto; + height: max-content; + } + + .anchor { + padding: 6px 16px 12px 12px; + height: 100%; + position: sticky; + right: 0; + top: 0; + align-self: flex-start; + max-width: 200px; + flex-shrink: 0; // 防止anchor被压缩 + } +} diff --git a/src/component/AnchorContainer/index.tsx b/src/component/AnchorContainer/index.tsx new file mode 100644 index 000000000..adbf523a8 --- /dev/null +++ b/src/component/AnchorContainer/index.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + +import React, { CSSProperties, useRef } from 'react'; +import { Anchor } from 'antd'; +import { AnchorLinkItemProps } from 'antd/es/anchor/Anchor'; +import styles from './index.less'; + +interface AnchorContainerProps { + items: AnchorLinkItemProps[]; + containerWrapStyle?: CSSProperties; +} + +const AnchorContainer: React.FC = (props) => { + const { items, containerWrapStyle = {} } = props; + const scrollContainerRef = useRef(null); + return ( +
+
+ {props.children} +
+
+ scrollContainerRef.current!} + onClick={(e) => { + e.preventDefault(); + }} + items={items} + /> +
+
+ ); +}; + +export default AnchorContainer; diff --git a/src/component/BatchSelectionPopover/index.tsx b/src/component/BatchSelectionPopover/index.tsx index 1bef211a5..4b1dc9d3b 100644 --- a/src/component/BatchSelectionPopover/index.tsx +++ b/src/component/BatchSelectionPopover/index.tsx @@ -1,6 +1,22 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { formatMessage } from '@/util/intl'; import React, { useEffect, useState, useMemo } from 'react'; -import { Button, Checkbox, Popover, Spin, Empty, Input, Space } from 'antd'; +import { Button, Checkbox, Popover, Spin, Empty, Input, Space, message } from 'antd'; import { PlusOutlined } from '@ant-design/icons'; import type { CheckboxChangeEvent } from 'antd/es/checkbox'; import styles from './index.less'; @@ -10,12 +26,15 @@ interface BatchSelectionPopoverProps { options: { label: string; value: string; + disabled?: boolean; }[]; handleConfirm: (selectedList: any[]) => void; + disabled?: boolean; + maxCount?: number; } const BatchSelectionPopover: React.FC = (props) => { - const { options = [], handleConfirm } = props; + const { options = [], handleConfirm, disabled = false, maxCount } = props; const [checkedList, setCheckedList] = useState([]); const [searchValue, setSearchValue] = useState(undefined); @@ -91,11 +110,16 @@ const BatchSelectionPopover: React.FC = (props) => {
!item?.disabled)?.length === filterCheckedList?.length } - disabled={!filterOptions?.length} + disabled={!filterOptions?.filter((item) => !item?.disabled)?.length} onChange={(e: CheckboxChangeEvent) => { - setCheckedList(e.target.checked ? filterOptions?.map((item) => item?.value) : []); + setCheckedList( + e.target.checked + ? filterOptions?.filter((item) => !item?.disabled)?.map((item) => item?.value) + : [], + ); }} > {formatMessage({ @@ -107,6 +131,18 @@ const BatchSelectionPopover: React.FC = (props) => { size="small" type="primary" onClick={() => { + if (maxCount && checkedList?.length > maxCount) { + message.warning( + formatMessage( + { + id: 'src.component.BatchSelectionPopover.8DFDBE89', + defaultMessage: '最多还可以添加{maxCount}个', + }, + { maxCount }, + ), + ); + return; + } handleConfirm(checkedList); setOpen(false); }} @@ -130,6 +166,7 @@ const BatchSelectionPopover: React.FC = (props) => { { if (e.target.checked) { @@ -182,6 +219,7 @@ const BatchSelectionPopover: React.FC = (props) => { onClick={() => { setOpen(true); }} + disabled={disabled} > {formatMessage({ id: 'src.component.BatchSelectionPopover.F72B9B10', diff --git a/src/component/Button/FIlterIcon/index.less b/src/component/Button/FIlterIcon/index.less index d2d85dab8..770ad9444 100644 --- a/src/component/Button/FIlterIcon/index.less +++ b/src/component/Button/FIlterIcon/index.less @@ -3,10 +3,16 @@ color: var(--icon-color-normal); font-size: 13px; cursor: pointer; + box-sizing: content-box; &:hover { background-color: var(--hover-color); } + &.border { + border: 1px solid var(--odc-border-color); + border-radius: 2px; + } &.iconActive { color: var(--icon-color-focus); + border-color: var(--icon-color-focus); } } diff --git a/src/component/Button/FIlterIcon/index.tsx b/src/component/Button/FIlterIcon/index.tsx index 0661151fa..ebcc3a7fe 100644 --- a/src/component/Button/FIlterIcon/index.tsx +++ b/src/component/Button/FIlterIcon/index.tsx @@ -23,6 +23,7 @@ interface IProps { isActive?: boolean; className?: string; style?: React.CSSProperties; + border?: boolean; } const FilterIcon: React.FC = function ({ @@ -30,11 +31,16 @@ const FilterIcon: React.FC = function ({ className, isActive, onClick, + border = false, ...rest }) { return (
diff --git a/src/component/Button/SyncMetadata/index.tsx b/src/component/Button/SyncMetadata/index.tsx index 19f850d48..6fd2440ef 100644 --- a/src/component/Button/SyncMetadata/index.tsx +++ b/src/component/Button/SyncMetadata/index.tsx @@ -1,8 +1,24 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { syncAll } from '@/common/network/database'; import { DBObjectSyncStatus, IDatabase } from '@/d.ts/database'; import { ReactComponent as SyncMetadataSvg } from '@/svgr/sync_metadata.svg'; import { formatMessage } from '@/util/intl'; -import { getLocalFormatDateTime } from '@/util/utils'; +import { getLocalFormatDateTime } from '@/util/data/dateTime'; import { LoadingOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; import { useEffect, useRef, useState, useContext } from 'react'; diff --git a/src/component/CommonCopyIcon/index.tsx b/src/component/CommonCopyIcon/index.tsx index 2fca668a5..5cb286f16 100644 --- a/src/component/CommonCopyIcon/index.tsx +++ b/src/component/CommonCopyIcon/index.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import CopyToClipboard from 'react-copy-to-clipboard'; import { CopyOutlined } from '@ant-design/icons'; import { Tooltip } from 'antd'; diff --git a/src/component/CommonIDE/index.tsx b/src/component/CommonIDE/index.tsx index 3935802a6..ff7bde69c 100644 --- a/src/component/CommonIDE/index.tsx +++ b/src/component/CommonIDE/index.tsx @@ -19,7 +19,7 @@ import { IResultSet, ISqlExecuteResultStatus } from '@/d.ts'; import DDLResultSet from '@/page/Workspace/components/DDLResultSet'; import SQLResultLog from '@/page/Workspace/components/SQLResultSet/SQLResultLog'; import SessionStore from '@/store/sessionManager/session'; -import { IEditor } from '@/util/editor'; +import { IEditor } from '@/util/ui/editor'; import { formatMessage } from '@/util/intl'; import { Tabs, Tooltip } from 'antd'; import classnames from 'classnames'; @@ -189,6 +189,7 @@ class CommonIDE extends React.PureComponent { onValueChange={this.onSQLChange} onEditorCreated={this.onEditorCreated} placeholder={placeholder} + actionGroupKey={toolbarGroupKey} {...editorProps} />
@@ -260,6 +261,7 @@ class CommonIDE extends React.PureComponent { onValueChange={this.onSQLChange} onEditorCreated={this.onEditorCreated} placeholder={placeholder} + actionGroupKey={toolbarGroupKey} {...editorProps} />
diff --git a/src/component/CommonTable/TableInfo.tsx b/src/component/CommonTable/TableInfo.tsx index 240312796..ad50853fc 100644 --- a/src/component/CommonTable/TableInfo.tsx +++ b/src/component/CommonTable/TableInfo.tsx @@ -27,7 +27,14 @@ interface IProps extends IRowSelecter { } export const TableInfo: React.FC> = (props) => { - const { options, selectedRowKeys, hideSelectAll, onCancelSelect, onSelectAllRows } = props; + const { + options, + selectedRowKeys, + hideSelectAll, + onCancelSelect, + onSelectAllRows, + selectAllText, + } = props; return (
> = (props) => { {!hideSelectAll && ( { - formatMessage({ - id: 'odc.component.CommonTable.TableInfo.SelectAll', - defaultMessage: '全选所有', - }) /*全选所有*/ + selectAllText ?? + formatMessage({ + id: 'odc.component.CommonTable.TableInfo.SelectAll', + defaultMessage: '全选所有', + }) /*全选所有*/ } )} diff --git a/src/component/CommonTable/Toolbar.tsx b/src/component/CommonTable/Toolbar.tsx index a6c886969..0ce02c539 100644 --- a/src/component/CommonTable/Toolbar.tsx +++ b/src/component/CommonTable/Toolbar.tsx @@ -29,6 +29,7 @@ import type { ITitleContent, } from './interface'; import useURLParams from '@/util/hooks/useUrlParams'; +import FilterIcon from '../Button/FIlterIcon'; interface IProps { loading: boolean; @@ -70,7 +71,7 @@ export const Toolbar: React.FC = (props) => { {operationContent?.isNeedOccupyElement &&
} {operationContent && } {titleContent && } - + {cascaderContent && } {urlTriggerValue && ( = (props) => { /> )} {enabledReload && ( - { onReload(); }} - spin={loading} - /> + > + + )} diff --git a/src/component/CommonTable/component/EditTableRow.tsx b/src/component/CommonTable/component/EditTableRow.tsx index dbd3714be..e05cf9ca2 100644 --- a/src/component/CommonTable/component/EditTableRow.tsx +++ b/src/component/CommonTable/component/EditTableRow.tsx @@ -1,3 +1,19 @@ +/* + * Copyright 2023 OceanBase + * + * 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. + */ + import { formatMessage } from '@/util/intl'; import { Form, GetRef, InputRef, Input, Tooltip } from 'antd'; import React, { useEffect, useRef, useState, useContext } from 'react'; diff --git a/src/component/CommonTable/component/FilterContent.tsx b/src/component/CommonTable/component/FilterContent.tsx index 1dfd4573b..80c9b0e82 100644 --- a/src/component/CommonTable/component/FilterContent.tsx +++ b/src/component/CommonTable/component/FilterContent.tsx @@ -41,11 +41,10 @@ export const FilterContent: React.FC = (props) => { {filterTitle && {filterTitle}} + + + + + + option?.value.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1 + } + /> + + + + + { + if (value.size > 512 * 1024 * 1024) { + return Promise.reject( + formatMessage({ + id: 'src.component.CreateExternalResourceModal.C77A8263', + defaultMessage: '文件不能大于 512 MB', + }), + ); + } + return Promise.resolve(); + }, + }, + ]} + > + { + setSelectedFile(file); + message.success( + formatMessage( + { + id: 'src.component.CreateExternalResourceModal.972F5731', + defaultMessage: '文件 {fileName} 选择成功', + }, + { fileName: file.name }, + ), + ); + }} + onError={(file, error) => { + message.error( + formatMessage( + { + id: 'src.component.CreateExternalResourceModal.6630BC2B', + defaultMessage: '文件验证失败: {error}', + }, + { error }, + ), + ); + setSelectedFile(null); + }} + onRemove={() => { + setSelectedFile(null); + }} + /> + + + +