From 9547ec8b3e0a4558a5ab82762ed061e53a34d3fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=89=BA=E6=B3=BD?= Date: Mon, 9 Dec 2024 10:27:38 +0800 Subject: [PATCH] =?UTF-8?q?PullRequest:=20589=20feat:=20=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BD=92=E6=A1=A3=E6=94=AF=E6=8C=81=E5=AF=B9=E8=B1=A1=E5=AD=98?= =?UTF-8?q?=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Merge branch 'feat/dataArchiving_433_oss of git@code.alipay.com:oceanbase/oceanbase-developer-center.git into dev-4.3.3 https://code.alipay.com/oceanbase/oceanbase-developer-center/pull_requests/589 Signed-off-by: 晓康 * feat: 数据归档支持对象存储 * feat: 删除调试代码、注释 * feat: 增加选择数据库逻辑 * feat: 数据归档源端数据库屏蔽对象存储、项目对象存储屏蔽操作列 * feat:删除log --- src/common/datasource/fileSystem/index.tsx | 48 +++++ src/common/datasource/index.tsx | 76 ++++++- src/common/datasource/interface.ts | 8 +- src/common/network/connection.ts | 3 +- src/common/network/sql/executeSQL.tsx | 9 +- src/common/network/sql/preHandle.tsx | 9 +- src/common/network/table/helper.ts | 23 +- src/component/ConnectionPopover/index.tsx | 47 ++++- src/component/EditorToolBar/index.tsx | 4 +- src/component/ExecuteSQLModal/index.tsx | 13 +- src/component/ScriptPage/index.tsx | 4 +- .../DataArchiveTask/CreateModal/index.tsx | 3 + .../Task/component/DatabaseSelect/index.tsx | 6 + .../Task/component/DatabaseSelecter/index.tsx | 8 +- .../Task/component/TableSelecter/interface.ts | 2 +- src/component/Task/helper.tsx | 3 + src/constant/label.ts | 11 + src/d.ts/datasource.ts | 4 + src/d.ts/index.ts | 15 +- src/d.ts/table.ts | 6 +- .../Datasource/Content/List/index.tsx | 9 +- .../Form/Account/PrivateAccount.tsx | 13 +- .../NewDatasourceDrawer/Form/AddressItems.tsx | 4 + .../Form/CloudStorageForm/index.tsx | 172 +++++++++++++++ .../Form/ExtraConfig/index.tsx | 3 + .../Form/components/ErrorTip/index.tsx | 16 ++ .../NewDatasourceDrawer/Form/index.tsx | 37 +++- .../NewDatasourceDrawer/NewButton.tsx | 171 ++++++--------- .../Datasource/NewDatasourceDrawer/index.less | 21 ++ .../components/AddDataBaseButton/index.tsx | 27 ++- .../components/AddObjectStorage/index.tsx | 197 ++++++++++++++++++ .../LogicDatabase/ManageLogicDatabase.tsx | 4 +- src/page/Project/Database/index.tsx | 19 +- .../Database/UserAuthList/index.tsx | 6 +- .../ManageModal/Table/UserAuthList/index.tsx | 6 +- .../components/ObjectList.tsx | 2 +- .../SideBar/ResourceTree/Nodes/table.tsx | 36 ++-- .../DBPermissionTableContent/index.tsx | 2 +- .../Workspace/components/SQLPage/index.tsx | 2 +- .../SessionSelect/SelectItem.tsx | 5 + .../SessionSelect/SessionDropdown/index.tsx | 45 ++-- .../components/TablePage/Partitions/helper.ts | 4 +- src/page/Workspace/index.tsx | 2 +- src/svgr/COS.svg | 1 + src/svgr/OBS.svg | 1 + src/svgr/S3.svg | 1 + src/svgr/S3_file.svg | 1 + src/svgr/cos_file.svg | 1 + src/svgr/obs_file.svg | 1 + src/svgr/oss.svg | 1 + src/svgr/oss_file.svg | 1 + src/util/connection.ts | 8 +- src/util/request/service.ts | 1 + 53 files changed, 901 insertions(+), 221 deletions(-) create mode 100644 src/common/datasource/fileSystem/index.tsx create mode 100644 src/page/Datasource/Datasource/NewDatasourceDrawer/Form/CloudStorageForm/index.tsx create mode 100644 src/page/Datasource/Datasource/NewDatasourceDrawer/Form/components/ErrorTip/index.tsx create mode 100644 src/page/Project/Database/components/AddObjectStorage/index.tsx create mode 100644 src/svgr/COS.svg create mode 100644 src/svgr/OBS.svg create mode 100644 src/svgr/S3.svg create mode 100644 src/svgr/S3_file.svg create mode 100644 src/svgr/cos_file.svg create mode 100644 src/svgr/obs_file.svg create mode 100644 src/svgr/oss.svg create mode 100644 src/svgr/oss_file.svg diff --git a/src/common/datasource/fileSystem/index.tsx b/src/common/datasource/fileSystem/index.tsx new file mode 100644 index 000000000..87ca3a17e --- /dev/null +++ b/src/common/datasource/fileSystem/index.tsx @@ -0,0 +1,48 @@ +import { ConnectType, TaskType } from '@/d.ts'; +import { IDataSourceModeConfig } from '../interface'; +import MySQLColumnExtra from '../oceanbase/MySQLColumnExtra'; +import { haveOCP } from '@/util/env'; + +const CloudStorageConfig: IDataSourceModeConfig = { + connection: { + address: { + items: ['ip'], + }, + account: false, + sys: false, + ssl: false, + disableURLParse: true, + cloudStorage: true, + disableExtraConfig: true, + }, + features: { + task: [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE], + obclient: false, + recycleBin: false, + sessionManage: false, + sessionParams: false, + sqlExplain: false, + resourceTree: false, + export: { + fileLimit: false, + snapshot: false, + }, + }, +}; + +const ALIYUN: Record = { + [ConnectType.OSS]: CloudStorageConfig, +}; +const AWSS3: Record = { + [ConnectType.S3A]: CloudStorageConfig, +}; + +const HUAWEI: Record = { + [ConnectType.OBS]: CloudStorageConfig, +}; + +const QCLOUD: Record = { + [ConnectType.COS]: CloudStorageConfig, +}; + +export default { ALIYUN, AWSS3, HUAWEI, QCLOUD }; diff --git a/src/common/datasource/index.tsx b/src/common/datasource/index.tsx index 3e7eb6563..2043cae95 100644 --- a/src/common/datasource/index.tsx +++ b/src/common/datasource/index.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ConnectType, ConnectionMode } from '@/d.ts'; +import { ConnectType, ConnectionMode, DatasourceGroup } from '@/d.ts'; import { IDataSourceModeConfig } from './interface'; import { IDataSourceType } from '@/d.ts/datasource'; import obOracle from './oceanbase/oboracle'; @@ -23,6 +23,7 @@ import oracle from './oracle'; import MySQL from './mysql'; import Doris from './doris'; import PG from './pg'; +import FileSystem from './fileSystem'; import { ReactComponent as OBSvg } from '@/svgr/source_ob.svg'; import { ReactComponent as DBOBSvg } from '@/svgr/database_oceanbase.svg'; import { ReactComponent as MySQLSvg } from '@/svgr/mysql.svg'; @@ -34,8 +35,16 @@ import { ReactComponent as OracleSvg } from '@/svgr/oracle.svg'; import { ReactComponent as DBOracleSvg } from '@/svgr/database_oracle.svg'; import { DBType, BooleanOptionType } from '@/d.ts/database'; import { ReactComponent as DBPGSvg } from '@/svgr/database_pg.svg'; +import { ReactComponent as OSSSvg } from '@/svgr/oss.svg'; +import { ReactComponent as DBOSSSvg } from '@/svgr/oss_file.svg'; +import { ReactComponent as COSSvg } from '@/svgr/COS.svg'; +import { ReactComponent as DBCOSSvg } from '@/svgr/cos_file.svg'; +import { ReactComponent as OBSSvg } from '@/svgr/OBS.svg'; +import { ReactComponent as DBOBSSvg } from '@/svgr/obs_file.svg'; +import { ReactComponent as S3Svg } from '@/svgr/S3.svg'; +import { ReactComponent as DBS3Svg } from '@/svgr/S3_file.svg'; -const _types: Map< +export const _types: Map< IDataSourceType, { connectTypes: ConnectType[]; @@ -91,9 +100,57 @@ const _styles = { component: DBPGSvg, }, }, + [IDataSourceType.ALIYUNOSS]: { + icon: { + component: OSSSvg, + color: '#000000', + }, + dbIcon: { + component: DBOSSSvg, + }, + }, + [IDataSourceType.HUAWEI]: { + icon: { + component: OBSSvg, + color: '#000000', + }, + dbIcon: { + component: DBOBSSvg, + }, + }, + [IDataSourceType.AWSS3]: { + icon: { + component: S3Svg, + color: '#000000', + }, + dbIcon: { + component: DBS3Svg, + }, + }, + [IDataSourceType.QCLOUD]: { + icon: { + component: COSSvg, + color: '#000000', + }, + dbIcon: { + component: DBCOSSvg, + }, + }, +}; + +const _gruops = { + [IDataSourceType.OceanBase]: DatasourceGroup.OceanBaseDatabase, + [IDataSourceType.MySQL]: DatasourceGroup.OtherDatabase, + [IDataSourceType.Doris]: DatasourceGroup.OtherDatabase, + [IDataSourceType.Oracle]: DatasourceGroup.OtherDatabase, + [IDataSourceType.PG]: DatasourceGroup.OtherDatabase, + [IDataSourceType.ALIYUNOSS]: DatasourceGroup.FileSystem, + [IDataSourceType.AWSS3]: DatasourceGroup.FileSystem, + [IDataSourceType.HUAWEI]: DatasourceGroup.FileSystem, + [IDataSourceType.QCLOUD]: DatasourceGroup.FileSystem, }; -const connectType2Ds: Map = new Map(); +export const connectType2Ds: Map = new Map(); function register( dataSourceType: IDataSourceType, @@ -135,6 +192,10 @@ register(IDataSourceType.MySQL, MySQL); register(IDataSourceType.Doris, Doris); register(IDataSourceType.Oracle, oracle); register(IDataSourceType.PG, PG); +register(IDataSourceType.ALIYUNOSS, FileSystem.ALIYUN); +register(IDataSourceType.AWSS3, FileSystem.AWSS3); +register(IDataSourceType.HUAWEI, FileSystem.HUAWEI); +register(IDataSourceType.QCLOUD, FileSystem.QCLOUD); function getAllConnectTypes(ds?: IDataSourceType): ConnectType[] { if (!ds) { @@ -188,6 +249,14 @@ function getDataSourceStyleByConnectType(ct: ConnectType) { return getDataSourceStyle(connectType2Ds[ct]); } +function getDataSourceGroup(ds: IDataSourceType) { + return _gruops[ds]; +} + +function getDataSourceGroupByConnectType(ct: ConnectType) { + return getDataSourceGroup(connectType2Ds[ct]); +} + function getDsByConnectType(ct: ConnectType) { return connectType2Ds[ct]; } @@ -202,6 +271,7 @@ export { getDataSourceModeConfigByConnectionMode, getDataSourceStyle, getDataSourceStyleByConnectType, + getDataSourceGroupByConnectType, getDefaultConnectType, getDsByConnectType, getAllDBTypes, diff --git a/src/common/datasource/interface.ts b/src/common/datasource/interface.ts index 5a73a66e2..8b61b11eb 100644 --- a/src/common/datasource/interface.ts +++ b/src/common/datasource/interface.ts @@ -116,7 +116,7 @@ interface IProcedureConfig { export interface IDataSourceModeConfig { priority?: number; connection: { - address: { + address?: { items: ('ip' | 'port' | 'cluster' | 'tenant' | 'sid' | 'catalogName')[]; }; account: boolean; @@ -127,6 +127,8 @@ export interface IDataSourceModeConfig { jdbcDoc?: string; disableURLParse?: boolean; unionUser?: boolean; + cloudStorage?: boolean; + disableExtraConfig?: boolean; }; features: { task: TaskType[]; @@ -148,13 +150,13 @@ export interface IDataSourceModeConfig { snapshot: boolean; }; }; - schema: { + schema?: { table: ICreateTableConfig; func: IFunctionConfig; proc: IProcedureConfig; innerSchema: string[]; }; - sql: { + sql?: { language: string; escapeChar: string; plParamMode?: 'text' | 'list'; diff --git a/src/common/network/connection.ts b/src/common/network/connection.ts index 27ff390bb..c1b4426ae 100644 --- a/src/common/network/connection.ts +++ b/src/common/network/connection.ts @@ -97,7 +97,6 @@ function generateConnectionParams(formData: Partial, isHide */ export async function createConnection(formData: Partial, isHiden?: boolean) { const params: Partial = generateConnectionParams(formData, isHiden); - const requestParams = { wantCatchError: false, holdErrorTip: true, @@ -326,7 +325,7 @@ export async function getSessionStatus(sessionId?: string): Promise<{ obVersion: string; }; session: ISessionStatus; - killCurrentQuerySupported: boolean + killCurrentQuerySupported: boolean; }> { const sid = generateSessionSid(sessionId); const res = await request.get(`/api/v2/datasource/sessions/${sessionId}/status`); diff --git a/src/common/network/sql/executeSQL.tsx b/src/common/network/sql/executeSQL.tsx index f8b46af7d..61e459ca3 100644 --- a/src/common/network/sql/executeSQL.tsx +++ b/src/common/network/sql/executeSQL.tsx @@ -19,8 +19,12 @@ import type { ISqlExecuteResult, IExecutingInfo } from '@/d.ts'; import { EStatus, ISqlExecuteResultStatus } from '@/d.ts'; import request from '@/util/request'; import { generateDatabaseSid, generateSessionSid } from '../pathUtil'; -import { executeSQLPreHandle, IExecuteSQLParams, IExecuteTaskResult, ISQLExecuteTask } from './preHandle'; - +import { + executeSQLPreHandle, + IExecuteSQLParams, + IExecuteTaskResult, + ISQLExecuteTask, +} from './preHandle'; class Task { public result: ISqlExecuteResult[] = []; @@ -176,7 +180,6 @@ export default async function executeSQL( }); const taskInfo: ISQLExecuteTask = res?.data; - const { pass, data: preHandleData, diff --git a/src/common/network/sql/preHandle.tsx b/src/common/network/sql/preHandle.tsx index 38452cd00..40dc8de09 100644 --- a/src/common/network/sql/preHandle.tsx +++ b/src/common/network/sql/preHandle.tsx @@ -38,7 +38,7 @@ export interface ISQLExecuteTask { violatedRules: IRule[]; unauthorizedDBResources: IUnauthorizedDBResources[]; errorMessage?: string; - approvalRequired?: boolean + approvalRequired?: boolean; } /** @@ -55,7 +55,7 @@ export interface IExecuteTaskResult { unauthorizedDBResources?: IUnauthorizedDBResources[]; unauthorizedSql?: string; errorMessage?: string; - approvalRequired?: boolean + approvalRequired?: boolean; } export function executeSQLPreHandle( @@ -63,7 +63,7 @@ export function executeSQLPreHandle( params: IExecuteSQLParams | IExecutePLForMysqlParams | string, needModal: boolean, sessionId: string, - handleUnauthInModal?: boolean + handleUnauthInModal?: boolean, ): { data: any; lintResultSet: ISQLLintReuslt[]; @@ -146,7 +146,7 @@ export function executeSQLPreHandle( sessionId, lintResultSet, unauthorizedDBResources, - status: unauthorizedDBResources?.length ? EStatus.DISABLED :lintStatus, + status: unauthorizedDBResources?.length ? EStatus.DISABLED : lintStatus, onSave: () => { // 关闭SQL确认窗口打开新建数据库变更抽屉 modal.updateWorkSpaceExecuteSQLModalProps(); @@ -167,7 +167,6 @@ export function executeSQLPreHandle( const requestId = taskInfo?.requestId; const sqls = taskInfo?.sqls; if (!requestId || !sqls?.length) { - return { data: null, lintResultSet, diff --git a/src/common/network/table/helper.ts b/src/common/network/table/helper.ts index 03eb55d72..aa56777af 100644 --- a/src/common/network/table/helper.ts +++ b/src/common/network/table/helper.ts @@ -535,11 +535,12 @@ export function convertServerTablePartitionToTablePartition( } }; - const handleColumns = (partition)=> partition?.partitionOption?.columnNames?.map((item) => { - return { - columnName: item, - }; - }) + const handleColumns = (partition) => + partition?.partitionOption?.columnNames?.map((item) => { + return { + columnName: item, + }; + }); switch (partType) { case IPartitionType.HASH: { @@ -549,7 +550,7 @@ export function convertServerTablePartitionToTablePartition( expression: partition?.partitionOption?.expression, columns: handleColumns(partition), partitions: handlePartitions(partType, dbMode, partition), - subpartitionTemplated: subpartitionTemplated + subpartitionTemplated: subpartitionTemplated, }; break; } @@ -560,7 +561,7 @@ export function convertServerTablePartitionToTablePartition( columns: handleColumns(partition), expression: partition?.partitionOption?.expression, partitions: handlePartitions(partType, dbMode, partition), - subpartitionTemplated: subpartitionTemplated + subpartitionTemplated: subpartitionTemplated, }; break; } @@ -570,7 +571,7 @@ export function convertServerTablePartitionToTablePartition( expression: partition?.partitionOption?.expression, columns: handleColumns(partition), partitions: handlePartitions(partType, dbMode, partition), - subpartitionTemplated: subpartitionTemplated + subpartitionTemplated: subpartitionTemplated, }; break; } @@ -580,7 +581,7 @@ export function convertServerTablePartitionToTablePartition( columns: handleColumns(partition), expression: partition?.partitionOption?.expression, partitions: handlePartitions(partType, dbMode, partition), - subpartitionTemplated: subpartitionTemplated + subpartitionTemplated: subpartitionTemplated, }; break; } @@ -590,7 +591,7 @@ export function convertServerTablePartitionToTablePartition( expression: partition?.partitionOption?.expression, columns: handleColumns(partition), partitions: handlePartitions(partType, dbMode, partition), - subpartitionTemplated: subpartitionTemplated + subpartitionTemplated: subpartitionTemplated, }; break; } @@ -600,7 +601,7 @@ export function convertServerTablePartitionToTablePartition( columns: handleColumns(partition), expression: partition?.partitionOption?.expression, partitions: handlePartitions(partType, dbMode, partition), - subpartitionTemplated: subpartitionTemplated + subpartitionTemplated: subpartitionTemplated, }; break; } diff --git a/src/component/ConnectionPopover/index.tsx b/src/component/ConnectionPopover/index.tsx index 3c5196ef5..8c4c73650 100644 --- a/src/component/ConnectionPopover/index.tsx +++ b/src/component/ConnectionPopover/index.tsx @@ -18,6 +18,7 @@ import { getDataSourceStyleByConnectType } from '@/common/datasource'; import DataBaseStatusIcon from '@/component/StatusIcon/DatabaseIcon'; import { ConnectTypeText } from '@/constant/label'; import { IConnection } from '@/d.ts'; +import { isConnectTypeBeFileSystemGroup } from '@/util/connection'; import { IDatabase } from '@/d.ts/database'; import { ClusterStore } from '@/store/cluster'; import { isLogicalDatabase } from '@/util/database'; @@ -38,13 +39,56 @@ const ConnectionPopover: React.FC<{ }> = (props) => { const { connection, clusterStore, showType = true, database } = props; const isLogicDb = isLogicalDatabase(database); - + const isFileSyetem = isConnectTypeBeFileSystemGroup(connection?.type); if (!connection && !isLogicDb) { return null; } const DBIcon = getDataSourceStyleByConnectType(connection?.type || database?.connectType)?.icon; + if (isFileSyetem) { + return ( +
{ + e.stopPropagation(); + }} + style={{ lineHeight: '20px' }} + > + + +
+
+ + {' '} + {connection.name} +
+
+
+ {renderConnectionMode()} +
{`连接串地址:${connection.host ?? '-'}`}
+
{`文件URL: ${connection.defaultSchema ?? '-'}`}
+
+
+ ); + } + if (isLogicDb) { return (
); } + let clusterAndTenant = (
{ diff --git a/src/component/EditorToolBar/index.tsx b/src/component/EditorToolBar/index.tsx index 1c3e841bb..837da45cd 100644 --- a/src/component/EditorToolBar/index.tsx +++ b/src/component/EditorToolBar/index.tsx @@ -42,7 +42,7 @@ interface IProps { }; databaseType?: string; editorValue?: string; - defaultEditorValue?: string + defaultEditorValue?: string; } interface IState {} interface ToolBarCommonAction { @@ -110,7 +110,7 @@ export default class EditorToolBar extends Component { }, ) { const { ctx, databaseType, editorValue, defaultEditorValue } = this.props; - const hasChangeEditorValue = editorValue !== defaultEditorValue + const hasChangeEditorValue = editorValue !== defaultEditorValue; let buttonsArr = []; if (!actionGroups) { diff --git a/src/component/ExecuteSQLModal/index.tsx b/src/component/ExecuteSQLModal/index.tsx index 18a368d53..ceccda865 100644 --- a/src/component/ExecuteSQLModal/index.tsx +++ b/src/component/ExecuteSQLModal/index.tsx @@ -46,7 +46,7 @@ interface IProps { onChange?: (sql: string) => void; status?: EStatus; lintResultSet?: ISQLLintReuslt[]; - unauthorizedDBResources?: IUnauthorizedDBResources[] + unauthorizedDBResources?: IUnauthorizedDBResources[]; callback?: () => void; } const ExecuteSQLModal: React.FC = (props) => { @@ -62,7 +62,7 @@ const ExecuteSQLModal: React.FC = (props) => { callback, status, lintResultSet, - unauthorizedDBResources + unauthorizedDBResources, } = props; const [loading, setLoading] = useState(false); const [isFormatting, setIsFormatting] = useState(false); @@ -70,12 +70,13 @@ const ExecuteSQLModal: React.FC = (props) => { const editorRef = useRef(); const connectionMode = sessionStore?.connection?.dialectType; const config = getDataSourceModeConfigByConnectionMode(connectionMode); - const [permissionList, setPermissionList] = useState(unauthorizedDBResources); + const [permissionList, setPermissionList] = + useState(unauthorizedDBResources); const hadlintResultSet = lintResultSet?.length > 0; - useEffect(()=>{ - setPermissionList(unauthorizedDBResources) - }, [unauthorizedDBResources]) + useEffect(() => { + setPermissionList(unauthorizedDBResources); + }, [unauthorizedDBResources]); const hasTable = useMemo(() => { return hadlintResultSet || permissionList?.length > 0; diff --git a/src/component/ScriptPage/index.tsx b/src/component/ScriptPage/index.tsx index e2edc3a30..ab6ccc8e8 100644 --- a/src/component/ScriptPage/index.tsx +++ b/src/component/ScriptPage/index.tsx @@ -80,7 +80,7 @@ export default class ScriptPage extends PureComponent { templateName: '', offset: null, /// resultHeight: RESULT_HEIGHT - editorValue: this.props?.editor?.defaultValue + editorValue: this.props?.editor?.defaultValue, }; componentDidMount() { @@ -94,7 +94,7 @@ export default class ScriptPage extends PureComponent { } setStateForEditorValue = (value: string) => { - this.props?.ctx?.handleSQLChanged(value) + this.props?.ctx?.handleSQLChanged(value); this.setState({ editorValue: value, }); diff --git a/src/component/Task/DataArchiveTask/CreateModal/index.tsx b/src/component/Task/DataArchiveTask/CreateModal/index.tsx index 8a3683f47..fee639e4b 100644 --- a/src/component/Task/DataArchiveTask/CreateModal/index.tsx +++ b/src/component/Task/DataArchiveTask/CreateModal/index.tsx @@ -567,6 +567,9 @@ const CreateModal: React.FC = (props) => { })} /*源端数据库*/ projectId={projectId} onChange={handleDBChange} + options={{ + hideFileSystem: true, + }} /> void; } const DatabaseSelect: React.FC = (props) => { @@ -49,6 +53,7 @@ const DatabaseSelect: React.FC = (props) => { disabled = false, isLogicalDatabase = false, onChange, + options, } = props; return ( @@ -75,6 +80,7 @@ const DatabaseSelect: React.FC = (props) => { onChange={onChange} isLogicalDatabase={isLogicalDatabase} placeholder={placeholder} + options={options} /> ); diff --git a/src/component/Task/component/DatabaseSelecter/index.tsx b/src/component/Task/component/DatabaseSelecter/index.tsx index 3038de927..2dcf7f06a 100644 --- a/src/component/Task/component/DatabaseSelecter/index.tsx +++ b/src/component/Task/component/DatabaseSelecter/index.tsx @@ -174,7 +174,13 @@ const DatabaseSelecter: React.FC = function ({ }} >
- {item?.name} + + {item?.name} + {item?.dataSource?.name} diff --git a/src/component/Task/component/TableSelecter/interface.ts b/src/component/Task/component/TableSelecter/interface.ts index 6a1977848..ba159e334 100644 --- a/src/component/Task/component/TableSelecter/interface.ts +++ b/src/component/Task/component/TableSelecter/interface.ts @@ -8,7 +8,7 @@ export interface tableTreeEventDataNode extends EventDataNode { } export interface TableSelecterRef { - loadDatabases: ()=> Promise + loadDatabases: () => Promise; loadTables: (dbId: number) => Promise<{ tables: LoadTableItems[]; externalTables: LoadTableItems[]; diff --git a/src/component/Task/helper.tsx b/src/component/Task/helper.tsx index 72621c6bb..43571265a 100644 --- a/src/component/Task/helper.tsx +++ b/src/component/Task/helper.tsx @@ -74,6 +74,9 @@ export const isCycleTaskPage = (type: TaskPageType) => { ].includes(type); }; +export const isSupportFileSystemTask = (type: TaskType) => { + return [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE].includes(type); +}; export const isSupportChangeDetail = (type: TaskType) => { return [TaskType.DATA_ARCHIVE, TaskType.DATA_DELETE].includes(type); }; diff --git a/src/constant/label.ts b/src/constant/label.ts index e199bcbf1..7835fba47 100644 --- a/src/constant/label.ts +++ b/src/constant/label.ts @@ -21,6 +21,7 @@ import { SchemaComparingResult, SQLLintMode, SQLSessionMode, + DatasourceGroup, } from '@/d.ts'; import { DBType, BooleanOptionType } from '@/d.ts/database'; import { ColumnStoreType } from '@/d.ts/table'; @@ -103,6 +104,16 @@ export const ConnectTypeText = { [ConnectType.DORIS]: 'Doris', [ConnectType.ORACLE]: 'Oracle', [ConnectType.PG]: 'PostgreSQL', + [ConnectType.OSS]: '阿里云 OSS', + [ConnectType.COS]: '腾讯云 COS', + [ConnectType.OBS]: '华为云 OBS', + [ConnectType.S3A]: 'AWS S3', +}; + +export const GruopTypeText = { + [DatasourceGroup.OceanBaseDatabase]: 'OceanBase 数据库', + [DatasourceGroup.OtherDatabase]: '其他数据库', + [DatasourceGroup.FileSystem]: '对象存储', }; export const DBTypeText = { diff --git a/src/d.ts/datasource.ts b/src/d.ts/datasource.ts index 76f5d1e1d..bbe587879 100644 --- a/src/d.ts/datasource.ts +++ b/src/d.ts/datasource.ts @@ -50,4 +50,8 @@ export enum IDataSourceType { Doris = 'doris', Oracle = 'oracle', PG = 'postgresql', + ALIYUNOSS = 'ALIYUNOSS', + QCLOUD = 'QCLOUD', + HUAWEI = 'HUAWEI', + AWSS3 = 'AWSS3', } diff --git a/src/d.ts/index.ts b/src/d.ts/index.ts index 19d1a2725..fe46823e5 100644 --- a/src/d.ts/index.ts +++ b/src/d.ts/index.ts @@ -915,6 +915,9 @@ export enum IConnectionTestErrorType { CONNECT_TYPE_NOT_MATCH = 'ConnectionDatabaseTypeMismatched', INIT_SCRIPT_FAILED = 'ConnectionInitScriptFailed', OB_WEAK_READ_CONSISTENCY_REQUIRED = 'ObWeakReadConsistencyRequired', + ACCESS_DENIED = 'AccessDenied', + INVALID_ACCESSKEY_ID = 'InvalidAccessKeyId', + SIGNATURE_DOES_NOT_MATCH = 'SignatureDoesNotMatch', } export interface IConnectionProperty { @@ -3378,6 +3381,16 @@ export enum ConnectType { DORIS = 'DORIS', PG = 'POSTGRESQL', ORACLE = 'ORACLE', + OSS = 'OSS', + COS = 'COS', + OBS = 'OBS', + S3A = 'S3A', +} + +export enum DatasourceGroup { + OceanBaseDatabase = 'OceanBaseDatabase', + OtherDatabase = 'OtherDatabase', + FileSystem = 'FileSystem', } export enum DragInsertType { @@ -3605,7 +3618,7 @@ export interface IPartitionPlanTable { ordinalPosition: unknown; type: string; valuesList: unknown; - parentPartitionDefinition?: IPartitionPlanTable['partition']['partitionDefinitions'] + parentPartitionDefinition?: IPartitionPlanTable['partition']['partitionDefinitions']; }[]; partitionKeyTypes: { name: string; diff --git a/src/d.ts/table.ts b/src/d.ts/table.ts index d4113b12d..cf63c2ff5 100644 --- a/src/d.ts/table.ts +++ b/src/d.ts/table.ts @@ -33,8 +33,8 @@ export interface IServerTable { verticalColumnNames: string[]; }>; partitionDefinitions?: Partial[]; - subpartition?: Partial - subpartitionTemplated?: boolean + subpartition?: Partial; + subpartitionTemplated?: boolean; }; DDL: string; createTime: number; @@ -163,7 +163,7 @@ export interface IServerTablePartitionDefinition { maxRows: number; minRows: number; ordinalPosition: number; - parentPartitionDefinition?: IServerTablePartitionDefinition + parentPartitionDefinition?: IServerTablePartitionDefinition; } /** diff --git a/src/page/Datasource/Datasource/Content/List/index.tsx b/src/page/Datasource/Datasource/Content/List/index.tsx index 052e54237..5c717b6c4 100644 --- a/src/page/Datasource/Datasource/Content/List/index.tsx +++ b/src/page/Datasource/Datasource/Content/List/index.tsx @@ -15,8 +15,9 @@ */ import { getConnectionList } from '@/common/network/connection'; +import { getDataSourceGroupByConnectType } from '@/common/datasource'; import { IConnection } from '@/d.ts'; -import { Space, Spin } from 'antd'; +import { Space, Spin, message } from 'antd'; import React, { forwardRef, useContext, @@ -33,7 +34,7 @@ import ListItem from '../ListItem'; import LoadingItem from '../ListItem/Loading'; import ConnectionName from './ConnectionNameItem'; import MoreBtn from './MoreBtn'; - +import { isConnectTypeBeFileSystemGroup } from '@/util/connection'; import { DataSourceEmpty } from '@/component/Empty/DataSourceEmpty'; import RiskLevelLabel from '@/component/RiskLevelLabel'; import { IPageType } from '@/d.ts/_index'; @@ -90,6 +91,10 @@ const List: React.FC = forwardRef(function ( }, []); async function openNewConnection(connection: IConnection) { + if (isConnectTypeBeFileSystemGroup(connection?.type)) { + message.info('对象存储数据源暂不支持查看详情'); + return; + } history.push(`/datasource/${connection.id}/${IPageType.Datasource_info}`); } diff --git a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/Account/PrivateAccount.tsx b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/Account/PrivateAccount.tsx index 4cefcd4c0..388faad5e 100644 --- a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/Account/PrivateAccount.tsx +++ b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/Account/PrivateAccount.tsx @@ -23,23 +23,12 @@ import React, { useContext, useMemo, useState } from 'react'; import DatasourceFormContext from '../context'; import FormItemGroup from '../FormItemGroup'; import UserInput from './UserInput'; +import ErrorTip from '../components/ErrorTip'; interface IProps { isEdit: boolean; } -export const ErrorTip: React.FC<{ - errorMessage: string; -}> = ({ errorMessage }) => { - return ( - !!errorMessage && ( -
- {errorMessage} -
- ) - ); -}; - const PrivateAccount: React.FC = function (props) { const { isEdit } = props; const [passwordIsEditing, setPasswordIsEditing] = useState(false); diff --git a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/AddressItems.tsx b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/AddressItems.tsx index 9473da673..45f5e8780 100644 --- a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/AddressItems.tsx +++ b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/AddressItems.tsx @@ -288,6 +288,10 @@ const AddressItems: React.FC = function (props) {
); }; + + if (dataSourceConfig?.cloudStorage) { + return null; + } return ( <>
diff --git a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/CloudStorageForm/index.tsx b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/CloudStorageForm/index.tsx new file mode 100644 index 000000000..79f45da46 --- /dev/null +++ b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/CloudStorageForm/index.tsx @@ -0,0 +1,172 @@ +import DatasourceFormContext from '../context'; +import { useContext, useMemo, useState } from 'react'; +import { Space, Form, Input, Typography, Row, message } from 'antd'; +import Action from '@/component/Action'; +import { formatMessage } from '@/util/intl'; +import ErrorTip from '../components/ErrorTip'; +import { testConnection } from '@/common/network/connection'; +import { AccountType, IConnectionTestErrorType, ConnectType } from '@/d.ts'; + +interface CloudStorageFormProps { + isEdit: boolean; +} + +const CloudStorageForm: React.FC = (props) => { + const { dataSourceConfig, form } = useContext(DatasourceFormContext) || {}; + const { isEdit } = props; + const [errorMessage, setErrorMessage] = useState(undefined); + const [testResult, setTestResult] = useState<{ + active: boolean; + errorCode: IConnectionTestErrorType; + errorMessage: string; + type: ConnectType; + }>(); + if (!dataSourceConfig?.cloudStorage) { + return null; + } + + const handleTest = async () => { + try { + await form.validateFields(['username', 'password', 'host', 'defaultSchema']); + } catch (error) { + console.error('Validation failed:', error); + return; + } + const params = form.getFieldsValue(['username', 'password', 'host', 'type', 'defaultSchema']); + const res = await testConnection(params, AccountType.MAIN, true); + if (res?.errMsg) { + setTestResult({ + errorCode: IConnectionTestErrorType.UNKNOWN, + errorMessage: res?.errMsg, + active: false, + type: null, + }); + return; + } + if (!res?.data?.active) { + setErrorMessage(res?.data?.errorMessage); + switch (res?.data?.errorCode) { + case IConnectionTestErrorType.ACCESS_DENIED: + case IConnectionTestErrorType.INVALID_ACCESSKEY_ID: { + form.setFields([ + { + errors: [res?.data?.errorMessage], + name: ['username'], + }, + ]); + break; + } + case IConnectionTestErrorType.SIGNATURE_DOES_NOT_MATCH: { + form.setFields([ + { + errors: [res?.data?.errorMessage], + name: ['password'], + }, + ]); + break; + } + } + } + setTestResult(res?.data); + setErrorMessage(''); + message.success('测试连接成功'); + }; + + const passwordValidStatus = useMemo(() => { + if (testResult?.active) { + return 'success'; + } else if ( + [IConnectionTestErrorType.SIGNATURE_DOES_NOT_MATCH].includes(testResult?.errorCode) + ) { + return 'error'; + } + }, [testResult]); + + const usernameValidStatus = useMemo(() => { + if (testResult?.active) { + return 'success'; + } else if ( + [ + IConnectionTestErrorType.ACCESS_DENIED, + IConnectionTestErrorType.INVALID_ACCESSKEY_ID, + ].includes(testResult?.errorCode) + ) { + return 'error'; + } + }, [testResult]); + + return ( + <> + + + + + + + + + + + + + + + + + + + { + return handleTest(); + }} + > + {formatMessage({ + id: 'portal.connection.form.test', + defaultMessage: '测试连接', + })} + + + + + ); +}; + +export default CloudStorageForm; diff --git a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/ExtraConfig/index.tsx b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/ExtraConfig/index.tsx index 187a1b388..ef61f5b70 100644 --- a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/ExtraConfig/index.tsx +++ b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/ExtraConfig/index.tsx @@ -64,6 +64,9 @@ const ExtraConfig: React.FC = function () { forceRender: true, children: , }; + if (context?.dataSourceConfig?.disableExtraConfig) { + return null; + } return ( = ({ errorMessage }) => { + return ( + !!errorMessage && ( +
+ {errorMessage} +
+ ) + ); +}; + +export default ErrorTip; diff --git a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/index.tsx b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/index.tsx index cebaa143f..67d6345c9 100644 --- a/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/index.tsx +++ b/src/page/Datasource/Datasource/NewDatasourceDrawer/Form/index.tsx @@ -23,22 +23,24 @@ import { import { testConnection } from '@/common/network/connection'; import { listEnvironments } from '@/common/network/env'; import RiskLevelLabel from '@/component/RiskLevelLabel'; +import { isConnectTypeBeFileSystemGroup } from '@/util/connection'; import { ConnectTypeText } from '@/constant/label'; -import { AccountType, ConnectType, IConnectionTestErrorType } from '@/d.ts'; +import { AccountType, ConnectType, IConnectionTestErrorType, DatasourceGroup } from '@/d.ts'; import { IDatasource, IDataSourceType } from '@/d.ts/datasource'; import login from '@/store/login'; import { haveOCP } from '@/util/env'; import { formatMessage } from '@/util/intl'; import Icon from '@ant-design/icons'; import { useRequest } from 'ahooks'; -import { Form, FormInstance, Input, Select, Space, Typography } from 'antd'; -import { forwardRef, useImperativeHandle, useState } from 'react'; +import { Form, FormInstance, Input, Select, Space, Typography, Alert, Button } from 'antd'; +import { forwardRef, useImperativeHandle, useState, useMemo } from 'react'; import Account from './Account'; import AddressItems from './AddressItems'; import DatasourceFormContext from './context'; import ExtraConfig from './ExtraConfig'; import ParseURLItem from './ParseURLItem'; import ProjectItem from './ProjectItem'; +import CloudStorageForm from './CloudStorageForm'; const Option = Select.Option; export interface IFormRef { form: FormInstance; @@ -162,6 +164,32 @@ export default forwardRef(function DatasourceForm( ? getAllConnectTypes(getDsByConnectType(type)) : getAllConnectTypes(IDataSourceType.OceanBase); const dsc = getDataSourceModeConfig(type)?.connection; + + const AlertMessage = useMemo(() => { + if (isConnectTypeBeFileSystemGroup(type)) { + return ( + { + // dev_ing + }} + > + 查看详情 + + } + /> + ); + } + }, [type]); + return ( (function DatasourceForm( disableTheme, }} > + {AlertMessage}
(function DatasourceForm( autoType={!isEdit} /> )} - {dsc?.defaultSchema ? ( (function DatasourceForm( ) : null} + = function NewDatasourceButton(props) { const [visible, setVisible] = useState(false); const [type, setType] = useState(null); - const obConnectTypes = getAllConnectTypes(IDataSourceType.OceanBase); - const mysqlConnectTypes = getAllConnectTypes(IDataSourceType.MySQL); - const dorisConnectTypes = getAllConnectTypes(IDataSourceType.Doris); - const oracleConnectTypes = getAllConnectTypes(IDataSourceType.Oracle); - const pgConnectTypes = getAllConnectTypes(IDataSourceType.PG); + const connectTypes = [ + ...(getAllConnectTypes(IDataSourceType.OceanBase) || []), + ...(getAllConnectTypes(IDataSourceType.MySQL) || []), + ...(getAllConnectTypes(IDataSourceType.Doris) || []), + ...(getAllConnectTypes(IDataSourceType.Oracle) || []), + ...(getAllConnectTypes(IDataSourceType.PG) || []), + ...(getAllConnectTypes(IDataSourceType.ALIYUNOSS) || []), + ...(getAllConnectTypes(IDataSourceType.QCLOUD) || []), + ...(getAllConnectTypes(IDataSourceType.HUAWEI) || []), + ...(getAllConnectTypes(IDataSourceType.AWSS3) || []), + ]; const batchImportRef = useRef<{ closeModal: () => void; @@ -87,69 +99,19 @@ const NewDatasourceButton: React.FC<{ } }; - const results: ItemType[] = useMemo(() => { - let results: ItemType[] = obConnectTypes.map((item) => { + const results: MenuItemGroupType[] = useMemo(() => { + let results: MenuItemGroupType[] = Object.values(DatasourceGroup).map((item) => { return { - label: ConnectTypeText[item], + label: GruopTypeText[item], key: item, - icon: ( - - ), + type: 'group', + children: [], }; }); - if (mysqlConnectTypes?.length || oracleConnectTypes?.length) { - results.push({ - type: 'divider', - }); - if (mysqlConnectTypes?.length) { - results = results.concat( - mysqlConnectTypes.map((item) => { - return { - label: ConnectTypeText[item], - key: item, - icon: ( - - ), - }; - }), - ); - } - if (oracleConnectTypes?.length) { - results = results.concat( - oracleConnectTypes.map((item) => { - return { - label: ConnectTypeText[item], - key: item, - icon: ( - - ), - }; - }), - ); - } - } - if (dorisConnectTypes?.length) { - results.push({ - type: 'divider', - }); - results = results.concat( - dorisConnectTypes.map((item) => { - return { + results.forEach((at) => { + connectTypes.forEach((item) => { + if (getDataSourceGroupByConnectType(item) === at.key) { + at.children.push({ label: ConnectTypeText[item], key: item, icon: ( @@ -161,45 +123,31 @@ const NewDatasourceButton: React.FC<{ }} /> ), - }; - }), - ); - } - if (pgConnectTypes?.length) { - results.push({ - type: 'divider', + }); + } }); - results = results.concat( - pgConnectTypes.map((item) => { - return { - label: ConnectTypeText[item], - key: item, - icon: ( - - ), - }; - }), + }); + return results; + }, []); + + const customDropdownContent = useMemo(() => { + if (haveOCP()) { + return null; + } else { + return ( + <> + + + + + ); } - if (!haveOCP()) { - results.push({ - type: 'divider', - }); - results = results.concat({ - label: formatMessage({ - id: 'odc.component.BatchImportButton.BatchImport', - defaultMessage: '批量导入', - }) /*批量导入*/, - key: 'batchImport', - }); - } - return results; }, []); function batchImport() { @@ -228,20 +176,23 @@ const NewDatasourceButton: React.FC<{ setType(key); setVisible(true); } + return ( <> ( + <> + {menu} + {customDropdownContent} + + )} > {props.children || ( ); } diff --git a/src/page/Workspace/SideBar/ResourceTree/Nodes/table.tsx b/src/page/Workspace/SideBar/ResourceTree/Nodes/table.tsx index 98a38fa60..d2a856af0 100644 --- a/src/page/Workspace/SideBar/ResourceTree/Nodes/table.tsx +++ b/src/page/Workspace/SideBar/ResourceTree/Nodes/table.tsx @@ -163,23 +163,25 @@ export function TableTreeData(dbSession: SessionStore, database: IDatabase): Tre const subpartitionsDataHelper = (key, partitions, name) => { if (!partitions) return []; - return partitions?.filter(_s=> _s?.parentName === name)?.map((s) => { - return { - title: s.name, - key: `${key}-${s.name}`, - isLeaf: true, - sessionId: dbSession?.sessionId, - icon: ( - - ), - type: ResourceNodeType.TablePartition, - }; - }); + return partitions + ?.filter((_s) => _s?.parentName === name) + ?.map((s) => { + return { + title: s.name, + key: `${key}-${s.name}`, + isLeaf: true, + sessionId: dbSession?.sessionId, + icon: ( + + ), + type: ResourceNodeType.TablePartition, + }; + }); }; /** diff --git a/src/page/Workspace/components/DBPermissionTableContent/index.tsx b/src/page/Workspace/components/DBPermissionTableContent/index.tsx index 28324fcd6..42a5f2fe4 100644 --- a/src/page/Workspace/components/DBPermissionTableContent/index.tsx +++ b/src/page/Workspace/components/DBPermissionTableContent/index.tsx @@ -102,7 +102,7 @@ const getColumns = ( }); /* 无法申请数据库权限:数据库没有归属项目 */ tableTooltip = _.projectId ? `无法申请表/视图权限:没有加入数据库所属项目` - : `无法申请表/视图权限:表所属数据库没有归属项目` + : `无法申请表/视图权限:表所属数据库没有归属项目`; } return ( diff --git a/src/page/Workspace/components/SQLPage/index.tsx b/src/page/Workspace/components/SQLPage/index.tsx index 04f018ed0..10093dfff 100644 --- a/src/page/Workspace/components/SQLPage/index.tsx +++ b/src/page/Workspace/components/SQLPage/index.tsx @@ -361,7 +361,7 @@ export class SQLPage extends Component { const selectedSQL = this.editor.getSelectionContent(); const sqlToExecute = selectedSQL || params.scriptText; - const range = await utils.getCurrentSelectRange(this.editor); + const range = await utils.getCurrentSelectRange(this.editor); const result = await this.executeSQL( sqlToExecute, false, diff --git a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx index 1ab077d2a..3594e4c1d 100644 --- a/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx +++ b/src/page/Workspace/components/SessionContextWrap/SessionSelect/SelectItem.tsx @@ -43,6 +43,9 @@ interface IProps { datasourceMode?: boolean; projectMode?: boolean; onChange?: (value: number) => void; + options?: { + hideFileSystem?: boolean; + }; } const SelectItem: React.FC = ({ @@ -60,6 +63,7 @@ const SelectItem: React.FC = ({ isLogicalDatabase = false, datasourceMode = false, projectMode = isLogicalDatabase, + options, }) => { const [from, setFrom] = useState<'project' | 'datasource'>( datasourceMode || projectMode ? (datasourceMode ? 'datasource' : 'project') : 'datasource', @@ -178,6 +182,7 @@ const SelectItem: React.FC = ({ filters={filters} width={width || DEFALT_WIDTH} taskType={taskType} + options={options} >